diff --git a/.gitignore b/.gitignore index 5e90c60cd..6213482b3 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,6 @@ keys.txt export.sh dist/ tmp/ +docs/ server.sh diff --git a/.jsdoc b/.jsdoc new file mode 100644 index 000000000..25ce96588 --- /dev/null +++ b/.jsdoc @@ -0,0 +1,13 @@ +{ + "source": { + "include": ["./src/js"] + }, + "opts": { + "readme": "./README.md", + "recurse": true, + "destination": "./docs" + }, + "plugins": [ + "plugins/markdown" + ] +} diff --git a/package.json b/package.json index 802d018b5..cb1626edb 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "build": "rm -rf dist && broccoli build dist", "build-website": "./bin/build-website.sh", "deploy-website": "./bin/deploy-website.sh", - "update-changelog": "./node_modules/conventional-changelog-cli/cli.js -i CHANGELOG.md -r 0 -s && git add CHANGELOG.md && git commit -m 'Update changelog'" + "update-changelog": "conventional-changelog -i CHANGELOG.md -r 0 -s && git add CHANGELOG.md && git commit -m 'Update changelog'", + "docs": "jsdoc -c ./.jsdoc" }, "keywords": [ "html", @@ -49,6 +50,7 @@ "conventional-changelog": "^1.1.0", "conventional-changelog-cli": "^1.1.1", "jquery": "^2.2.2", + "jsdoc": "^3.4.0", "saucie": "^1.4.0", "testem": "^1.6.0", "tiny-lr": "^0.2.1" diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 3f2b569c6..34524581f 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -78,13 +78,42 @@ const CALLBACK_QUEUES = { DID_REPARSE: 'didReparse' }; -/** - * @class Editor - * An individual Editor - * @param element `Element` node - * @param options hash of options - */ class Editor { + /** + * The Editor is a core component of mobiledoc-kit. After instantiating + * an editor, use {@link Editor#render} to display the editor on the web page. + * + * An editor uses a {@link Post} internally to represent the displayed document. + * The post can be serialized as mobiledoc using {@link Editor#serialize}. Mobiledoc + * is the transportable "over-the-wire" format (JSON) that is suited for persisting + * and sharing between editors and renderers (for display, e.g.), whereas the Post + * model is better suited for programmatic editing. + * + * The editor will call registered callbacks for certain state changes. These are: + * * cursorDidChange + * * didUpdate + * + * @param {Object} [options] + * @param {Object} [options.mobiledoc] The mobiledoc to load into the editor. + * Supersedes `options.html`. + * @param {String|DOM} [options.html] The html (as a string or DOM fragment) + * to parse and load into the editor. + * Will be ignored if `options.mobiledoc` is also passed. + * @param {Array} [options.parserPlugins=[]] + * @param {Array} [options.cards=[]] The cards that the editor may render. + * @param {Array} [options.atoms=[]] The atoms that the editor may render. + * @param {Function} [options.unknownCardHandler] Invoked by the editor's renderer + * whenever it encounters an unknown card. + * @param {Function} [options.unknownAtomHandler] Invoked by the editor's renderer + * whenever it encounters an unknown atom. + * @param {String} [options.placeholder] Default text to show before user starts typing. + * @param {Boolean} [options.spellcheck=true] Whether to enable spellcheck + * @param {Boolean} [options.autofocus=true] Whether to focus the editor when it is first rendered. + * @param {number} [options.undoDepth=5] How many undo levels will be available. + * Set to 0 to disable undo/redo functionality. + * @return {Editor} + * @public + */ constructor(options={}) { assert('editor create accepts an options object. For legacy usage passing an element for the first argument, consider the `html` option for loading DOM or HTML posts. For other cases call `editor.render(domNode)` after editor creation', (options && !options.nodeType)); @@ -94,14 +123,14 @@ class Editor { // FIXME: This should merge onto this.options mergeWithOptions(this, defaults, options); - this.cards.push(ImageCard); DEFAULT_TEXT_EXPANSIONS.forEach(e => this.registerExpansion(e)); DEFAULT_KEY_COMMANDS.forEach(kc => this.registerKeyCommand(kc)); this._parser = new DOMParser(this.builder); - this._renderer = new Renderer(this, this.cards, this.atoms, this.unknownCardHandler, this.unknownAtomHandler, this.cardOptions); + let {cards, atoms, unknownCardHandler, unknownAtomHandler, cardOptions} = this; + this._renderer = new Renderer(this, cards, atoms, unknownCardHandler, unknownAtomHandler, cardOptions); this.post = this.loadPost(); this._renderTree = new RenderTree(this.post); @@ -123,14 +152,15 @@ class Editor { } loadPost() { - if (this.mobiledoc) { - return mobiledocParsers.parse(this.builder, this.mobiledoc); - } else if (this.html) { - if (typeof this.html === 'string') { + let {mobiledoc, html} = this; + if (mobiledoc) { + return mobiledocParsers.parse(this.builder, mobiledoc); + } else if (html) { + if (typeof html === 'string') { let options = {plugins: this._parserPlugins}; return new HTMLParser(this.builder, options).parse(this.html); } else { - let dom = this.html; + let dom = html; return this._parser.parse(dom); } } else { @@ -156,6 +186,11 @@ class Editor { this.runCallbacks(CALLBACK_QUEUES.DID_RENDER); } + /** + * @param {Element} element The DOM element to render into. + * Its contents will be replaced by the editor's rendered post. + * @public + */ render(element) { assert('Cannot render an editor twice. Use `rerender` to update the ' + 'rendering of an existing editor instance.', @@ -209,7 +244,6 @@ class Editor { } /** - * @method registerExpansion * @param {Object} expansion The text expansion to register. It must specify a * trigger character (e.g. the `` character) and a text string that precedes * the trigger (e.g. "*"), and a `run` method that will be passed the @@ -222,7 +256,6 @@ class Editor { } /** - * @method registerKeyCommand * @param {Object} keyCommand The key command to register. It must specify a * modifier key (meta, ctrl, etc), a string representing the ascii key, and * a `run` method that will be passed the editor instance when the key command @@ -236,7 +269,7 @@ class Editor { } /** - * @param {KeyEvent} event optional + * @param {KeyEvent} [event] * @private */ handleDeletion(event=null) { @@ -303,9 +336,9 @@ class Editor { } /** - * @private * If the range is different from the previous range, this method will * fire 'rangeDidChange'-related callbacks + * @private */ renderRange(range) { let prevRange = this._range; @@ -329,6 +362,7 @@ class Editor { * Return the current range for the editor (may be cached). * The #_resetRange method forces a re-read of * the range from DOM. + * @return {Range} */ get range() { if (this._range) { @@ -345,9 +379,10 @@ class Editor { this._range = newRange; } - /* + /** * force re-reading range from dom * Fires `rangeDidChange`-related callbacks if the range is different + * @private */ _resetRange() { let prevRange = this._range; @@ -410,7 +445,7 @@ class Editor { ); } - /* + /** * @return {array} The sections from the cursor's selection start to the selection end */ get activeSections() { @@ -445,21 +480,23 @@ class Editor { /** * @public + * * @param {string} version The mobiledoc version to serialize to. - * @return {Object} Serialized mobiledoc + * @return {Mobiledoc} Serialized mobiledoc */ serialize(version=MOBILEDOC_VERSION) { return this.serializePost(this.post, 'mobiledoc', {version}); } /** - * @public + * Serialize the editor's post to the requested format. * Note that only mobiledoc format is lossless. If cards or atoms are present * in the post, the html and text formats will omit them in output because * the editor does not have access to the html and text versions of the * cards/atoms. * @param {string} format The format to serialize ('mobiledoc', 'text', 'html') * @return {Object|String} The editor's post, serialized to {format} + * @public */ serializeTo(format) { let post = this.post; @@ -469,8 +506,10 @@ class Editor { /** * @param {Post} * @param {String} format Same as {serializeTo} - * @param {[Object]} version to serialize to (default: MOBILEDOC_VERSION} + * @param {Object} [options] + * @param {String} [options.version=MOBILEDOC_VERSION] version to serialize to * @return {Object|String} + * @private */ serializePost(post, format, options={}) { const validFormats = ['mobiledoc', 'html', 'text']; @@ -507,13 +546,18 @@ class Editor { * Whether the editor has a cursor (or a selected range). * It is possible for the editor to be focused but not have a selection. * In this case, key events will fire but the editor will not be able to - * determine a cursor position. - * @return {bool} + * determine a cursor position, so they will be ignored. + * @return {boolean} + * @public */ hasCursor() { return this.cursor.hasCursor(); } + /** + * Tears down the editor's attached event listeners and views. + * @public + */ destroy() { this._isDestroyed = true; if (this.hasCursor()) { @@ -527,10 +571,9 @@ class Editor { } /** - * Keep the user from directly editing the post. Modification via the - * programmatic API is still permitted. - * - * @method disableEditing + * Keep the user from directly editing the post using the keyboard and mouse. + * Modification via the programmatic API is still permitted. + * @see Editor#enableEditing * @public */ disableEditing() { @@ -542,10 +585,10 @@ class Editor { } /** - * Allow the user to directly interact with editing a post via a cursor. - * - * @method enableEditing - * @return undefined + * Allow the user to directly interact with editing a post via keyboard and mouse input. + * Editor instances are editable by default. Use this method to re-enable + * editing after disabling it. + * @see Editor#disableEditing * @public */ enableEditing() { @@ -561,7 +604,6 @@ class Editor { * If called before the card has been rendered, it will be marked so that * it is rendered in edit mode when it gets rendered. * @param {CardSection} cardSection - * @return undefined * @public */ editCard(cardSection) { @@ -581,29 +623,28 @@ class Editor { } /** - * Run a new post editing session. Yields a block with a new `postEditor` - * instance. This instance can be used to interact with the post abstract, - * and defers rendering until the end of all changes. + * Run a new post editing session. Yields a block with a new {@link PostEditor} + * instance. This instance can be used to interact with the post abstract. + * Rendering will be deferred until after the callback is completed. * * Usage: - * - * let markerRange = this.range; - * editor.run((postEditor) => { - * postEditor.deleteRange(markerRange); - * // editing surface not updated yet - * postEditor.schedule(() => { - * console.log('logs during rerender flush'); - * }); - * // logging not yet flushed + * ``` + * let markerRange = this.range; + * editor.run((postEditor) => { + * postEditor.deleteRange(markerRange); + * // editing surface not updated yet + * postEditor.schedule(() => { + * console.log('logs during rerender flush'); * }); - * // editing surface now updated. - * // logging now flushed + * // logging not yet flushed + * }); + * // editing surface now updated. + * // logging now flushed + * ``` * - * The return value of `run` is whatever was returned from the callback. - * - * @method run - * @param {Function} callback Function to handle post editing with, provided the `postEditor` as an argument. - * @return {} Whatever the return value of `callback` is. + * @param {Function} callback Called with an instance of + * {@link PostEditor} as its argument. + * @return {Mixed} The return value of `callback`. * @public */ run(callback) { @@ -636,17 +677,15 @@ class Editor { } /** - * @method didUpdatePost - * @param {Function} callback This callback will be called with `postEditor` - * argument when the post is updated * @public + * + * @param {Function} callback Called with `postEditor` as its argument. */ didUpdatePost(callback) { this.addCallback(CALLBACK_QUEUES.DID_UPDATE, callback); } /** - * @method willRender * @param {Function} callback This callback will be called before the editor * is rendered. * @public @@ -656,7 +695,6 @@ class Editor { } /** - * @method didRender * @param {Function} callback This callback will be called after the editor * is rendered. * @public @@ -666,7 +704,6 @@ class Editor { } /** - * @method cursorDidChange * @param {Function} callback This callback will be called after the cursor * position (or selection) changes. * @public @@ -703,6 +740,7 @@ class Editor { * Clear the cached active markups and force a re-read of the markups * from the current range. * If markups have changed, fires an event + * @private */ _resetActiveMarkups() { let activeMarkupsDidChange = this._editState.resetActiveMarkups(); @@ -768,7 +806,6 @@ class Editor { * If a command returns `false` then the next matching command * is run instead. * - * @method handleKeyCommand * @param {Event} event The keyboard event triggered by the user * @return {Boolean} true when a command was successfully run * @private @@ -806,7 +843,9 @@ class Editor { return Position.atPoint(x, y, this); } - // @private + /** + * @private + */ _setCardMode(cardSection, mode) { const renderNode = cardSection.renderNode; if (renderNode && renderNode.isRendered) { diff --git a/src/js/editor/post.js b/src/js/editor/post.js index ae5591377..629842d4b 100644 --- a/src/js/editor/post.js +++ b/src/js/editor/post.js @@ -19,6 +19,21 @@ const CALLBACK_QUEUES = { }; class PostEditor { + /** + * The PostEditor is used to modify a post. It should not be instantiated directly. + * Instead, a new instance of a PostEditor is created by the editor and passed + * as the argument to the callback in {@link Editor#run}. + * + * Usage: + * ``` + * editor.run((postEditor) => { + * // postEditor is an instance of PostEditor that can operate on the + * // editor's post + * }); + * ``` + * @param {Editor} editor + * @class PostEditor + */ constructor(editor) { this.editor = editor; this.builder = this.editor.builder; @@ -43,16 +58,15 @@ class PostEditor { } /** - * Remove a range from the post + * Delete a range from the post * * Usage: - * + * ``` * let { range } = editor; * editor.run((postEditor) => { * postEditor.deleteRange(range); * }); - * - * @method deleteRange + * ``` * @param {Range} range Cursor Range object with head and tail Positions * @return {Position} The position where the cursor would go after deletion * @public @@ -132,6 +146,7 @@ class PostEditor { /** * @return {Position} + * @private */ cutSection(section, headSectionOffset, tailSectionOffset) { if (section.isBlank || headSectionOffset === tailSectionOffset) { @@ -343,7 +358,6 @@ class PostEditor { * or direction is `FORWARD` and the offset is equal to the length of the * marker. * - * @method deleteFrom * @param {Position} position object with {section, offset} the marker and offset to delete from * @param {Number} direction The direction to delete in (default is BACKWARD) * @return {Position} for positioning the cursor @@ -393,7 +407,6 @@ class PostEditor { /** * delete 1 character in the FORWARD direction from the given position - * @method _deleteForwardFrom * @param {Position} position * @private */ @@ -448,8 +461,9 @@ class PostEditor { /** * delete 1 character forward from the markerPosition * - * @method _deleteForwardFromMarkerPosition - * @param {Object} markerPosition {marker, offset} + * @param {Object} markerPosition + * @param {Marker} markerPosition.marker + * @param {number} markerPosition.offset * @return {Position} The position the cursor should be put after this deletion * @private */ @@ -487,7 +501,6 @@ class PostEditor { /** * delete 1 character in the BACKWARD direction from the given position - * @method _deleteBackwardFrom * @param {Position} position * @return {Position} The position the cursor should be put after this deletion * @private @@ -534,16 +547,15 @@ class PostEditor { * (e.g. `editor.range`) as an argument. * * Usage: - * + * ``` * let markerRange = this.cursor.offsets; * editor.run((postEditor) => { * postEditor.splitMarkers(markerRange); * }); - * + * ``` * The return value will be marker object completely inside the offsets * provided. Markers on the outside of the split may also have been modified. * - * @method splitMarkers * @param {Range} markerRange * @return {Array} of markers that are inside the split * @private @@ -590,19 +602,18 @@ class PostEditor { * Split the section at the position. * * Usage: - * + * ``` * let position = editor.cursor.offsets.head; * editor.run((postEditor) => { * postEditor.splitSection(position); * }); * // Will result in the creation of two new sections * // replacing the old one at the cursor position - * + * ``` * The return value will be the two new sections. One or both of these * sections can be blank (contain only a blank marker), for example if the * headMarkerOffset is 0. * - * @method splitSection * @param {Position} position * @return {Array} new sections, one for the first half and one for the second * @public @@ -637,10 +648,9 @@ class PostEditor { } /** - * @method _splitCardSection * @param {Section} cardSection * @param {Position} position to split at - * @return {Array} pre and post-split sections + * @return {Section[]} 2-item array of pre and post-split sections * @private */ _splitCardSection(cardSection, position) { @@ -667,7 +677,6 @@ class PostEditor { } /** - * @method replaceSection * @param {Section} section * @param {Section} newSection * @return null @@ -691,7 +700,6 @@ class PostEditor { } /** - * @method moveSectionUp * @param {Section} section A section that is already in DOM * @public */ @@ -707,7 +715,6 @@ class PostEditor { } /** - * @method moveSectionDown * @param {Section} section A section that is already in DOM * @public */ @@ -798,7 +805,6 @@ class PostEditor { * The return value will be all markers between the split, the same return * value as `splitMarkers`. * - * @method addMarkupToRange * @param {Range} range * @param {Markup} markup A markup post abstract node * @public @@ -818,7 +824,7 @@ class PostEditor { * markup from all contained markers. * * Usage: - * + * ``` * let { range } = editor; * let markup = markerRange.headMarker.markups[0]; * editor.run(postEditor => { @@ -826,8 +832,7 @@ class PostEditor { * }); * // Will result in some markers possibly being split, and the markup * // being removed from all markers between the split. - * - * @method removeMarkupFromRange + * ``` * @param {Range} range Object with offsets * @param {Markup|Function} markupOrCallback A markup post abstract node or * a function that returns true when passed a markup that should be removed @@ -849,7 +854,7 @@ class PostEditor { * has the markup, the markup will be added to everything in the selection. * * Usage: - * + * ``` * // Remove any 'strong' markup if it exists in the selection, otherwise * // make it all 'strong' * editor.run(postEditor => postEditor.toggleMarkup('strong')); @@ -859,8 +864,7 @@ class PostEditor { * const linkMarkup = postEditor.builder.createMarkup('a', {href: 'http://bustle.com'}); * postEditor.toggleMarkup(linkMarkup); * }); - * - * @method toggleMarkup + * ``` * @param {Markup|String} markupOrString Either a markup object created using * the builder (useful when adding a markup with attributes, like an 'a' markup), * or, if a string, the tag name of the markup (e.g. 'strong', 'em') to toggle. @@ -886,15 +890,13 @@ class PostEditor { } /** - * @method toggleSection - * - * Toggles the active section or sections. + * Toggles the tagName of the active section or sections. * If every section has the tag name, they will all be reset to default sections. * Otherwise, every section will be changed to the requested type * * @param {String} sectionTagName A valid markup section or list section tag name (e.g. 'blockquote', 'h2', 'ul') * @param {Range} range The range over which to toggle. Defaults to the current editor's offsets - * a list section + * a list section * @public */ toggleSection(sectionTagName, range=this._range) { @@ -952,6 +954,7 @@ class PostEditor { * If thse position is at the start or end of the item, the pre- or post-item * will contain a single empty ("") marker. * @return {Array} the pre-item and post-item on either side of the split + * @private */ _splitListItem(item, position) { let { section, offset } = position; @@ -981,6 +984,8 @@ class PostEditor { * * Note: Contiguous list sections will be joined in the before_complete queue * of the postEditor. + * + * @private */ _splitListAtPosition(list, position) { assert('Cannot split list at position not in list', @@ -1029,6 +1034,7 @@ class PostEditor { * be a 1-item list containing `item`. `prev` and `next` will be * removed in the before_complete queue if they are blank * (and still attached). + * * @private */ _splitListAtItem(list, item) { @@ -1113,7 +1119,7 @@ class PostEditor { * and the rendered UI. * * Usage: - * + * ``` * let markerRange = editor.range; * let sectionWithCursor = markerRange.headMarker.section; * let section = editor.builder.createCardSection('my-image'); @@ -1121,12 +1127,11 @@ class PostEditor { * editor.run((postEditor) => { * postEditor.insertSectionBefore(collection, section, sectionWithCursor); * }); - * - * @method insertSectionBefore + * ``` * @param {LinkedList} collection The list of sections to insert into * @param {Object} section The new section * @param {Object} beforeSection Optional The section "before" is relative to, - * if falsy the new section will be appended to the collection + * if falsy the new section will be appended to the collection * @public */ insertSectionBefore(collection, section, beforeSection) { @@ -1137,7 +1142,6 @@ class PostEditor { /** * Insert the given section after the current active section, or, if no * section is active, at the end of the document. - * @method insertSection * @param {Section} section * @public */ @@ -1151,7 +1155,6 @@ class PostEditor { /** * Insert the given section at the end of the document. - * @method insertSectionAtEnd * @param {Section} section * @public */ @@ -1160,7 +1163,7 @@ class PostEditor { } /** - * @method insertPost + * Insert the `post` at the given position in the editor's post. * @param {Position} position * @param {Post} post * @private @@ -1176,14 +1179,13 @@ class PostEditor { * Remove a given section from the post abstract and the rendered UI. * * Usage: - * + * ``` * let { range } = editor; * let sectionWithCursor = range.head.section; * editor.run((postEditor) => { * postEditor.removeSection(sectionWithCursor); * }); - * - * @method removeSection + * ``` * @param {Object} section The section to remove * @public */ @@ -1224,7 +1226,6 @@ class PostEditor { /** * A method for adding work the deferred queue * - * @method schedule * @param {Function} callback to run during completion * @public */ @@ -1237,7 +1238,6 @@ class PostEditor { /** * Add a rerender job to the queue * - * @method scheduleRerender * @public */ scheduleRerender() { @@ -1250,7 +1250,6 @@ class PostEditor { /** * Add a didUpdate job to the queue * - * @method scheduleDidUpdate * @public */ scheduleDidUpdate() { @@ -1268,7 +1267,6 @@ class PostEditor { * Flush any work on the queue. `editor.run` already does this. Calling this * method directly should not be needed outside `editor.run`. * - * @method complete * @private */ complete() { diff --git a/src/js/models/post.js b/src/js/models/post.js index 35f8d29b6..d0e87af66 100644 --- a/src/js/models/post.js +++ b/src/js/models/post.js @@ -7,6 +7,15 @@ import Range from 'mobiledoc-kit/utils/cursor/range'; import Position from 'mobiledoc-kit/utils/cursor/position'; import deprecate from 'mobiledoc-kit/utils/deprecate'; +/** + * The Post is an in-memory representation of an editor's document. + * An editor always has a single post. The post is organized into a list of + * sections. Each section may be markerable (contains "markers", aka editable + * text) or non-markerable (e.g., a card). + * When persisting a post, it must first be serialized (loss-lessly) into + * mobiledoc using {@link Editor#serialize}. + * @class Post + */ export default class Post { constructor() { this.type = POST_TYPE; diff --git a/src/js/parsers/dom.js b/src/js/parsers/dom.js index e84af5895..308f4ee00 100644 --- a/src/js/parsers/dom.js +++ b/src/js/parsers/dom.js @@ -89,10 +89,10 @@ function walkMarkerableNodes(parent, callback) { } } -/** - * Parses DOM element -> Post - */ export default class DOMParser { + /** + * Parses DOM element -> Post + */ constructor(builder, options={}) { this.builder = builder; this.sectionParser = new SectionParser(this.builder, options); diff --git a/src/js/parsers/mobiledoc/0-2.js b/src/js/parsers/mobiledoc/0-2.js index 02d14df85..cc375849d 100644 --- a/src/js/parsers/mobiledoc/0-2.js +++ b/src/js/parsers/mobiledoc/0-2.js @@ -16,7 +16,6 @@ export default class MobiledocParser { } /** - * @method parse * @param {Mobiledoc} * @return {Post} */ diff --git a/src/js/parsers/mobiledoc/0-3.js b/src/js/parsers/mobiledoc/0-3.js index 137d88874..0e88f1304 100644 --- a/src/js/parsers/mobiledoc/0-3.js +++ b/src/js/parsers/mobiledoc/0-3.js @@ -18,7 +18,6 @@ export default class MobiledocParser { } /** - * @method parse * @param {Mobiledoc} * @return {Post} */ diff --git a/src/js/parsers/section.js b/src/js/parsers/section.js index 50ba7c0c4..2ea24ae06 100644 --- a/src/js/parsers/section.js +++ b/src/js/parsers/section.js @@ -44,12 +44,11 @@ const SKIPPABLE_ELEMENT_TAG_NAMES = [ 'style', 'head', 'title', 'meta' ].map(normalizeTagName); -/** - * parses an element into a section, ignoring any non-markup - * elements contained within - * @return {Array} sections - */ export default class SectionParser { + /** + * parses an element into a section, ignoring any non-markup + * elements contained within + */ constructor(builder, options={}) { this.builder = builder; this.plugins = options.plugins || []; diff --git a/src/js/renderers/editor-dom.js b/src/js/renderers/editor-dom.js index 7637d9dd2..6d49fec91 100644 --- a/src/js/renderers/editor-dom.js +++ b/src/js/renderers/editor-dom.js @@ -119,6 +119,7 @@ function renderCard() { /** * Wrap the element in all of the opened markups * @return {DOMElement} the wrapped element + * @private */ function wrapElement(element, openedMarkups) { let wrappedElement = element; @@ -188,9 +189,11 @@ function getNextMarkerElement(renderNode) { * @param {DOMNode} element the element to attach the rendered marker to * @param {RenderNode} [previousRenderNode] The render node before this one, which * affects the determination of where to insert this rendered marker. - * @return {element, markupElement} The element (textNode) that has the text for + * @return {Object} With properties `element` and `markupElement`. + * The element (textNode) that has the text for * this marker, and the outermost rendered element. If the marker has no * markups, element and markupElement will be the same textNode + * @private */ function renderMarker(marker, parentElement, previousRenderNode) { let text = renderHTMLText(marker); diff --git a/src/js/renderers/mobiledoc/0-2.js b/src/js/renderers/mobiledoc/0-2.js index 4afadce60..5e85061bd 100644 --- a/src/js/renderers/mobiledoc/0-2.js +++ b/src/js/renderers/mobiledoc/0-2.js @@ -111,7 +111,6 @@ const postOpcodeCompiler = { */ export default { /** - * @method render * @param {Post} * @return {Mobiledoc} */ diff --git a/src/js/renderers/mobiledoc/0-3.js b/src/js/renderers/mobiledoc/0-3.js index 8c9069ee9..b11b2a855 100644 --- a/src/js/renderers/mobiledoc/0-3.js +++ b/src/js/renderers/mobiledoc/0-3.js @@ -146,7 +146,6 @@ const postOpcodeCompiler = { */ export default { /** - * @method render * @param {Post} * @return {Mobiledoc} */ diff --git a/src/js/utils/array-utils.js b/src/js/utils/array-utils.js index ed109361c..1e20e3dfc 100644 --- a/src/js/utils/array-utils.js +++ b/src/js/utils/array-utils.js @@ -40,6 +40,7 @@ function toArray(arrayLike) { /** * Useful for array-like things that aren't * actually arrays, like NodeList + * @private */ function forEach(enumerable, callback) { if (enumerable.forEach) { @@ -61,6 +62,7 @@ function filter(enumerable, conditionFn) { /** * @return {Integer} the number of items that are the same, starting from the 0th index, in a and b + * @private */ function commonItemLength(listA, listB) { let offset = 0; @@ -89,6 +91,7 @@ function reduce(enumerable, callback, initialValue) { /** * @param {Array} array of key1,value1,key2,value2,... * @return {Object} {key1:value1, key2:value2, ...} + * @private */ function kvArrayToObject(array) { const obj = {}; diff --git a/src/js/utils/cursor/position.js b/src/js/utils/cursor/position.js index cb90f1ca7..2d069d713 100644 --- a/src/js/utils/cursor/position.js +++ b/src/js/utils/cursor/position.js @@ -56,14 +56,23 @@ function findOffsetInSection(section, node, offset) { } const Position = class Position { + /** + * A position is a logical location (zero-width, or "collapsed") in a post, + * typically between two characters in a section. + * Two positions (a head and a tail) make up a {@link Range}. + * @constructor + */ constructor(section, offset=0) { assert('Position must have a section that is addressable by the cursor', (section && section.isLeafSection)); assert('Position must have numeric offset', (offset !== null && offset !== undefined)); + /** @property {Section} section */ this.section = section; + /** @property {number} offset */ this.offset = offset; + this.isBlank = false; } @@ -137,12 +146,11 @@ const Position = class Position { } /** - * This method returns a new Position instance, it does not modify - * this instance. + * Move the position 1 unit in `direction`. * * @param {Direction} direction to move - * @return {Position|null} Return the position one unit in the given - * direction, or null if it is not possible to move that direction + * @return {Position|null} Return a new position one unit in the given + * direction or null if it is not possible to move that direction */ move(direction) { switch (direction) { @@ -288,7 +296,6 @@ const Position = class Position { assert('cannot get markerPosition of a non-markerable', !!this.section.isMarkerable); return this.section.markerPositionAtOffset(this.offset); } - }; export default Position; diff --git a/src/js/utils/cursor/range.js b/src/js/utils/cursor/range.js index 530769aa0..a9effaa86 100644 --- a/src/js/utils/cursor/range.js +++ b/src/js/utils/cursor/range.js @@ -2,12 +2,38 @@ import Position from './position'; import { DIRECTION } from '../key'; export default class Range { + /** + * A logical range of a {@link Post}. + * Usually an instance of Range will be read from the {@link Editor#range} property, + * but it may be useful to instantiate a range directly in cases + * when programmatically modifying a Post. + * @constructor + * @param {Position} head + * @param {Position} [tail=head] + * @param {Direction} [direction=null] + * @return {Range} + */ constructor(head, tail=head, direction=null) { + /** @property {Position} head */ this.head = head; + + /** @property {Position} tail */ this.tail = tail; + + /** @property {Direction} direction */ this.direction = direction; } + /** + * Shorthand to create a new range from a section(s) and offset(s). + * When given only a head section and offset, creates a collapsed range. + * @param {Section} headSection + * @param {number} headOffset + * @param {Section} [tailSection=headSection] + * @param {number} [tailOffset=headOffset] + * @param {Direction} [direction=null] + * @return {Range} + */ static create(headSection, headOffset, tailSection=headSection, tailOffset=headOffset, direction=null) { return new Range( new Position(headSection, headOffset), @@ -32,6 +58,7 @@ export default class Range { * wholly contained. It's possible to call `trimTo` with a selection that is * outside of the range, though, which would invalidate that assumption. * There's no efficient way to determine if a section is within a range, yet. + * @private */ trimTo(section) { const length = section.length; @@ -45,39 +72,39 @@ export default class Range { } /** - * Expands the range in the given direction - * @param {Direction} newDirection - * @return {Range} Always returns an expanded, non-collapsed range + * Expands the range 1 unit in the given direction + * @param {Direction} direction + * @return {Range} If the range is expandable in the given direction, always returns a + * non-collapsed range. * @public */ - extend(newDirection) { - let { head, tail, direction } = this; - switch (direction) { + extend(direction) { + let { head, tail, direction: currentDirection } = this; + switch (currentDirection) { case DIRECTION.FORWARD: - return new Range(head, tail.move(newDirection), direction); + return new Range(head, tail.move(direction), currentDirection); case DIRECTION.BACKWARD: - return new Range(head.move(newDirection), tail, direction); + return new Range(head.move(direction), tail, currentDirection); default: - return new Range(head, tail, newDirection).extend(newDirection); + return new Range(head, tail, direction).extend(direction); } } /** - * Moves this range in {newDirection}. - * If the range is collapsed, returns a collapsed range shifted 1 unit in - * {newDirection}, otherwise collapses this range to the position at the - * {newDirection} end of the range. - * @param {Direction} newDirection + * Moves this range 1 unit in the given direction. + * If the range is collapsed, returns a collapsed range shifted by 1 unit, + * otherwise collapses this range to the position at the `direction` end of the range. + * @param {Direction} direction * @return {Range} Always returns a collapsed range * @public */ - move(newDirection) { + move(direction) { let { focusedPosition, isCollapsed } = this; if (isCollapsed) { - return new Range(focusedPosition.move(newDirection)); + return new Range(focusedPosition.move(direction)); } else { - return this._collapse(newDirection); + return this._collapse(direction); } } diff --git a/src/js/utils/dom-utils.js b/src/js/utils/dom-utils.js index a83ff8f04..9c45f611a 100644 --- a/src/js/utils/dom-utils.js +++ b/src/js/utils/dom-utils.js @@ -52,6 +52,7 @@ function clearChildNodes(element) { * (e.g., inclusive containment) the parent node * see https://github.com/webmodules/node-contains/blob/master/index.js * Mimics the behavior of `Node.contains`, which is broken in IE 10 + * @private */ function containsNode(parentNode, childNode) { if (parentNode === childNode) { @@ -66,6 +67,7 @@ function containsNode(parentNode, childNode) { * an object with key-value pairs * @param {DOMNode} element * @return {Object} key-value pairs + * @private */ function getAttributes(element) { const result = {}; diff --git a/src/js/utils/key.js b/src/js/utils/key.js index 3176cb482..ff010f939 100644 --- a/src/js/utils/key.js +++ b/src/js/utils/key.js @@ -1,4 +1,10 @@ import Keycodes from './keycodes'; +/** + * @typedef Direction + * @enum {number} + * @property {number} FORWARD + * @property {number} BACKWARD + */ export const DIRECTION = { FORWARD: 1, BACKWARD: -1 diff --git a/src/js/utils/logger.js b/src/js/utils/logger.js index 8f4e593e4..68c419463 100644 --- a/src/js/utils/logger.js +++ b/src/js/utils/logger.js @@ -6,9 +6,10 @@ let options = { /** * A Logger to add debugging-style logging - * that can be turned on/off by type of log + * that can be turned on/off by type of log. * * Usage: + * ``` * Logger.enable(); * Logger.enableTypes(['my-events']); * let log = Logger.for('my-events'); @@ -17,6 +18,8 @@ let options = { * log = Logger.for('other-events'); * log('another message'); // => wil not be logged, because 'other-events' * // is not enabled + * ``` + * @private */ const Logger = class Logger { constructor(type) { diff --git a/src/js/utils/merge.js b/src/js/utils/merge.js index 987674c4f..193091a73 100644 --- a/src/js/utils/merge.js +++ b/src/js/utils/merge.js @@ -12,6 +12,7 @@ function mergeWithOptions(original, updates, options) { /** * Merges properties of one object into another + * @private */ function merge(original, updates) { return mergeWithOptions(original, updates); diff --git a/src/js/utils/parse-utils.js b/src/js/utils/parse-utils.js index 940b826d6..50f54dc88 100644 --- a/src/js/utils/parse-utils.js +++ b/src/js/utils/parse-utils.js @@ -13,6 +13,7 @@ const MOBILEDOC_REGEX = new RegExp(/data\-mobiledoc='(.*?)'>/); /** * @return {Post} + * @private */ function parsePostFromHTML(html, builder, plugins) { let post; @@ -30,6 +31,7 @@ function parsePostFromHTML(html, builder, plugins) { /** * @return {Post} + * @private */ function parsePostFromText(text, builder, plugins) { let parser = new TextParser(builder, {plugins}); @@ -39,6 +41,7 @@ function parsePostFromText(text, builder, plugins) { /** * @return {{html: String, text: String}} + * @private */ export function getContentFromPasteEvent(event, window) { let html = '', text = ''; @@ -60,6 +63,7 @@ export function getContentFromPasteEvent(event, window) { /** * @return {{html: String, text: String}} + * @private */ function getContentFromDropEvent(event) { let html = '', text = ''; @@ -81,6 +85,7 @@ function getContentFromDropEvent(event) { * @param {CopyEvent|CutEvent} * @param {Editor} * @param {Window} + * @private */ export function setClipboardData(event, {mobiledoc, html, text}, window) { if (mobiledoc && html) { @@ -105,6 +110,7 @@ export function setClipboardData(event, {mobiledoc, html, text}, window) { * @param {PasteEvent} * @param {{builder: Builder, _parserPlugins: Array}} options * @return {Post} + * @private */ export function parsePostFromPaste(pasteEvent, {builder, _parserPlugins: plugins}, {targetFormat}={targetFormat:'html'}) { let { html, text } = getContentFromPasteEvent(pasteEvent, window); @@ -120,6 +126,7 @@ export function parsePostFromPaste(pasteEvent, {builder, _parserPlugins: plugins * @param {DropEvent} * @param {{builder: Builder, _parserPlugins: Array}} options * @return {Post} + * @private */ export function parsePostFromDrop(dropEvent, {builder, _parserPlugins: plugins}) { let { html, text } = getContentFromDropEvent(dropEvent);