UNPKG

wed

Version:

Wed is a schema-aware editor for XML documents.

306 lines 12.8 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; define(["require", "exports", "merge-options", "./domutil", "./mode-loader", "./wed-options-validation"], function (require, exports, merge_options_1, domutil_1, mode_loader_1, wed_options_validation_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); merge_options_1 = __importDefault(merge_options_1); /** * A node for the mode tree. */ class ModeNode { /** * @param mode The mode that this node holds. * * @param editor The editor for which we are holding a mode. * * @param selector The selector that determines to what this modes apply. This * selector must have been converted to operate in the GUI tree. * * @param submodes The submodes set for this mode. * * @param wedOptions The cleaned up wed options that pertain to the mode held * by this node. */ constructor(mode, editor, selector, submodes, wedOptions) { this.mode = mode; this.editor = editor; this.selector = selector; this.submodes = submodes; this.wedOptions = wedOptions; } /** * Determines whether an element matched by the selector of this ``ModeNode`` * node in the GUI tree contains a node. If it does, this means that the mode * that this ``ModeNode`` holds, or one of the submode, governs the node. * * @param parentScope The element from which the selector in this ``ModeNode`` * is interpreted. * * @param node A GUI node to test. * * @returns The element that represents the top of the mode's region of * activity and contains ``node``. Returns ``null`` if no element contains the * node. */ containingElement(parentScope, node) { if (!parentScope.contains(node)) { return null; } if (this.selector === "") { return parentScope; } const regions = parentScope.querySelectorAll(this.selector); for (const region of Array.from(regions)) { if (region.contains(node)) { return region; } } return null; } reduceTopFirst(fn, initialValue) { let value = fn(initialValue, this); for (const submode of this.submodes) { value = submode.reduceTopFirst(fn, value); } return value; } eachTopFirst(fn) { fn(this); for (const submode of this.submodes) { submode.eachTopFirst(fn); } } get attributeHidingSpecs() { if (this._attributeHidingSpecs === undefined) { const attributeHiding = this.wedOptions.attributes.autohide; if (attributeHiding === undefined) { // No attribute hiding... this._attributeHidingSpecs = null; } else { const method = attributeHiding.method; if (method !== "selector") { throw new Error(`unknown attribute hiding method: ${method}`); } const specs = { elements: [], }; for (const element of attributeHiding.elements) { const copy = merge_options_1.default({}, element); copy.selector = domutil_1.toGUISelector(copy.selector, this.mode.getAbsoluteNamespaceMappings()); specs.elements.push(copy); } this._attributeHidingSpecs = specs; } } return this._attributeHidingSpecs; } get decorator() { if (this._decorator === undefined) { this._decorator = this.mode.makeDecorator(); } return this._decorator; } } /** * A tree containing the modes configured for the current editing session. */ class ModeTree { /** * @param editor The editor for which we are building this tree. * * @param option The ``mode`` option from the options passed to the wed * instance. This object will construct a tree from this option. */ constructor(editor, option) { this.editor = editor; this.option = option; this.loader = new mode_loader_1.ModeLoader(editor, editor.runtime); } /** * Load the modes, initialize them and build the tree. * * @returns A promise that resolves to ``this`` once all the modes are loaded * and initialized. */ init() { return __awaiter(this, void 0, void 0, function* () { const combinedErrors = []; this.root = yield this.makeNodes("", this.option, (path, errors) => { for (const error of errors) { combinedErrors.push(`mode at path ${path} has an error in its wed options: ${error}`); } }); if (combinedErrors.length > 0) { throw new Error(`wed options are incorrect: ${combinedErrors.join("")}`); } return this; }); } /** * Make the nodes of the tree. This function operates recursively: it will * inspect ``option`` for a ``submode`` option and will call itself to create * the necessary child nodes. * * @param selector The selector associated with the options passed in the 2nd * argument. * * @param option The mode option being processed. * * @param errorHanler The handler to call on errors in processing the wed * options. If this handler is called at all, then the returned value should * not be used. We operate this way because we want to report all errors that * can be reported, rather than abort early. * * @returns A promise that resolves to the created node. */ makeNodes(selector, option, errorHandler) { return __awaiter(this, void 0, void 0, function* () { const submode = option.submode; const mode = yield this.loader.initMode(option.path, option.options); const submodes = (submode !== undefined) ? [yield this.makeNodes(domutil_1.toGUISelector(submode.selector, mode.getAbsoluteNamespaceMappings()), submode.mode, errorHandler)] : []; const rawOptions = mode.getWedOptions(); const result = wed_options_validation_1.processWedOptions(rawOptions); let cleanedOptions; if (Array.isArray(result)) { errorHandler(option.path, result); // This is a lie. cleanedOptions = rawOptions; } else { cleanedOptions = result; } return new ModeNode(mode, this.editor, selector, submodes, cleanedOptions); }); } getMode(node) { return this.getModeNode(node).mode; } getDecorator(node) { return this.getModeNode(node).decorator; } getWedOptions(node) { const modeNode = this.getModeNode(node); return modeNode.wedOptions; } getAttributeHandling(node) { return this.getWedOptions(node).attributes.handling; } getAttributeHidingSpecs(node) { return this.getModeNode(node).attributeHidingSpecs; } /** * Get the mode node that governs a node. * * @param The node we want to check. This must be a done in the data tree or * the GUI tree. * * @returns The mode that governs the node. */ getModeNode(node) { // Handle the trivial case where there is no submode first. if (this.root.submodes.length === 0) { return this.root; } if (domutil_1.contains(this.editor.dataRoot, node)) { const data = this.editor.fromDataNode(node); if (data !== null) { node = data; } } if (!this.editor.guiRoot.contains(node)) { throw new Error("did not pass a node in the GUI or data tree"); } const result = this._getModeNode(this.root, this.editor.guiRoot, node); if (result === undefined) { throw new Error("cannot find a mode for the node; something is wrong"); } return result; } _getModeNode(parent, parentScope, node) { const scope = parent.containingElement(parentScope, node); if (scope !== null) { let narrower; for (const submode of parent.submodes) { narrower = this._getModeNode(submode, scope, node); if (narrower !== undefined) { return narrower; } } return parent; } return undefined; } getStylesheets() { return Object.keys(this.root.reduceTopFirst((accumulator, node) => { for (const sheet of node.mode.getStylesheets()) { accumulator[sheet] = true; } return accumulator; }, Object.create(null))); } getMaxLabelLevel() { return this.maxLabelLevelNode.wedOptions.label_levels.max; } getInitialLabelLevel() { return this.maxLabelLevelNode.wedOptions.label_levels.initial; } /** * The node with the maximum label visibility level. If multiple nodes have * the same value, the earlier node "wins", and is the one provided by this * property. For instance, if the root node and its submode have the same * number, then this property has the root node for value. * * This is a cached value, computed on first access. */ get maxLabelLevelNode() { if (this.cachedMaxLabelNode === undefined) { this.cachedMaxLabelNode = this.root.reduceTopFirst((accumulator, node) => { const accMax = accumulator.wedOptions.label_levels.max; const nodeMax = node.wedOptions.label_levels.max; return (nodeMax > accMax) ? node : accumulator; }, this.root); } return this.cachedMaxLabelNode; } getValidators() { return this.root.reduceTopFirst((accumulator, node) => { const validator = node.mode.getValidator(); return validator !== undefined ? accumulator.concat(validator) : accumulator; }, []); } /** * Call on each decorator to add its event handlers. */ addDecoratorHandlers() { this.root.eachTopFirst((node) => { node.decorator.addHandlers(); }); } /** * Call on each decorator to start listening. */ startListening() { this.root.eachTopFirst((node) => { node.decorator.startListening(); }); } } exports.ModeTree = ModeTree; }); // LocalWords: MPL submodes submode combinedErrors nd preprocessed // LocalWords: stylesheets //# sourceMappingURL=mode-tree.js.map