- Breaking change: loadSvgString now returns a Promise rather than accepting a callback

- Breaking change: Treat callbacks to `editor.ready` as Promises, only resolving after all resolve
- Breaking change: Make `editor.runCallbacks` return a `Promise` which resolves upon all callbacks resolving
- Breaking change: Require `npx` (used with `babel-node`) to allow Node files
  for HTML building and JSDoc type checking to be expressed as ESM.
- Breaking change: `addExtension` now throws upon a repeated attempt to add an already-added extension
- Breaking change (storage preference cookies): Namespace the cookie as "svgeditstore" instead of just "store"
- Breaking change (API): Remove `svgCanvas.rasterExport` fourth (callback) argument, collapsing fifth (options) to fourth
- Breaking change (API): Remove `svgCanvas.exportPDF` third (callback) argument
- Breaking change (API): `editor/contextmenu.js` `add` now throws instead of giving a console error only upon detecting a bad menuitem or preexisting context menu
- Breaking change (API): Remove `svgCanvas.embedImage` second (callback) argument
- Breaking change (API): Make `getHelpXML` a class instead of instance method of `RGBColor`
- Breaking change (internal API): Refactor `dbox` (and `alert`/`confirm`/`process`/`prompt`/`select`) to avoid a callback argument in favor of return a Promise
- Fix: Avoid running in extension `langReady` multiple times or serially
- Enhancement (API): Add svgCanvas.runExtension to run just one extension and add `nameFilter` callback to `runExtensions`
- Enhancement (API): Supply `$` (our wrapped jQuery) to extensions so can use its plugins, e.g., dbox with its `alert`
- Enhancement: Use alert dialog in place of `alert` in webappfind
- Enhancement: `editor.ready` now returns a Promise resolving when all callbacks have resolved
- Enhancement: Allow `noAlert` option as part of second argument to `loadSvgString` (and `loadFromURL` and `loadFromDataURI`) to avoid UI alert (and trigger promise rejection)
- Enhancement: Make `dbox` as a separate module for alert, prompt, etc. dialogs
- Refactoring: Internal `PaintBox` as class; other misc. tweaks; no bitwise in canvg
- Linting (ESLint): Further linting changes (for editor); rename `.eslintrc` -> `.eslintrc.json` per recommendation
- Optimization: Recompress images (imageoptim-cli updated)
- npm: Update devDeps
- npm: Bump to 4.0.0
This commit is contained in:
Brett Zamir
2018-11-08 14:48:01 +08:00
parent 7c470e9909
commit 2e5c7557a9
159 changed files with 25502 additions and 15658 deletions

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-arrows.js
*
@@ -13,22 +12,30 @@ export default {
const strings = await S.importLocale();
const svgEditor = this;
const svgCanvas = svgEditor.canvas;
const $ = jQuery;
const // {svgcontent} = S,
addElem = svgCanvas.addSVGElementFromJson,
{nonce} = S,
{nonce, $} = S,
prefix = 'se_arrow_';
let selElems, arrowprefix, randomizeIds = S.randomize_ids;
function setArrowNonce (window, n) {
/**
* @param {Window} win
* @param {!(string|Integer)} n
* @returns {undefined}
*/
function setArrowNonce (win, n) {
randomizeIds = true;
arrowprefix = prefix + n + '_';
pathdata.fw.id = arrowprefix + 'fw';
pathdata.bk.id = arrowprefix + 'bk';
}
function unsetArrowNonce (window) {
/**
* @param {Window} win
* @returns {undefined}
*/
function unsetArrowNonce (win) {
randomizeIds = false;
arrowprefix = prefix;
pathdata.fw.id = arrowprefix + 'fw';
@@ -49,6 +56,12 @@ export default {
bk: {d: 'm10,0l-10,5l10,5l-5,-5l5,-5z', refx: 2, id: arrowprefix + 'bk'}
};
/**
* Gets linked element.
* @param {Element} elem
* @param {string} attr
* @returns {Element}
*/
function getLinked (elem, attr) {
const str = elem.getAttribute(attr);
if (!str) { return null; }
@@ -59,6 +72,10 @@ export default {
return svgCanvas.getElem(m[1]);
}
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
$('#arrow_panel').toggle(on);
if (on) {
@@ -88,6 +105,10 @@ export default {
}
}
/**
*
* @returns {undefined}
*/
function resetMarker () {
const el = selElems[0];
el.removeAttribute('marker-start');
@@ -95,6 +116,12 @@ export default {
el.removeAttribute('marker-end');
}
/**
* @param {"bk"|"fw"} dir
* @param {"both"|"mid"|"end"|"start"} type
* @param {string} id
* @returns {Element}
*/
function addMarker (dir, type, id) {
// TODO: Make marker (or use?) per arrow type, since refX can be different
id = id || arrowprefix + dir;
@@ -136,6 +163,10 @@ export default {
return marker;
}
/**
*
* @returns {undefined}
*/
function setArrow () {
resetMarker();
@@ -163,6 +194,10 @@ export default {
svgCanvas.call('changed', selElems);
}
/**
* @param {Element} elem
* @returns {undefined}
*/
function colorChanged (elem) {
const color = elem.getAttribute('stroke');
const mtypes = ['start', 'mid', 'end'];
@@ -183,7 +218,7 @@ export default {
const attrs = $(this).children().attr(['fill', 'd']);
if (attrs.fill === color && attrs.d === curD) {
// Found another marker with this color and this path
newMarker = this;
newMarker = this; // eslint-disable-line consistent-this
}
});
@@ -202,14 +237,16 @@ export default {
// Check if last marker can be removed
let remove = true;
$(S.svgcontent).find('line, polyline, path, polygon').each(function () {
const elem = this;
const element = this; // eslint-disable-line consistent-this
$.each(mtypes, function (j, mtype) {
if ($(elem).attr('marker-' + mtype) === 'url(#' + marker.id + ')') {
if ($(element).attr('marker-' + mtype) === 'url(#' + marker.id + ')') {
remove = false;
return remove;
}
return undefined;
});
if (!remove) { return false; }
return undefined;
});
// Not found, so can safely remove
@@ -242,9 +279,9 @@ export default {
$('#arrow_list option')[0].id = 'connector_no_arrow';
},
async addLangData ({lang, importLocale}) {
const strings = await importLocale();
const {langList} = await importLocale();
return {
data: strings.langList
data: langList
};
},
selectedChanged (opts) {

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-closepath.js
*
@@ -13,9 +12,8 @@ import '../svgpathseg.js';
// The button toggles whether the path is open or closed
export default {
name: 'closepath',
async init ({importLocale}) {
async init ({importLocale, $}) {
const strings = await importLocale();
const $ = jQuery;
const svgEditor = this;
let selElems;
const updateButton = function (path) {

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-connector.js
*
@@ -11,11 +10,10 @@
export default {
name: 'connector',
async init (S) {
const $ = jQuery;
const svgEditor = this;
const svgCanvas = svgEditor.canvas;
const {getElem} = svgCanvas;
const {svgroot, importLocale} = S,
const {$, svgroot, importLocale} = S,
addElem = svgCanvas.addSVGElementFromJson,
selManager = S.selectorManager,
connSel = '.se_connector',
@@ -34,6 +32,14 @@ export default {
connections = [],
selElems = [];
/**
*
* @param {Float} x
* @param {Float} y
* @param {module:utilities.BBoxObject} bb
* @param {Float} offset
* @returns {module:math.XYObject}
*/
function getBBintersect (x, y, bb, offset) {
if (offset) {
offset -= 0;
@@ -66,6 +72,11 @@ export default {
};
}
/**
* @param {"start"|"end"} side
* @param {Element} line
* @returns {Float}
*/
function getOffset (side, line) {
const giveOffset = line.getAttribute('marker-' + side);
// const giveOffset = $(line).data(side+'_off');
@@ -75,6 +86,10 @@ export default {
return giveOffset ? size : 0;
}
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let connRules = $('#connector_rules');
if (!connRules.length) {
@@ -84,6 +99,14 @@ export default {
$('#connector_panel').toggle(on);
}
/**
* @param {Element} elem
* @param {Integer|"end"} pos
* @param {Float} x
* @param {Float} y
* @param {boolean} [setMid]
* @returns {undefined}
*/
function setPoint (elem, pos, x, y, setMid) {
const pts = elem.points;
const pt = svgroot.createSVGPoint();
@@ -112,6 +135,11 @@ export default {
}
}
/**
* @param {Float} diffX
* @param {Float} diffY
* @returns {undefined}
*/
function updateLine (diffX, diffY) {
// Update line with element
let i = connections.length;
@@ -158,6 +186,10 @@ export default {
// Loop through connectors to see if one is connected to the element
connectors.each(function () {
let addThis;
/**
*
* @returns {undefined}
*/
function add () {
if (elems.includes(this)) {
// Pretend this element is selected
@@ -205,6 +237,10 @@ export default {
});
}
/**
* @param {Element[]} [elems=selElems]
* @returns {undefined}
*/
function updateConnectors (elems) {
// Updates connector lines based on selected elements
// Is not used on mousemove, as it runs getStrokedBBox every time,
@@ -278,7 +314,10 @@ export default {
seNs = svgCanvas.getEditorNS();
}());
// Do on reset
/**
* Do on reset.
* @returns {undefined}
*/
function init () {
// Make sure all connectors have data set
$(svgcontent).find('*').each(function () {
@@ -331,7 +370,7 @@ export default {
buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
}),
/* async */ addLangData ({lang, importLocale}) {
/* async */ addLangData ({lang}) { // , importLocale: importLoc
return {
data: strings.langList
};
@@ -344,7 +383,7 @@ export default {
const {curConfig: {initStroke}} = svgEditor;
if (mode === 'connector') {
if (started) { return; }
if (started) { return undefined; }
const mouseTarget = e.target;
@@ -386,6 +425,7 @@ export default {
if (mode === 'select') {
findConnectors();
}
return undefined;
},
mouseMove (opts) {
const zoom = svgCanvas.getZoom();
@@ -433,7 +473,7 @@ export default {
let mouseTarget = e.target;
if (svgCanvas.getMode() !== 'connector') {
return;
return undefined;
}
const fo = $(mouseTarget).closest('foreignObject');
if (fo.length) { mouseTarget = fo[0]; }
@@ -469,6 +509,7 @@ export default {
const dupe = $(svgcontent).find(connSel).filter(function () {
const conn = this.getAttributeNS(seNs, 'connector');
if (conn === connStr || conn === altStr) { return true; }
return false;
});
if (dupe.length) {
$(curLine).remove();
@@ -596,7 +637,7 @@ export default {
// Check validity - the field would be something like 'svg_21 svg_22', but
// if one end is missing, it would be 'svg_21' and therefore fail this test
if (!/. ./.test(elem.attr['se:connector'])) {
if (!(/. ./).test(elem.attr['se:connector'])) {
remove.push(elem.attr.id);
}
}

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-eyedropper.js
*
@@ -13,8 +12,7 @@ export default {
async init (S) {
const strings = await S.importLocale();
const svgEditor = this;
const $ = jQuery;
const {ChangeElementCommand} = S, // , svgcontent,
const {$, ChangeElementCommand} = S, // , svgcontent,
// svgdoc = S.svgroot.parentNode.ownerDocument,
svgCanvas = svgEditor.canvas,
addToHistory = function (cmd) { svgCanvas.undoMgr.addCommandToHistory(cmd); },
@@ -27,6 +25,11 @@ export default {
strokeLinejoin: 'miter'
};
/**
*
* @param {module:svgcanvas.SvgCanvas#event:ext-selectedChanged|module:svgcanvas.SvgCanvas#event:ext-elementChanged} opts
* @returns {undefined}
*/
function getStyle (opts) {
// if we are in eyedropper mode, we don't want to disable the eye-dropper tool
const mode = svgCanvas.getMode();

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-foreignobject.js
*
@@ -12,8 +11,7 @@ export default {
name: 'foreignobject',
async init (S) {
const svgEditor = this;
const {text2xml, NS, importLocale} = S;
const $ = jQuery;
const {$, text2xml, NS, importLocale} = S;
const svgCanvas = svgEditor.canvas;
const
// {svgcontent} = S,
@@ -27,6 +25,10 @@ export default {
$('#svg_source_textarea').css('height', height);
};
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let fcRules = $('#fc_rules');
if (!fcRules.length) {
@@ -36,6 +38,10 @@ export default {
$('#foreignObject_panel').toggle(on);
}
/**
* @param {boolean} on
* @returns {undefined}
*/
function toggleSourceButtons (on) {
$('#tool_source_save, #tool_source_cancel').toggle(!on);
$('#foreign_save, #foreign_cancel').toggle(on);
@@ -62,13 +68,18 @@ export default {
svgCanvas.call('changed', [elt]);
svgCanvas.clearSelection();
} catch (e) {
console.log(e);
// Todo: Surface error to user
console.log(e); // eslint-disable-line no-console
return false;
}
return true;
}
/**
*
* @returns {undefined}
*/
function showForeignEditor () {
const elt = selElems[0];
if (!elt || editingforeign) { return; }
@@ -83,6 +94,11 @@ export default {
$('#svg_source_textarea').focus();
}
/**
* @param {string} attr
* @param {string|Float} val
* @returns {undefined}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
@@ -167,14 +183,13 @@ export default {
// Create source save/cancel buttons
/* const save = */ $('#tool_source_save').clone()
.hide().attr('id', 'foreign_save').unbind()
.appendTo('#tool_source_back').click(function () {
.appendTo('#tool_source_back').click(async function () {
if (!editingforeign) { return; }
if (!setForeignString($('#svg_source_textarea').val())) {
$.confirm('Errors found. Revert to original?', function (ok) {
if (!ok) { return false; }
endChanges();
});
const ok = await $.confirm('Errors found. Revert to original?');
if (!ok) { return; }
endChanges();
} else {
endChanges();
}
@@ -190,50 +205,51 @@ export default {
},
mouseDown (opts) {
// const e = opts.event;
if (svgCanvas.getMode() === 'foreign') {
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'foreignObject',
attr: {
x: opts.start_x,
y: opts.start_y,
id: svgCanvas.getNextId(),
'font-size': 16, // cur_text.font_size,
width: '48',
height: '20',
style: 'pointer-events:inherit'
}
});
const m = svgdoc.createElementNS(NS.MATH, 'math');
m.setAttributeNS(NS.XMLNS, 'xmlns', NS.MATH);
m.setAttribute('display', 'inline');
const mi = svgdoc.createElementNS(NS.MATH, 'mi');
mi.setAttribute('mathvariant', 'normal');
mi.textContent = '\u03A6';
const mo = svgdoc.createElementNS(NS.MATH, 'mo');
mo.textContent = '\u222A';
const mi2 = svgdoc.createElementNS(NS.MATH, 'mi');
mi2.textContent = '\u2133';
m.append(mi, mo, mi2);
newFO.append(m);
return {
started: true
};
if (svgCanvas.getMode() !== 'foreign') {
return undefined;
}
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'foreignObject',
attr: {
x: opts.start_x,
y: opts.start_y,
id: svgCanvas.getNextId(),
'font-size': 16, // cur_text.font_size,
width: '48',
height: '20',
style: 'pointer-events:inherit'
}
});
const m = svgdoc.createElementNS(NS.MATH, 'math');
m.setAttributeNS(NS.XMLNS, 'xmlns', NS.MATH);
m.setAttribute('display', 'inline');
const mi = svgdoc.createElementNS(NS.MATH, 'mi');
mi.setAttribute('mathvariant', 'normal');
mi.textContent = '\u03A6';
const mo = svgdoc.createElementNS(NS.MATH, 'mo');
mo.textContent = '\u222A';
const mi2 = svgdoc.createElementNS(NS.MATH, 'mi');
mi2.textContent = '\u2133';
m.append(mi, mo, mi2);
newFO.append(m);
return {
started: true
};
},
mouseUp (opts) {
// const e = opts.event;
if (svgCanvas.getMode() === 'foreign' && started) {
const attrs = $(newFO).attr(['width', 'height']);
const keep = (attrs.width !== '0' || attrs.height !== '0');
svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
if (svgCanvas.getMode() !== 'foreign' || !started) {
return undefined;
}
const attrs = $(newFO).attr(['width', 'height']);
const keep = (attrs.width !== '0' || attrs.height !== '0');
svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
},
selectedChanged (opts) {
// Use this to update the current selected elements

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-grid.js
*
@@ -10,10 +9,9 @@
export default {
name: 'grid',
async init ({NS, getTypeMap, importLocale}) {
async init ({$, NS, getTypeMap, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const svgdoc = document.getElementById('svgcanvas').ownerDocument,
{assignAttributes} = svgCanvas,
@@ -72,6 +70,11 @@ export default {
});
$('#canvasGrid').append(gridBox);
/**
*
* @param {Float} zoom
* @returns {undefined}
*/
function updateGrid (zoom) {
// TODO: Try this with <line> elements, then compare performance difference
const unit = units[svgEditor.curConfig.baseUnit]; // 1 = 1px
@@ -124,6 +127,10 @@ export default {
svgCanvas.setHref(gridimg, datauri);
}
/**
*
* @returns {undefined}
*/
function gridUpdate () {
if (showGrid) {
updateGrid(svgCanvas.getZoom());

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-helloworld.js
*
@@ -15,11 +14,10 @@
*/
export default {
name: 'helloworld',
async init ({importLocale}) {
async init ({$, importLocale}) {
// See `/editor/extensions/ext-locale/helloworld/`
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
return {
name: strings.name,
@@ -61,6 +59,7 @@ export default {
// a value of true in order for mouseUp to be triggered
return {started: true};
}
return undefined;
},
// This is triggered from anywhere, but "started" must have been set

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-imagelib.js
*
@@ -9,7 +8,7 @@
*/
export default {
name: 'imagelib',
async init ({decode64, importLocale, dropXMLInternalSubset}) {
async init ({$, decode64, importLocale, dropXMLInternalSubset}) {
const imagelibStrings = await importLocale();
const modularVersion = !('svgEditor' in window) ||
@@ -18,16 +17,17 @@ export default {
const svgEditor = this;
const $ = jQuery;
const {uiStrings, canvas: svgCanvas, curConfig: {extIconsPath}} = svgEditor;
imagelibStrings.imgLibs = imagelibStrings.imgLibs.map(({name, url, description}) => {
// Todo: Adopt some standard formatting library like `fluent.js` instead
url = url
.replace(/\{path\}/g, extIconsPath)
.replace(/\{modularVersion\}/g, modularVersion
? (imagelibStrings.moduleEnding || '-es')
: ''
.replace(
/\{modularVersion\}/g,
modularVersion
? (imagelibStrings.moduleEnding || '-es')
: ''
);
return {name, url, description};
});
@@ -39,10 +39,18 @@ export default {
}
});
/**
*
* @returns {undefined}
*/
function closeBrowser () {
$('#imgbrowse_holder').hide();
}
/**
* @param {string} url
* @returns {undefined}
*/
function importImage (url) {
const newImage = svgCanvas.addSVGElementFromJson({
element: 'image',
@@ -68,7 +76,7 @@ export default {
let preview, submit;
// Receive `postMessage` data
window.addEventListener('message', function ({origin, data: response}) {
window.addEventListener('message', async function ({origin, data: response}) { // eslint-disable-line no-shadow
if (!response || !['string', 'object'].includes(typeof response)) {
// Do nothing
return;
@@ -84,7 +92,8 @@ export default {
return;
}
if (!allowedImageLibOrigins.includes('*') && !allowedImageLibOrigins.includes(origin)) {
console.log(`Origin ${origin} not whitelisted for posting to ${window.origin}`);
// Todo: Surface this error to user?
console.log(`Origin ${origin} not whitelisted for posting to ${window.origin}`); // eslint-disable-line no-console
return;
}
const hasName = 'name' in response;
@@ -140,12 +149,11 @@ export default {
const message = uiStrings.notification.retrieving.replace('%s', name);
if (mode !== 'm') {
$.process_cancel(message, function () {
transferStopped = true;
// Should a message be sent back to the frame?
await $.process_cancel(message);
transferStopped = true;
// Should a message be sent back to the frame?
$('#dialog_box').hide();
});
$('#dialog_box').hide();
} else {
entry = $('<div>').text(message).data('id', curMeta.id);
preview.append(entry);
@@ -183,7 +191,7 @@ export default {
} else {
pending[id].entry.remove();
}
// $.alert('Unexpected data was returned: ' + response, function() {
// await $.alert('Unexpected data was returned: ' + response, function() {
// if (mode !== 'm') {
// closeBrowser();
// } else {
@@ -203,7 +211,7 @@ export default {
}
closeBrowser();
break;
case 'm':
case 'm': {
// Import multiple
multiArr.push([(svgStr ? 'svg' : 'img'), response]);
curMeta = pending[id];
@@ -265,20 +273,24 @@ export default {
}
}
break;
case 'o':
} case 'o': {
// Open
if (!svgStr) { break; }
svgEditor.openPrep(function (ok) {
if (!ok) { return; }
svgCanvas.clear();
svgCanvas.setSvgString(response);
// updateCanvas();
});
closeBrowser();
const ok = await svgEditor.openPrep();
if (!ok) { return; }
svgCanvas.clear();
svgCanvas.setSvgString(response);
// updateCanvas();
break;
}
}
}, true);
/**
* @param {boolean} show
* @returns {undefined}
*/
function toggleMulti (show) {
$('#lib_framewrap, #imglib_opts').css({right: (show ? 200 : 10)});
if (!preview) {
@@ -319,6 +331,10 @@ export default {
submit.toggle(show);
}
/**
*
* @returns {undefined}
*/
function showBrowser () {
let browser = $('#imgbrowse');
if (!browser.length) {

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-markers.js
*
@@ -34,7 +33,7 @@ export default {
async init (S) {
const strings = await S.importLocale();
const svgEditor = this;
const $ = jQuery;
const {$} = S;
const svgCanvas = svgEditor.canvas;
const // {svgcontent} = S,
addElem = svgCanvas.addSVGElementFromJson;
@@ -94,6 +93,12 @@ export default {
return svgCanvas.getElem(m[1]);
}
/**
*
* @param {"start"|"mid"|"end"} pos
* @param {string} id
* @returns {undefined}
*/
function setIcon (pos, id) {
if (id.substr(0, 1) !== '\\') { id = '\\textmarker'; }
const ci = '#' + idPrefix + pos + '_' + id.substr(1);
@@ -102,8 +107,12 @@ export default {
}
let selElems;
// toggles context tool panel off/on
// sets the controls with the selected element's settings
/**
* Toggles context tool panel off/on. Sets the controls with the
* selected element's settings.
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
$('#marker_panel').toggle(on);
@@ -135,15 +144,20 @@ export default {
}
}
/**
* @param {string} id
* @param {""|"\\nomarker"|"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} val
* @returns {undefined}
*/
function addMarker (id, val) {
const txtBoxBg = '#ffffff';
const txtBoxBorder = 'none';
const txtBoxStrokeWidth = 0;
let marker = svgCanvas.getElem(id);
if (marker) { return; }
if (marker) { return undefined; }
if (val === '' || val === '\\nomarker') { return; }
if (val === '' || val === '\\nomarker') { return undefined; }
const el = selElems[0];
const color = el.getAttribute('stroke');
@@ -161,7 +175,7 @@ export default {
seType = val.substr(1);
} else { seType = 'textmarker'; }
if (!markerTypes[seType]) { return; } // an unknown type!
if (!markerTypes[seType]) { return undefined; } // an unknown type!
// create a generic marker
marker = addElem({
@@ -233,6 +247,10 @@ export default {
return marker;
}
/**
* @param {Element} elem
* @returns {SVGPolylineElement}
*/
function convertline (elem) {
// this routine came from the connectors extension
// it is needed because midpoint markers don't work with line elements
@@ -275,6 +293,10 @@ export default {
return pline;
}
/**
*
* @returns {undefined}
*/
function setMarker () {
const poslist = {start_marker: 'start', mid_marker: 'mid', end_marker: 'end'};
const pos = poslist[this.id];
@@ -301,8 +323,12 @@ export default {
setIcon(pos, val);
}
// called when the main system modifies an object
// this routine changes the associated markers to be the same color
/**
* Called when the main system modifies an object. This routine changes
* the associated markers to be the same color.
* @param {Element} elem
* @returns {undefined}
*/
function colorChanged (elem) {
const color = elem.getAttribute('stroke');
@@ -319,8 +345,12 @@ export default {
});
}
// called when the main system creates or modifies an object
// primary purpose is create new markers for cloned objects
/**
* Called when the main system creates or modifies an object.
* Its primary purpose is to create new markers for cloned objects.
* @param {Element} el
* @returns {undefined}
*/
function updateReferences (el) {
$.each(mtypes, function (i, pos) {
const id = markerPrefix + pos + '_' + el.id;
@@ -343,6 +373,11 @@ export default {
}
// simulate a change event a text box that stores the current element's marker type
/**
* @param {"start"|"mid"|"end"} pos
* @param {string} val
* @returns {undefined}
*/
function triggerTextEntry (pos, val) {
$('#' + pos + '_marker').val(val);
$('#' + pos + '_marker').change();
@@ -351,12 +386,17 @@ export default {
// else {txtbox.show();}
}
function showTextPrompt (pos) {
/**
* @param {"start"|"mid"|"end"} pos
* @returns {Promise} Resolves to `undefined`
*/
async function showTextPrompt (pos) {
let def = $('#' + pos + '_marker').val();
if (def.substr(0, 1) === '\\') { def = ''; }
$.prompt('Enter text for ' + pos + ' marker', def, function (txt) {
if (txt) { triggerTextEntry(pos, txt); }
});
const txt = await $.prompt('Enter text for ' + pos + ' marker', def);
if (txt) {
triggerTextEntry(pos, txt);
}
}
/*
@@ -372,18 +412,23 @@ export default {
case 'dimension':
triggerTextEntry('start','\\leftarrow');
triggerTextEntry('end','\\rightarrow');
showTextPrompt('mid');
await showTextPrompt('mid');
break;
case 'label':
triggerTextEntry('mid','\\nomarker');
triggerTextEntry('end','\\rightarrow');
showTextPrompt('start');
await showTextPrompt('start');
break;
}
}
*/
// callback function for a toolbar button click
function setArrowFromButton (obj) {
/**
* @param {Event} ev
* @returns {Promise} Resolves to `undefined`
*/
async function setArrowFromButton (ev) {
const parts = this.id.split('_');
const pos = parts[1];
let val = parts[2];
@@ -392,20 +437,27 @@ export default {
if (val !== 'textmarker') {
triggerTextEntry(pos, '\\' + val);
} else {
showTextPrompt(pos);
await showTextPrompt(pos);
}
}
/**
* @param {"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} id
* @returns {undefined}
*/
function getTitle (id) {
const {langList} = strings;
const item = langList.find((item) => {
return item.id === id;
const item = langList.find((itm) => {
return itm.id === id;
});
return item ? item.title : id;
}
// build the toolbar button array from the marker definitions
function buildButtonList (lang) {
/**
* Build the toolbar button array from the marker definitions.
* @returns {module:SVGEditor.Button[]}
*/
function buildButtonList () {
const buttons = [];
// const i = 0;
/*
@@ -500,7 +552,7 @@ export default {
callback () {
$('#marker_panel').addClass('toolset').hide();
},
async addLangData ({importLocale, lang}) {
/* async */ addLangData ({importLocale, lang}) {
return {data: strings.langList};
},
selectedChanged (opts) {

View File

@@ -1,4 +1,4 @@
/* globals jQuery, MathJax */
/* globals MathJax */
/**
* ext-mathjax.js
*
@@ -12,10 +12,9 @@ import {importScript} from '../external/dynamic-import-polyfill/importModule.js'
export default {
name: 'mathjax',
async init ({importLocale}) {
async init ({$, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
// Configuration of the MathJax extention.
@@ -54,7 +53,8 @@ export default {
locationY,
mathjaxLoaded = false;
// TODO: Implement language support. Move these uiStrings to the locale files and the code to the langReady callback.
// TODO: Implement language support. Move these uiStrings to the locale files and
// the code to the langReady callback. Also i18nize alert and HTML below
$.extend(uiStrings, {
mathjax: {
embed_svg: 'Save as mathematics',
@@ -65,6 +65,10 @@ export default {
}
});
/**
*
* @returns {undefined}
*/
function saveMath () {
const code = $('#mathjax_code_textarea').val();
// displaystyle to force MathJax NOT to use the inline style. Because it is
@@ -122,11 +126,15 @@ export default {
type: 'mode',
icon: svgEditor.curConfig.extIconsPath + 'mathjax.png',
events: {
click () {
async click () {
// Set the mode.
svgCanvas.setMode('mathjax');
// Only load Mathjax when needed, we don't want to strain Svg-Edit any more.
// From this point on it is very probable that it will be needed, so load it.
if (mathjaxLoaded === false) {
$('<div id="mathjax">' +
$(
'<div id="mathjax">' +
'<!-- Here is where MathJax creates the math -->' +
'<div id="mathjax_creator" class="tex2jax_process" style="display:none">' +
'$${}$$' +
@@ -191,21 +199,20 @@ export default {
*/
// We use `extIconsPath` here for now as it does not vary with
// the modular type as does `extPath`
importScript(svgEditor.curConfig.extIconsPath + mathjaxSrcSecure).then(() => {
try {
await importScript(svgEditor.curConfig.extIconsPath + mathjaxSrcSecure);
// When MathJax is loaded get the div where the math will be rendered.
MathJax.Hub.queue.Push(function () {
math = MathJax.Hub.getAllJax('#mathjax_creator')[0];
console.log(math);
console.log(math); // eslint-disable-line no-console
mathjaxLoaded = true;
console.log('MathJax Loaded');
console.log('MathJax Loaded'); // eslint-disable-line no-console
});
}).catch(() => {
console.log('Failed loadeing MathJax.');
} catch (e) {
console.log('Failed loading MathJax.'); // eslint-disable-line no-console
$.alert('Failed loading MathJax. You will not be able to change the mathematics.');
});
}
}
// Set the mode.
svgCanvas.setMode('mathjax');
}
}
}];
@@ -221,6 +228,7 @@ export default {
if (svgCanvas.getMode() === 'mathjax') {
return {started: true};
}
return undefined;
},
mouseUp (opts) {
if (svgCanvas.getMode() === 'mathjax') {
@@ -233,6 +241,7 @@ export default {
$('#mathjax').show();
return {started: false}; // Otherwise the last selected object dissapears.
}
return undefined;
},
callback () {
$('<style>').text(

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-overview_window.js
*
@@ -9,8 +8,7 @@
*/
export default {
name: 'overview_window',
init ({isChrome, isIE}) {
const $ = jQuery;
init ({$, isChrome, isIE}) {
const overviewWindowGlobals = {};
// Disabled in Chrome 48-, see https://github.com/SVG-Edit/svgedit/issues/26 and
// https://code.google.com/p/chromium/issues/detail?id=565120.
@@ -18,7 +16,7 @@ export default {
const verIndex = navigator.userAgent.indexOf('Chrome/') + 7;
const chromeVersion = parseInt(navigator.userAgent.substring(verIndex));
if (chromeVersion < 49) {
return;
return undefined;
}
}

View File

@@ -36,6 +36,7 @@ export default {
svgEditor.setPanning(true);
return {started: true};
}
return undefined;
},
mouseUp () {
if (svgCanvas.getMode() === 'ext-panning') {
@@ -45,6 +46,7 @@ export default {
element: null
};
}
return undefined;
}
};
}

View File

@@ -1,13 +1,15 @@
/* globals jQuery */
// TODO: Might add support for "exportImage" custom
// handler as in "ext-server_opensave.js" (and in savefile.php)
export default {
name: 'php_savefile',
init () {
init ({$}) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
/**
* Get file name out of SVGEdit document title.
* @returns {string}
*/
function getFileNameFromTitle () {
const title = svgCanvas.getDocumentTitle();
return title.trim();

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-polygon.js
*
@@ -10,9 +9,8 @@ export default {
name: 'polygon',
async init (S) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const {importLocale} = S, // {svgcontent}
const {$, importLocale} = S, // {svgcontent}
// addElem = svgCanvas.addSVGElementFromJson,
editingitex = false;
const strings = await importLocale();
@@ -37,6 +35,11 @@ export default {
const height = $('#svg_source_container').height() - 80;
$('#svg_source_textarea').css('height', height);
}; */
/**
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let fcRules = $('#fc_rules');
if (!fcRules.length) {
@@ -53,15 +56,28 @@ export default {
}
*/
/**
* @param {string} attr
* @param {string|Float} val
* @returns {undefined}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
}
/**
* @param {Float} n
* @returns {Float}
*/
function cot (n) {
return 1 / Math.tan(n);
}
/**
* @param {Float} n
* @returns {Float}
*/
function sec (n) {
return 1 / Math.cos(n);
}
@@ -148,6 +164,7 @@ export default {
$('#polygon_panel').hide();
const endChanges = function () {
// Todo: Missing?
};
// TODO: Needs to be done after orig icon loads
@@ -160,12 +177,11 @@ export default {
// Todo: Uncomment the setItexString() function above and handle ajaxEndpoint?
/*
if (!setItexString($('#svg_source_textarea').val())) {
$.confirm('Errors found. Revert to original?', function (ok) {
if (!ok) {
return false;
}
endChanges();
});
const ok = await $.confirm('Errors found. Revert to original?', function (ok) {
if (!ok) {
return false;
}
endChanges();
} else { */
endChanges();
// }
@@ -178,6 +194,9 @@ export default {
}, 3000);
},
mouseDown (opts) {
if (svgCanvas.getMode() !== 'polygon') {
return undefined;
}
// const e = opts.event;
const rgb = svgCanvas.getColor('fill');
// const ccRgbEl = rgb.substring(1, rgb.length);
@@ -185,79 +204,76 @@ export default {
// ccSRgbEl = sRgb.substring(1, rgb.length);
const sWidth = svgCanvas.getStrokeWidth();
if (svgCanvas.getMode() === 'polygon') {
started = true;
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'polygon',
attr: {
cx: opts.start_x,
cy: opts.start_y,
id: svgCanvas.getNextId(),
shape: 'regularPoly',
sides: document.getElementById('polySides').value,
orient: 'x',
edge: 0,
fill: rgb,
strokecolor: sRgb,
strokeWidth: sWidth
}
});
newFO = svgCanvas.addSVGElementFromJson({
element: 'polygon',
attr: {
cx: opts.start_x,
cy: opts.start_y,
id: svgCanvas.getNextId(),
shape: 'regularPoly',
sides: document.getElementById('polySides').value,
orient: 'x',
edge: 0,
fill: rgb,
strokecolor: sRgb,
strokeWidth: sWidth
}
});
return {
started: true
};
}
return {
started: true
};
},
mouseMove (opts) {
if (!started) {
return;
if (!started || svgCanvas.getMode() !== 'polygon') {
return undefined;
}
if (svgCanvas.getMode() === 'polygon') {
// const e = opts.event;
const c = $(newFO).attr(['cx', 'cy', 'sides', 'orient', 'fill', 'strokecolor', 'strokeWidth']);
let x = opts.mouse_x;
let y = opts.mouse_y;
const {cx, cy, fill, strokecolor, strokeWidth, sides} = c, // {orient} = c,
edg = (Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) / 1.5;
newFO.setAttribute('edge', edg);
// const e = opts.event;
const c = $(newFO).attr(['cx', 'cy', 'sides', 'orient', 'fill', 'strokecolor', 'strokeWidth']);
let x = opts.mouse_x;
let y = opts.mouse_y;
const {cx, cy, fill, strokecolor, strokeWidth, sides} = c, // {orient} = c,
edg = (Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) / 1.5;
newFO.setAttribute('edge', edg);
const inradius = (edg / 2) * cot(Math.PI / sides);
const circumradius = inradius * sec(Math.PI / sides);
let points = '';
for (let s = 0; sides >= s; s++) {
const angle = 2.0 * Math.PI * s / sides;
x = (circumradius * Math.cos(angle)) + cx;
y = (circumradius * Math.sin(angle)) + cy;
const inradius = (edg / 2) * cot(Math.PI / sides);
const circumradius = inradius * sec(Math.PI / sides);
let points = '';
for (let s = 0; sides >= s; s++) {
const angle = 2.0 * Math.PI * s / sides;
x = (circumradius * Math.cos(angle)) + cx;
y = (circumradius * Math.sin(angle)) + cy;
points += x + ',' + y + ' ';
}
// const poly = newFO.createElementNS(NS.SVG, 'polygon');
newFO.setAttribute('points', points);
newFO.setAttribute('fill', fill);
newFO.setAttribute('stroke', strokecolor);
newFO.setAttribute('stroke-width', strokeWidth);
// newFO.setAttribute('transform', 'rotate(-90)');
// const shape = newFO.getAttribute('shape');
// newFO.append(poly);
// DrawPoly(cx, cy, sides, edg, orient);
return {
started: true
};
points += x + ',' + y + ' ';
}
// const poly = newFO.createElementNS(NS.SVG, 'polygon');
newFO.setAttribute('points', points);
newFO.setAttribute('fill', fill);
newFO.setAttribute('stroke', strokecolor);
newFO.setAttribute('stroke-width', strokeWidth);
// newFO.setAttribute('transform', 'rotate(-90)');
// const shape = newFO.getAttribute('shape');
// newFO.append(poly);
// DrawPoly(cx, cy, sides, edg, orient);
return {
started: true
};
},
mouseUp (opts) {
if (svgCanvas.getMode() === 'polygon') {
const attrs = $(newFO).attr('edge');
const keep = (attrs.edge !== '0');
// svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
if (svgCanvas.getMode() !== 'polygon') {
return undefined;
}
const attrs = $(newFO).attr('edge');
const keep = (attrs.edge !== '0');
// svgCanvas.addToSelection([newFO], true);
return {
keep,
element: newFO
};
},
selectedChanged (opts) {
// Use this to update the current selected elements

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-server_moinsave.js
*
@@ -12,10 +11,9 @@ import {canvg} from '../canvg/canvg.js';
export default {
name: 'server_moinsave',
async init ({encode64, importLocale}) {
async init ({$, encode64, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const saveSvgAction = '/+modify';
@@ -48,7 +46,7 @@ export default {
.append('<input type="hidden" name="contenttype" value="application/x-svgdraw">')
.appendTo('body')
.submit().remove();
alert(strings.saved);
$.alert(strings.saved);
top.window.location = '/' + name;
}
});

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-server_opensave.js
*
@@ -11,19 +10,35 @@ import {canvg} from '../canvg/canvg.js';
export default {
name: 'server_opensave',
async init ({decode64, encode64, importLocale}) {
async init ({$, decode64, encode64, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
/**
*
* @returns {string}
*/
function getFileNameFromTitle () {
const title = svgCanvas.getDocumentTitle();
// We convert (to underscore) only those disallowed Win7 file name characters
return title.trim().replace(/[/\\:*?"<>|]/g, '_');
}
/**
* Escapes XML predefined entities for quoted attributes.
* @param {string} str
* @returns {string}
*/
function xhtmlEscape (str) {
return str.replace(/&(?!amp;)/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;'); // < is actually disallowed above anyways
}
/**
*
* @param {string} [filename='image']
* @param {string} suffix To add to file name
* @param {string} uri
* @returns {boolean}
*/
function clientDownloadSupport (filename, suffix, uri) {
const support = $('<a>')[0].download === '';
let a;
@@ -35,6 +50,7 @@ export default {
a[0].click();
return true;
}
return false;
}
const
saveSvgAction = svgEditor.curConfig.extPath + 'filesave.php',
@@ -101,7 +117,7 @@ export default {
}
if (note.length) {
alert(note);
await $.alert(note);
}
const filename = getFileNameFromTitle();
@@ -178,36 +194,45 @@ export default {
// It appears necessary to rebuild this input every time a file is
// selected so the same file can be picked and the change event can fire.
/**
*
* @param {external:jQuery} form
* @returns {undefined}
*/
function rebuildInput (form) {
form.empty();
const inp = $('<input type="file" name="svg_file">').appendTo(form);
function submit () {
// This submits the form, which returns the file data using svgEditor.processFile()
/**
* Submit the form, empty its contents for reuse and show
* uploading message.
* @returns {undefined}
*/
async function submit () {
// This submits the form, which returns the file data using `svgEditor.processFile()`
form.submit();
rebuildInput(form);
$.process_cancel(strings.uploading, function () {
cancelled = true;
$('#dialog_box').hide();
});
await $.process_cancel(strings.uploading);
cancelled = true;
$('#dialog_box').hide();
}
if (form[0] === openSvgForm[0]) {
inp.change(function () {
inp.change(async function () {
// This takes care of the "are you sure" dialog box
svgEditor.openPrep(function (ok) {
if (!ok) {
rebuildInput(form);
return;
}
submit();
});
const ok = await svgEditor.openPrep();
if (!ok) {
rebuildInput(form);
return;
}
await submit();
});
} else {
inp.change(function () {
inp.change(async function () {
// This submits the form, which returns the file data using svgEditor.processFile()
submit();
await submit();
});
}
}

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-shapes.js
*
@@ -9,10 +8,9 @@
*/
export default {
name: 'shapes',
async init ({importLocale}) {
async init ({$, importLocale}) {
const strings = await importLocale();
const svgEditor = this;
const $ = jQuery;
const canv = svgEditor.canvas;
const svgroot = canv.getRootElem();
let lastBBox = {};
@@ -63,10 +61,26 @@ export default {
let currentD, curShapeId, curShape, startX, startY;
let curLib = library.basic;
/**
*
* @returns {undefined}
*/
function loadIcons () {
$('#shape_buttons').empty().append(curLib.buttons);
}
/**
* @typedef {PlainObject} module:Extension.Shapes.Shapes
* @property {PlainObject.<string, string>} data
* @property {Integer} [size]
* @property {boolean} [fill]
*/
/**
* @param {string|"basic"} cat Category ID
* @param {module:Extension.Shapes.Shapes} shapes
* @returns {undefined}
*/
function makeButtons (cat, shapes) {
const size = curLib.size || 300;
const fill = curLib.fill || false;
@@ -78,7 +92,8 @@ export default {
'<svg viewBox="' + vb + '">' +
'<path fill="' + (fill ? '#333' : 'none') +
'" stroke="#000" stroke-width="' + stroke + '" /></svg></svg>',
'text/xml');
'text/xml'
);
const width = 24;
const height = 24;
@@ -88,9 +103,7 @@ export default {
const {data} = shapes;
curLib.buttons = [];
for (const id in data) {
const pathD = data[id];
curLib.buttons = Object.entries(data).map(([id, pathD]) => {
const icon = svgElem.clone();
icon.find('path').attr('d', pathD);
@@ -99,10 +112,14 @@ export default {
title: id
});
// Store for later use
curLib.buttons.push(iconBtn[0]);
}
return iconBtn[0];
});
}
/**
* @param {string|"basic"} catId
* @returns {undefined}
*/
function loadLibrary (catId) {
const lib = library[catId];
@@ -141,33 +158,34 @@ export default {
return Object.assign(buttons[i], button);
}),
callback () {
$('<style>').text(
'#shape_buttons {' +
'overflow: auto;' +
'width: 180px;' +
'max-height: 300px;' +
'display: table-cell;' +
'vertical-align: middle;' +
'}' +
'#shape_cats {' +
'min-width: 110px;' +
'display: table-cell;' +
'vertical-align: middle;' +
'height: 300px;' +
'}' +
'#shape_cats > div {' +
'line-height: 1em;' +
'padding: .5em;' +
'border:1px solid #B0B0B0;' +
'background: #E8E8E8;' +
'margin-bottom: -1px;' +
'}' +
'#shape_cats div:hover {' +
'background: #FFFFCC;' +
'}' +
'#shape_cats div.current {' +
'font-weight: bold;' +
'}').appendTo('head');
$('<style>').text(`
#shape_buttons {
overflow: auto;
width: 180px;
max-height: 300px;
display: table-cell;
vertical-align: middle;
}
#shape_cats {
min-width: 110px;
display: table-cell;
vertical-align: middle;
height: 300px;
}
#shape_cats > div {
line-height: 1em;
padding: .5em;
border:1px solid #B0B0B0;
background: #E8E8E8;
margin-bottom: -1px;
}
#shape_cats div:hover {
background: #FFFFCC;
}
#shape_cats div.current {
font-weight: bold;
}
`).appendTo('head');
const btnDiv = $('<div id="shape_buttons">');
$('#tools_shapelib > *').wrapAll(btnDiv);
@@ -229,14 +247,14 @@ export default {
});
// Now add shape categories from locale
const cats = {};
for (const o in categories) {
cats['#shape_cats [data-cat="' + o + '"]'] = categories[o];
}
Object.entries(categories).forEach(([o, categoryName]) => {
cats['#shape_cats [data-cat="' + o + '"]'] = categoryName;
});
this.setStrings('content', cats);
},
mouseDown (opts) {
const mode = canv.getMode();
if (mode !== modeId) { return; }
if (mode !== modeId) { return undefined; }
startX = opts.start_x;
const x = startX;
@@ -259,7 +277,7 @@ export default {
});
// Make sure shape uses absolute values
if (/[a-z]/.test(currentD)) {
if ((/[a-z]/).test(currentD)) {
currentD = curLib.data[curShapeId] = canv.pathActions.convertPath(curShape);
curShape.setAttribute('d', currentD);
canv.pathActions.fixEnd(curShape);
@@ -343,7 +361,7 @@ export default {
},
mouseUp (opts) {
const mode = canv.getMode();
if (mode !== modeId) { return; }
if (mode !== modeId) { return undefined; }
const keepObject = (opts.event.clientX !== startClientPos.x && opts.event.clientY !== startClientPos.y);

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-star.js
*
@@ -10,10 +9,9 @@ export default {
name: 'star',
async init (S) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
const {importLocale} = S; // {svgcontent},
const {$, importLocale} = S; // {svgcontent},
let
selElems,
// editingitex = false,
@@ -25,6 +23,12 @@ export default {
// undoCommand = 'Not image',
// modeChangeG, ccZoom, wEl, hEl, wOffset, hOffset, ccRgbEl, brushW, brushH;
const strings = await importLocale();
/**
*
* @param {boolean} on
* @returns {undefined}
*/
function showPanel (on) {
let fcRules = $('#fc_rules');
if (!fcRules.length) {
@@ -40,6 +44,12 @@ export default {
}
*/
/**
*
* @param {string} attr
* @param {string|Float} val
* @returns {undefined}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
@@ -140,10 +150,11 @@ export default {
started: true
};
}
return undefined;
},
mouseMove (opts) {
if (!started) {
return;
return undefined;
}
if (svgCanvas.getMode() === 'star') {
const c = $(newFO).attr(['cx', 'cy', 'point', 'orient', 'fill', 'strokecolor', 'strokeWidth', 'radialshift']);
@@ -195,6 +206,7 @@ export default {
started: true
};
}
return undefined;
},
mouseUp () {
if (svgCanvas.getMode() === 'star') {
@@ -205,6 +217,7 @@ export default {
element: newFO
};
}
return undefined;
},
selectedChanged (opts) {
// Use this to update the current selected elements

View File

@@ -1,4 +1,3 @@
/* globals jQuery */
/**
* ext-storage.js
*
@@ -23,9 +22,8 @@
*/
export default {
name: 'storage',
init () {
init ({$}) {
const svgEditor = this;
const $ = jQuery;
const svgCanvas = svgEditor.canvas;
// We could empty any already-set data for users when they decline storage,
@@ -49,6 +47,11 @@ export default {
} = svgEditor.curConfig;
const {storage, updateCanvas} = svgEditor;
/**
* Replace `storagePrompt` parameter within URL.
* @param {string} val
* @returns {undefined}
*/
function replaceStoragePrompt (val) {
val = val ? 'storagePrompt=' + val : '';
const loc = top.location; // Allow this to work with the embedded editor as well
@@ -60,6 +63,13 @@ export default {
loc.href += (loc.href.includes('?') ? '&' : '?') + val;
}
}
/**
* Sets SVG content as a string with "svgedit-" and the current
* canvas name as namespace.
* @param {string} val
* @returns {undefined}
*/
function setSVGContentStorage (val) {
if (storage) {
const name = 'svgedit-' + svgEditor.curConfig.canvasName;
@@ -71,25 +81,36 @@ export default {
}
}
/**
* Set the cookie to expire.
* @param {string} cookie
* @returns {undefined}
*/
function expireCookie (cookie) {
document.cookie = encodeURIComponent(cookie) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
}
/**
* Expire the storage cookie.
* @returns {undefined}
*/
function removeStoragePrefCookie () {
expireCookie('store');
expireCookie('svgeditstore');
}
/**
* Empties storage for each of the current preferences.
* @returns {undefined}
*/
function emptyStorage () {
setSVGContentStorage('');
for (let name in svgEditor.curPrefs) {
if (svgEditor.curPrefs.hasOwnProperty(name)) {
name = 'svg-edit-' + name;
if (storage) {
storage.removeItem(name);
}
expireCookie(name);
Object.keys(svgEditor.curPrefs).forEach((name) => {
name = 'svg-edit-' + name;
if (storage) {
storage.removeItem(name);
}
}
expireCookie(name);
});
}
// emptyStorage();
@@ -106,10 +127,10 @@ export default {
function setupBeforeUnloadListener () {
window.addEventListener('beforeunload', function (e) {
// Don't save anything unless the user opted in to storage
if (!document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/)) {
if (!document.cookie.match(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/)) {
return;
}
if (document.cookie.match(/(?:^|;\s*)store=prefsAndContent/)) {
if (document.cookie.match(/(?:^|;\s*)svgeditstore=prefsAndContent/)) {
setSVGContentStorage(svgCanvas.getSvgString());
}
@@ -118,24 +139,21 @@ export default {
const {curPrefs} = svgEditor;
for (let key in curPrefs) {
if (curPrefs.hasOwnProperty(key)) { // It's our own config, so we don't need to iterate up the prototype chain
let val = curPrefs[key];
const store = (val !== undefined);
key = 'svg-edit-' + key;
if (!store) {
continue;
}
if (storage) {
storage.setItem(key, val);
} else if (window.widget) {
window.widget.setPreferenceForKey(val, key);
} else {
val = encodeURIComponent(val);
document.cookie = encodeURIComponent(key) + '=' + val + '; expires=Fri, 31 Dec 9999 23:59:59 GMT';
}
Object.entries(curPrefs).forEach(([key, val]) => {
const store = (val !== undefined);
key = 'svg-edit-' + key;
if (!store) {
return;
}
}
if (storage) {
storage.setItem(key, val);
} else if (window.widget) {
window.widget.setPreferenceForKey(val, key);
} else {
val = encodeURIComponent(val);
document.cookie = encodeURIComponent(key) + '=' + val + '; expires=Fri, 31 Dec 9999 23:59:59 GMT';
}
});
});
}
@@ -175,7 +193,7 @@ export default {
// continual prompts about it)...
storagePrompt !== false &&
// ...and this user hasn't previously indicated a desire for storage
!document.cookie.match(/(?:^|;\s*)store=(?:prefsAndContent|prefsOnly)/)
!document.cookie.match(/(?:^|;\s*)svgeditstore=(?:prefsAndContent|prefsOnly)/)
)
// ...then show the storage prompt.
)) {
@@ -205,60 +223,10 @@ export default {
// Open select-with-checkbox dialog
// From svg-editor.js
$.select(
svgEditor.storagePromptState = 'waiting';
const {response: pref, checked} = await $.select(
message,
options,
function (pref, checked) {
if (pref && pref !== 'noPrefsOrContent') {
// Regardless of whether the user opted
// to remember the choice (and move to a URL which won't
// ask them again), we have to assume the user
// doesn't even want to remember their not wanting
// storage, so we don't set the cookie or continue on with
// setting storage on beforeunload
document.cookie = 'store=' + encodeURIComponent(pref) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT'; // 'prefsAndContent' | 'prefsOnly'
// If the URL was configured to always insist on a prompt, if
// the user does indicate a wish to store their info, we
// don't want ask them again upon page refresh so move
// them instead to a URL which does not always prompt
if (storagePrompt === true && checked) {
replaceStoragePrompt();
return;
}
} else { // The user does not wish storage (or cancelled, which we treat equivalently)
removeStoragePrefCookie();
if (pref && // If the user explicitly expresses wish for no storage
emptyStorageOnDecline
) {
emptyStorage();
}
if (pref && checked) {
// Open a URL which won't set storage and won't prompt user about storage
replaceStoragePrompt('false');
return;
}
}
// Reset width/height of dialog (e.g., for use by Export)
$('#dialog_container')[0].style.width = oldContainerWidth;
$('#dialog_container')[0].style.marginLeft = oldContainerMarginLeft;
$('#dialog_content')[0].style.height = oldContentHeight;
$('#dialog_container')[0].style.height = oldContainerHeight;
// It should be enough to (conditionally) add to storage on
// beforeunload, but if we wished to update immediately,
// we might wish to try setting:
// svgEditor.setConfig({noStorageOnLoad: true});
// and then call:
// svgEditor.loadContentAndPrefs();
// We don't check for noStorageOnLoad here because
// the prompt gives the user the option to store data
setupBeforeUnloadListener();
svgEditor.storagePromptState = 'closed';
updateCanvas(true);
},
null,
null,
{
@@ -267,7 +235,55 @@ export default {
tooltip: rememberTooltip
}
);
svgEditor.storagePromptState = 'waiting';
if (pref && pref !== 'noPrefsOrContent') {
// Regardless of whether the user opted
// to remember the choice (and move to a URL which won't
// ask them again), we have to assume the user
// doesn't even want to remember their not wanting
// storage, so we don't set the cookie or continue on with
// setting storage on beforeunload
document.cookie = 'svgeditstore=' + encodeURIComponent(pref) + '; expires=Fri, 31 Dec 9999 23:59:59 GMT'; // 'prefsAndContent' | 'prefsOnly'
// If the URL was configured to always insist on a prompt, if
// the user does indicate a wish to store their info, we
// don't want ask them again upon page refresh so move
// them instead to a URL which does not always prompt
if (storagePrompt === true && checked) {
replaceStoragePrompt();
return;
}
} else { // The user does not wish storage (or cancelled, which we treat equivalently)
removeStoragePrefCookie();
if (pref && // If the user explicitly expresses wish for no storage
emptyStorageOnDecline
) {
emptyStorage();
}
if (pref && checked) {
// Open a URL which won't set storage and won't prompt user about storage
replaceStoragePrompt('false');
return;
}
}
// Reset width/height of dialog (e.g., for use by Export)
$('#dialog_container')[0].style.width = oldContainerWidth;
$('#dialog_container')[0].style.marginLeft = oldContainerMarginLeft;
$('#dialog_content')[0].style.height = oldContentHeight;
$('#dialog_container')[0].style.height = oldContainerHeight;
// It should be enough to (conditionally) add to storage on
// beforeunload, but if we wished to update immediately,
// we might wish to try setting:
// svgEditor.setConfig({noStorageOnLoad: true});
// and then call:
// svgEditor.loadContentAndPrefs();
// We don't check for noStorageOnLoad here because
// the prompt gives the user the option to store data
setupBeforeUnloadListener();
svgEditor.storagePromptState = 'closed';
updateCanvas(true);
} else if (!noStorageOnLoad || forceStorage) {
setupBeforeUnloadListener();
}

View File

@@ -7,7 +7,7 @@
export default {
name: 'webappfind',
async init ({importLocale}) {
async init ({importLocale, $}) {
const strings = await importLocale();
const svgEditor = this;
const saveMessage = 'save',
@@ -48,7 +48,7 @@ export default {
} */
break;
case 'save-end':
alert(`save complete for pathID ${pathID}!`);
$.alert(`save complete for pathID ${pathID}!`);
break;
default:
throw new Error('Unexpected WebAppFind event type');

View File

@@ -23,7 +23,7 @@ export default {
// to configure
const {allowedOrigins} = svgEditor.curConfig;
if (!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin)) {
console.log(`Origin ${e.origin} not whitelisted for posting to ${window.origin}`);
console.log(`Origin ${e.origin} not whitelisted for posting to ${window.origin}`); // eslint-disable-line no-console
return;
}
const cbid = data.id;
@@ -42,7 +42,7 @@ export default {
e.source.postMessage(JSON.stringify(message), '*');
});
} catch (err) {
console.log('Error with xdomain message listener: ' + err);
console.log('Error with xdomain message listener: ' + err); // eslint-disable-line no-console
}
}
};

View File

@@ -32,7 +32,8 @@ $('a').click(function () {
data = canvas.toDataURL();
} catch (err) {
// This fails in Firefox with `file:///` URLs :(
alert('Data URL conversion failed: ' + err);
// Todo: This could use a generic alert library instead
alert('Data URL conversion failed: ' + err); // eslint-disable-line no-alert
data = '';
}
post({href, data});

View File

@@ -6,6 +6,8 @@ manipulation($, jml);
const baseAPIURL = 'https://openclipart.org/search/json/';
const jsVoid = 'javascript: void(0);'; // eslint-disable-line no-script-url
/**
* Shows results after query submission.
* @param {string} url
@@ -18,7 +20,7 @@ async function processResults (url) {
*/
function queryLink (query) {
return ['a', {
href: 'javascript: void(0);',
href: jsVoid,
dataset: {value: query},
$on: {click (e) {
e.preventDefault();
@@ -34,7 +36,8 @@ async function processResults (url) {
// console.log('json', json);
if (!json || json.msg !== 'success') {
alert('There was a problem downloading the results');
// Todo: This could use a generic alert library instead
alert('There was a problem downloading the results'); // eslint-disable-line no-alert
return;
}
const {payload, info: {
@@ -82,8 +85,8 @@ async function processResults (url) {
['button', {style: 'margin-right: 8px; border: 2px solid black;', dataset: {id, value: svgURL}, $on: {
async click (e) {
e.preventDefault();
const {value: svgURL, id} = this.dataset;
// console.log('this', id, svgURL);
const {value: svgurl} = this.dataset;
// console.log('this', id, svgurl);
const post = (message) => {
// Todo: Make origin customizable as set by opening window
// Todo: If dropping IE9, avoid stringifying
@@ -95,13 +98,13 @@ async function processResults (url) {
// Send metadata (also indicates file is about to be sent)
post({
name: title,
id: svgURL
id: svgurl
});
const result = await fetch(svgURL);
const result = await fetch(svgurl);
const svg = await result.text();
// console.log('url and svg', svgURL, svg);
// console.log('url and svg', svgurl, svg);
post({
href: svgURL,
href: svgurl,
data: svg
});
}
@@ -117,7 +120,7 @@ async function processResults (url) {
['span', [
'(ID: ',
['a', {
href: 'javascript: void(0);',
href: jsVoid,
dataset: {value: id},
$on: {
click (e) {
@@ -172,7 +175,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();
@@ -188,7 +191,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();
@@ -204,7 +207,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();
@@ -220,7 +223,7 @@ async function processResults (url) {
? ''
: ['span', [
['a', {
href: 'javascript: void(0);',
href: jsVoid,
$on: {
click (e) {
e.preventDefault();