diff --git a/CHANGES.md b/CHANGES.md index 249cbb33..d21b755a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # SVG-Edit CHANGES +## ? + +- Enhancement: Return a Promise for Editor's `setCustomHandlers`, + `loadFromString`, `loadFromDataURI` so known when ready and set +- Docs (Refactoring): Formally specify `Promise` resolve type; + add `typedef` for dialog result object; add an + `ArbitraryCallbackResult` type + ## 4.3.0 - Fix: Droplets for gradient pickers can now be double-clicked in diff --git a/editor/dbox.js b/editor/dbox.js index f02196f3..113353ab 100644 --- a/editor/dbox.js +++ b/editor/dbox.js @@ -23,13 +23,19 @@ export default function jQueryPluginDBox ($, strings = {ok: 'Ok', cancel: 'Cance btnHolder = $('#dialog_buttons'), dialogContent = $('#dialog_content'); + /** + * @typedef {PlainObject} module:jQueryPluginDBox.PromiseResultObject + * @property {string|true} response + * @property {boolean} checked + */ + /** * Resolves to `false` (if cancelled), for prompts and selects * without checkboxes, it resolves to the value of the form control. For other * types without checkboxes, it resolves to `true`. For checkboxes, it resolves * to an object with the `response` key containing the same value as the previous * mentioned (string or `true`) and a `checked` (boolean) property. - * @typedef {Promise} module:jQueryPluginDBox.PromiseResult + * @typedef {Promise} module:jQueryPluginDBox.PromiseResult */ /** * @typedef {PlainObject} module:jQueryPluginDBox.SelectOption diff --git a/editor/extensions/ext-markers.js b/editor/extensions/ext-markers.js index 9ab5403d..dea646af 100644 --- a/editor/extensions/ext-markers.js +++ b/editor/extensions/ext-markers.js @@ -388,7 +388,7 @@ export default { /** * @param {"start"|"mid"|"end"} pos - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ async function showTextPrompt (pos) { let def = $('#' + pos + '_marker').val(); @@ -426,7 +426,7 @@ export default { // callback function for a toolbar button click /** * @param {Event} ev - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ async function setArrowFromButton (ev) { const parts = this.id.split('_'); diff --git a/editor/extensions/ext-placemark.js b/editor/extensions/ext-placemark.js index e904f898..100cbd3f 100644 --- a/editor/extensions/ext-placemark.js +++ b/editor/extensions/ext-placemark.js @@ -240,7 +240,7 @@ export default { } /** * @param {Event} ev - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ function setArrowFromButton (ev) { const parts = this.id.split('_'); diff --git a/editor/external/dynamic-import-polyfill/importModule.js b/editor/external/dynamic-import-polyfill/importModule.js index 13090762..328263fc 100644 --- a/editor/external/dynamic-import-polyfill/importModule.js +++ b/editor/external/dynamic-import-polyfill/importModule.js @@ -40,7 +40,7 @@ function addScriptAtts (script, atts) { * @function module:importModule.importSetGlobalDefault * @param {string|string[]} url * @param {module:importModule.ImportConfig} config -* @returns {Promise} The value to which it resolves depends on the export of the targeted module. +* @returns {Promise<*>} The value to which it resolves depends on the export of the targeted module. */ export function importSetGlobalDefault (url, config) { return importSetGlobal(url, {...config, returnDefault: true}); @@ -49,7 +49,7 @@ export function importSetGlobalDefault (url, config) { * @function module:importModule.importSetGlobal * @param {string|string[]} url * @param {module:importModule.ImportConfig} config -* @returns {Promise} The promise resolves to either an `ArbitraryModule` or +* @returns {Promise} The promise resolves to either an `ArbitraryModule` or * any other value depends on the export of the targeted module. */ export async function importSetGlobal (url, {global: glob, returnDefault}) { @@ -68,7 +68,7 @@ export async function importSetGlobal (url, {global: glob, returnDefault}) { * @author Brett Zamir (other items are from `dynamic-import-polyfill`) * @param {string|string[]} url * @param {PlainObject} [atts={}] - * @returns {Promise} Resolves to `undefined` or rejects with an `Error` upon a + * @returns {Promise} Resolves to `undefined` or rejects with an `Error` upon a * script loading error */ export function importScript (url, atts = {}) { @@ -112,14 +112,14 @@ export function importScript (url, atts = {}) { } /** - * - * @param {string|string[]} url - * @param {PlainObject} [atts={}] - * @param {PlainObject} opts - * @param {boolean} [opts.returnDefault=false} = {}] - * @returns {Promise} Resolves to value of loading module or rejects with - * `Error` upon a script loading error. - */ +* +* @param {string|string[]} url +* @param {PlainObject} [atts={}] +* @param {PlainObject} opts +* @param {boolean} [opts.returnDefault=false} = {}] +* @returns {Promise<*>} Resolves to value of loading module or rejects with +* `Error` upon a script loading error. +*/ export function importModule (url, atts = {}, {returnDefault = false} = {}) { if (Array.isArray(url)) { return Promise.all(url.map((u) => { diff --git a/editor/locale/locale.js b/editor/locale/locale.js index ffeece9f..628fea69 100644 --- a/editor/locale/locale.js +++ b/editor/locale/locale.js @@ -127,7 +127,7 @@ export const init = (editor) => { * @function module:locale.readLang * @param {module:locale.LocaleStrings} langData See {@tutorial LocaleDocs} * @fires module:svgcanvas.SvgCanvas#event:ext-addLangData -* @returns {Promise} Resolves to [`LangAndData`]{@link module:locale.LangAndData} +* @returns {Promise} Resolves to [`LangAndData`]{@link module:locale.LangAndData} */ export const readLang = async function (langData) { const more = await editor_.addLangData(langParam); @@ -366,7 +366,7 @@ export const readLang = async function (langData) { * @fires module:svgcanvas.SvgCanvas#event:ext-addLangData * @fires module:svgcanvas.SvgCanvas#event:ext-langReady * @fires module:svgcanvas.SvgCanvas#event:ext-langChanged - * @returns {Promise} Resolves to result of {@link module:locale.readLang} + * @returns {Promise} Resolves to result of {@link module:locale.readLang} */ export const putLocale = async function (givenParam, goodLangs, conf) { if (givenParam) { diff --git a/editor/svg-editor.js b/editor/svg-editor.js index 6ff3334a..935133da 100644 --- a/editor/svg-editor.js +++ b/editor/svg-editor.js @@ -318,7 +318,7 @@ let svgCanvas, urldata, * @param {PlainObject} [opts={}] * @param {boolean} [opts.noAlert] * @throws {Error} Upon failure to load SVG - * @returns {Promise} Resolves to undefined upon success (or if `noAlert` is + * @returns {Promise} Resolves to undefined upon success (or if `noAlert` is * falsey, though only until after the `alert` is closed); rejects if SVG * loading fails and `noAlert` is truthy. */ @@ -348,13 +348,13 @@ function getImportLocale ({defaultLang, defaultName}) { * @param {PlainObject} localeInfo * @param {string} [localeInfo.name] Defaults to `defaultName` of {@link module:SVGEditor~getImportLocale} * @param {string} [localeInfo.lang=defaultLang] Defaults to `defaultLang` of {@link module:SVGEditor~getImportLocale} - * @returns {Promise} Resolves to {@link module:locale.LocaleStrings} + * @returns {Promise} Resolves to {@link module:locale.LocaleStrings} */ return async function importLocaleDefaulting ({name = defaultName, lang = defaultLang} = {}) { /** * * @param {string} language - * @returns {Promise} Resolves to {@link module:locale.LocaleStrings} + * @returns {Promise} Resolves to {@link module:locale.LocaleStrings} */ function importLocale (language) { const url = `${curConfig.extPath}ext-locale/${name}/${language}.js`; @@ -607,10 +607,10 @@ editor.setConfig = function (opts, cfgCfg) { * Allows one to override default SVGEdit `open`, `save`, and * `export` editor behaviors. * @param {module:SVGEditor.CustomHandler} opts Extension mechanisms may call `setCustomHandlers` with three functions: `opts.open`, `opts.save`, and `opts.exportImage` -* @returns {undefined} +* @returns {Promise} */ editor.setCustomHandlers = function (opts) { - editor.ready(function () { + return editor.ready(function () { if (opts.open) { $('#tool_open > input[type="file"]').remove(); $('#tool_open').show(); @@ -798,7 +798,7 @@ editor.init = function () { * @fires module:svgcanvas.SvgCanvas#event:ext-langReady * @fires module:svgcanvas.SvgCanvas#event:ext-langChanged * @fires module:svgcanvas.SvgCanvas#event:extensions_added - * @returns {Promise} Resolves to result of {@link module:locale.readLang} + * @returns {Promise} Resolves to result of {@link module:locale.readLang} */ const extAndLocaleFunc = async function () { // const lang = ('lang' in curPrefs) ? curPrefs.lang : null; @@ -1884,7 +1884,7 @@ editor.init = function () { /** * @param {PlainObject} [opts={}] * @param {boolean} [opts.cancelDeletes=false}] - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ async function promptImgURL ({cancelDeletes = false} = {}) { let curhref = svgCanvas.getHref(selectedElement); @@ -3041,7 +3041,7 @@ editor.init = function () { * @param {external:Window} win * @param {module:svgcanvas.SvgCanvas#event:extension_added} ext * @listens module:svgcanvas.SvgCanvas#event:extension_added - * @returns {Promise|undefined} Resolves to `undefined` + * @returns {Promise|undefined} Resolves to `undefined` */ const extAdded = async function (win, ext) { if (!ext) { @@ -4324,7 +4324,7 @@ editor.init = function () { /** * - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ const makeHyperlink = async function () { if (!Utils.isNullish(selectedElement) || multiselected) { @@ -4432,7 +4432,7 @@ editor.init = function () { /** * @fires module:svgcanvas.SvgCanvas#event:ext-onNewDocument - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ const clickClear = async function () { const [x, y] = curConfig.dimensions; @@ -4487,7 +4487,7 @@ editor.init = function () { let loadingURL; /** * - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ const clickExport = async function () { const imgType = await $.select('Select an image type for export: ', [ @@ -4724,7 +4724,7 @@ editor.init = function () { /** * - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ const saveSourceEditor = async function () { if (!editingsource) { return; } @@ -4853,7 +4853,7 @@ editor.init = function () { /** * - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ const cancelOverlays = async function () { $('#dialog_box').hide(); @@ -6045,7 +6045,7 @@ editor.init = function () { }; /** - * @returns {Promise} Resolves to boolean indicating `true` if there were no changes + * @returns {Promise} Resolves to boolean indicating `true` if there were no changes * and `false` after the user confirms. */ editor.openPrep = function () { @@ -6213,7 +6213,7 @@ editor.init = function () { * @param {module:locale.LocaleStrings} allStrings See {@tutorial LocaleDocs} * @fires module:svgcanvas.SvgCanvas#event:ext-langReady * @fires module:svgcanvas.SvgCanvas#event:ext-langChanged - * @returns {Promise} A Promise which resolves to `undefined` + * @returns {Promise} A Promise which resolves to `undefined` */ const setLang = editor.setLang = async function (lang, allStrings) { editor.langChanged = true; @@ -6336,14 +6336,14 @@ editor.init = function () { /** * @callback module:SVGEditor.ReadyCallback -* @returns {Promise|undefined} +* @returns {Promise|undefined} */ /** * Queues a callback to be invoked when the editor is ready (or * to be invoked immediately if it is already ready--i.e., * if `runCallbacks` has been run). * @param {module:SVGEditor.ReadyCallback} cb Callback to be queued to invoke -* @returns {Promise} Resolves when all callbacks, including the supplied have resolved +* @returns {Promise} Resolves when all callbacks, including the supplied have resolved */ editor.ready = function (cb) { // eslint-disable-line promise/prefer-await-to-callbacks return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new @@ -6357,7 +6357,7 @@ editor.ready = function (cb) { // eslint-disable-line promise/prefer-await-to-ca /** * Invokes the callbacks previous set by `svgEditor.ready` -* @returns {Promise} Resolves to `undefined` if all callbacks succeeded and rejects otherwise +* @returns {Promise} Resolves to `undefined` if all callbacks succeeded and rejects otherwise */ editor.runCallbacks = async function () { try { @@ -6380,10 +6380,10 @@ editor.runCallbacks = async function () { * @param {string} str The SVG string to load * @param {PlainObject} [opts={}] * @param {boolean} [opts.noAlert=false] Option to avoid alert to user and instead get rejected promise -* @returns {Promise} +* @returns {Promise} */ editor.loadFromString = function (str, {noAlert} = {}) { - editor.ready(async function () { + return editor.ready(async function () { try { await loadSvgString(str, {noAlert}); } catch (err) { @@ -6416,7 +6416,7 @@ editor.disableUI = function (featList) { * @param {PlainObject} [opts={}] May contain properties: `cache`, `callback` * @param {boolean} [opts.cache] * @param {boolean} [opts.noAlert] -* @returns {Promise} Resolves to `undefined` or rejects upon bad loading of +* @returns {Promise} Resolves to `undefined` or rejects upon bad loading of * the SVG (or upon failure to parse the loaded string) when `noAlert` is * enabled */ @@ -6457,10 +6457,10 @@ editor.loadFromURL = function (url, {cache, noAlert} = {}) { * @param {string} str The Data URI to base64-decode (if relevant) and load * @param {PlainObject} [opts={}] * @param {boolean} [opts.noAlert] -* @returns {Promise} Resolves to `undefined` and rejects if loading SVG string fails and `noAlert` is enabled +* @returns {Promise} Resolves to `undefined` and rejects if loading SVG string fails and `noAlert` is enabled */ editor.loadFromDataURI = function (str, {noAlert} = {}) { - editor.ready(function () { + return editor.ready(function () { let base64 = false; let pre = str.match(/^data:image\/svg\+xml;base64,/); if (pre) { @@ -6481,7 +6481,7 @@ editor.loadFromDataURI = function (str, {noAlert} = {}) { * @param {module:svgcanvas.ExtensionInitCallback} init Config to be invoked on this module * @param {module:svgcanvas.ExtensionInitArgs} initArgs * @throws {Error} If called too early - * @returns {Promise} Resolves to `undefined` + * @returns {Promise} Resolves to `undefined` */ editor.addExtension = function (name, init, initArgs) { // Note that we don't want this on editor.ready since some extensions diff --git a/editor/svgcanvas.js b/editor/svgcanvas.js index 0b459a9e..c3a92d8c 100644 --- a/editor/svgcanvas.js +++ b/editor/svgcanvas.js @@ -1113,7 +1113,7 @@ const runExtensions = this.runExtensions = function (action, vars, returnArray, /** * @function module:svgcanvas.ExtensionInitResponse#addLangData * @param {module:svgcanvas.SvgCanvas#event:ext-addLangData} arg - * @returns {Promise} Resolves to {@link module:locale.ExtensionLocaleData} + * @returns {Promise} Resolves to {@link module:locale.ExtensionLocaleData} */ /** * @function module:svgcanvas.ExtensionInitResponse#onNewDocument @@ -1136,7 +1136,7 @@ const runExtensions = this.runExtensions = function (action, vars, returnArray, * @callback module:svgcanvas.ExtensionInitCallback * @this module:SVGEditor * @param {module:svgcanvas.ExtensionArgumentObject} arg -* @returns {Promise} Resolves to [ExtensionInitResponse]{@link module:svgcanvas.ExtensionInitResponse} or `undefined` +* @returns {Promise} Resolves to [ExtensionInitResponse]{@link module:svgcanvas.ExtensionInitResponse} or `undefined` */ /** * @typedef {PlainObject} module:svgcanvas.ExtensionInitArgs @@ -1152,7 +1152,7 @@ const runExtensions = this.runExtensions = function (action, vars, returnArray, * @fires module:svgcanvas.SvgCanvas#event:extension_added * @throws {TypeError|Error} `TypeError` if `extInitFunc` is not a function, `Error` * if extension of supplied name already exists -* @returns {Promise} Resolves to `undefined` +* @returns {Promise} Resolves to `undefined` */ this.addExtension = async function (name, extInitFunc, {$: jq, importLocale}) { if (typeof extInitFunc !== 'function') { @@ -1416,7 +1416,7 @@ canvas.call = call; /** * The promise return, if present, resolves to `undefined` * (`extension_added`, `exported`, `saved`) - * @typedef {Promise|undefined} module:svgcanvas.EventHandlerReturn + * @typedef {Promise|void} module:svgcanvas.EventHandlerReturn */ /** @@ -3827,7 +3827,7 @@ this.svgToString = function (elem, indent) { * Converts a given image file to a data URL when possible, then runs a given callback. * @function module:svgcanvas.SvgCanvas#embedImage * @param {string} src - The path/URL of the image -* @returns {Promise} Resolves to Data URL (string|false) +* @returns {Promise} Resolves to a Data URL (string|false) */ this.embedImage = function (src) { // Todo: Remove this Promise in favor of making an async/await `Image.load` utility @@ -3967,7 +3967,7 @@ let canvg; * @param {boolean} [opts.avoidEvent] * @fires module:svgcanvas.SvgCanvas#event:exported * @todo Confirm/fix ICO type -* @returns {Promise} Resolves to {@link module:svgcanvas.ImageExportedResults} +* @returns {Promise} Resolves to {@link module:svgcanvas.ImageExportedResults} */ this.rasterExport = async function (imgType, quality, exportWindowName, opts = {}) { const type = imgType === 'ICO' ? 'BMP' : (imgType || 'PNG'); @@ -4053,7 +4053,7 @@ this.rasterExport = async function (imgType, quality, exportWindowName, opts = { * @param {string} [exportWindowName] Will also be used for the download file name here * @param {external:jsPDF.OutputType} [outputType="dataurlstring"] * @fires module:svgcanvas.SvgCanvas#event:exportedPDF -* @returns {Promise} Resolves to {@link module:svgcanvas.PDFExportedResults} +* @returns {Promise} Resolves to {@link module:svgcanvas.PDFExportedResults} */ this.exportPDF = async function ( exportWindowName, diff --git a/editor/typedefs.js b/editor/typedefs.js index 7422b42e..581654ba 100644 --- a/editor/typedefs.js +++ b/editor/typedefs.js @@ -10,10 +10,16 @@ * @typedef {null|boolean|Float|string|GenericArray|PlainObject} JSON */ +/** +* This should only be used when the return result from a callback +* is not known as to type. +* @typedef {*} ArbitraryCallbackResult +*/ + /** * @callback GenericCallback * @param {...*} args Signature dependent on the function -* @returns {*} Return dependent on the function +* @returns {ArbitraryCallbackResult} Return dependent on the function */ /** diff --git a/jsdoc-check-overly-generic-types.js b/jsdoc-check-overly-generic-types.js index e63367cb..27b4aa8b 100644 --- a/jsdoc-check-overly-generic-types.js +++ b/jsdoc-check-overly-generic-types.js @@ -76,16 +76,24 @@ function reduceFalseMatches (file, res) { ].includes(line); }); break; + case 'editor/external/dynamic-import-polyfill/importModule.js': + res.line = res.line.filter((line) => { + return ![ + '* @returns {Promise<*>} The value to which it resolves depends on the export of the targeted module.', + '* @returns {Promise<*>} Resolves to value of loading module or rejects with' + ].includes(line); + }); + break; case 'editor/typedefs.js': res.line = res.line.filter((line) => { return ![ '* @typedef {number} Float', + '* @typedef {*} ArbitraryCallbackResult', '* @typedef {Object} ArbitraryObject', '* @typedef {Object} ArbitraryModule', '* @typedef {Array} GenericArray', '* @typedef {*} Any', - '* @param {...*} args Signature dependent on the function', - '* @returns {*} Return dependent on the function' + '* @param {...*} args Signature dependent on the function' ].includes(line); }); break; diff --git a/test/ui-tests/accessibility.js b/test/ui-tests/accessibility.js index 4e3d20c1..d243b13e 100644 --- a/test/ui-tests/accessibility.js +++ b/test/ui-tests/accessibility.js @@ -3,12 +3,15 @@ // https://github.com/helen-dikareva/axe-testcafe import axeCheck from 'axe-testcafe'; +/** +* @external AxeResult +*/ /** * @external TestcafeTest */ /** * @param {external.TestcafeTest} t - * @returns {Promise} + * @returns {Promise} */ function axeCheckWithConfig (t) { return axeCheck(