- Linting (ESLint): Stricter rules (or switch to warning)

- Breaking internal API change: `updateGripCursor` moved to be class method of Selector rather than instance method
- Breaking internal API change: `subpathIsClosed` moved to be class method of `Path` rather than instance method
- Refactoring: Reuse utilities base64 encoder for SVG icons plugin
- Docs (JSDoc): Fix return of the `mouseUp` (can also be an object) and `mouseDown` (may also be a boolean) of `pathActions`; other JSDoc additions/improvements
This commit is contained in:
Brett Zamir
2018-11-07 14:51:50 +08:00
parent 901c9547fe
commit 7c470e9909
126 changed files with 2081 additions and 1373 deletions

314
.eslintrc
View File

@@ -6,7 +6,7 @@
"parserOptions": { "parserOptions": {
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["compat", "qunit", "testcafe", "jsdoc", "markdown"], "plugins": ["compat", "qunit", "testcafe", "jsdoc", "markdown", "import", "node", "promise"],
"env": { "env": {
"node": false, "node": false,
"browser": true "browser": true
@@ -26,24 +26,296 @@
"allowAugmentsExtendsWithoutParam": true "allowAugmentsExtendsWithoutParam": true
} }
}, },
"overrides": [{ "overrides": [
"files": ["**/*.md"], {
"rules": { "files": ["editor/locale/lang.*.js"],
"no-undef": ["off"], "rules": {
"no-unused-vars": ["warn"], "import/no-anonymous-default-export": ["off"]
"padded-blocks": ["off"] }
},
{
"files": ["editor/extensions/ext-locale/**"],
"rules": {
"import/no-anonymous-default-export": ["off"]
}
},
{
"files": ["editor/extensions/**/ext-*.js"],
"rules": {
"consistent-this": ["error", "svgEditor"],
"import/no-anonymous-default-export": ["off"]
}
},
{
"files": [
"editor/svgpathseg.js", "editor/touch.js", "editor/typedefs.js",
"editor/redirect-on-no-module-support.js",
"test/all_tests.js", "screencasts/svgopen2010/script.js",
"opera-widget/handlers.js",
"firefox-extension/handlers.js",
"firefox-extension/content/svg-edit-overlay.js"
],
"rules": {
"import/unambiguous": ["off"]
}
},
{
"files": ["**/*.md"],
"rules": {
"no-undef": ["off"],
"no-unused-vars": ["warn"],
"padded-blocks": ["off"],
"import/unambiguous": ["off"]
}
},
{
"files": ["test/**"],
"rules": {
"no-console": ["off"]
}
},
{
"files": [
"docs/jsdoc-config.js", "build-html.js", "jsdoc-check-overly-generic-types.js",
"rollup.config.js", "rollup-config.config.js"
],
"rules": {
"node/no-extraneous-import": ["error"],
"node/no-extraneous-require": ["error"],
"node/no-missing-import": ["error"],
"node/no-missing-require": ["error"],
"node/no-unpublished-bin": ["error"],
"node/no-unpublished-import": ["error"],
"node/no-unpublished-require": ["error"],
"node/no-unsupported-features/es-builtins": ["error"],
"node/no-unsupported-features/es-syntax": ["error"],
"node/no-unsupported-features/node-builtins": ["error"],
"node/process-exit-as-throw": ["error"],
"node/shebang": ["error"],
"node/exports-style": ["error", "module.exports"],
"node/prefer-global/buffer": ["error", "always"],
"node/prefer-global/console": ["error", "always"],
"node/prefer-global/process": ["error", "always"],
"node/prefer-global/text-decoder": ["error", "always"],
"node/prefer-global/text-encoder": ["error", "always"],
"node/prefer-global/url-search-params": ["error", "always"],
"node/prefer-global/url": ["error", "always"]
}
},
{
"files": ["rollup.config.js", "rollup-config.config.js"],
"rules": {
"node/no-unsupported-features/es-syntax": "off",
"node/no-unpublished-import": "off"
}
},
{
"files": ["jsdoc-check-overly-generic-types.js", "build-html.js"],
"rules": {
"import/unambiguous": "off",
"import/no-commonjs": "off"
}
} }
}], ],
"rules": { "rules": {
"semi": [2, "always"], "array-bracket-newline": ["error", "consistent"],
"array-bracket-spacing": ["error"],
"array-callback-return": ["error"],
"array-element-newline": ["off"],
"arrow-body-style": ["off"],
"arrow-parens": ["error"],
"block-scoped-var": ["error"],
"callback-return": ["error"],
"class-methods-use-this": ["warn"],
"computed-property-spacing": ["error"],
"consistent-return": ["error"],
"consistent-this": ["warn"],
"dot-notation": ["error"],
"for-direction": ["error"],
"func-name-matching": ["error"],
"func-names": ["off"],
"func-style": ["off"],
"function-paren-newline": ["error", "consistent"],
"getter-return": ["error"],
"global-require": ["error"],
"guard-for-in": ["error"],
"id-blacklist": ["off"],
"id-length": ["off"],
"id-match": ["off"],
"implicit-arrow-linebreak": ["error"],
"init-declarations": ["off"],
"jsx-quotes": ["error"],
"line-comment-position": ["off"],
"linebreak-style": ["error"],
"lines-around-comment": ["off"],
"lines-between-class-members": ["off"],
"max-classes-per-file": ["off"],
"max-depth": ["off"],
"max-lines-per-function": ["off"],
"max-lines": ["off"],
"max-nested-callbacks": ["error"],
"max-params": ["off"],
"max-statements-per-line": ["off"],
"max-statements": ["off"],
"multiline-comment-style": ["off"],
"multiline-ternary": ["error", "always-multiline"],
"newline-after-var": ["off"],
"newline-before-return": ["off"],
"newline-per-chained-call": ["off"],
"no-alert": ["warn"],
"no-async-promise-executor": ["error"],
"no-await-in-loop": ["error"],
"no-bitwise": ["error"],
"no-buffer-constructor": ["error"],
"no-case-declarations": ["error"],
"no-confusing-arrow": ["error"],
"no-console": ["warn"],
"no-continue": ["off"],
"no-div-regex": ["error"],
"no-duplicate-imports": ["error"],
"no-else-return": ["error"],
"no-empty-function": ["warn"],
"no-empty": ["error", {"allowEmptyCatch": true}],
"no-eq-null": ["error"],
"no-extra-label": ["error"],
"no-extra-semi": ["error"],
"no-implicit-coercion": ["error"],
"no-implicit-globals": ["error"],
"no-inline-comments": ["off"],
"no-invalid-this": ["off"],
"no-lonely-if": ["error"],
"no-loop-func": ["error"],
"no-misleading-character-class": ["error"],
"no-mixed-requires": ["error", {"grouping": true, "allowCall": true}],
"no-multi-assign": ["off"],
"no-negated-condition": ["off"],
"no-nested-ternary": ["off"],
"no-param-reassign": ["off"],
"no-plusplus": ["off"],
"no-process-env": ["error"],
"no-process-exit": ["error"],
"no-prototype-builtins": ["error"],
"no-restricted-globals": ["error", {
"name": "event",
"message": "Use local event parameter instead (preferably as \"e\" or \"ev\")."
}, {
"name": "fdescribe",
"message": "Do not commit fdescribe. Use describe instead."
}],
"no-restricted-imports": ["off"],
"no-restricted-modules": ["off"],
"no-restricted-properties": ["error", {
"property": "__defineGetter__",
"message": "Please use `Object.defineProperty` instead."
}],
"no-restricted-syntax": ["off"],
"no-script-url": ["error"],
"no-shadow": ["error", {"builtinGlobals": true, "hoist": "functions", "allow": ["parent", "top", "open", "name", "closed", "start"]}],
"no-spaced-func": ["error"],
"no-sync": ["error"],
"no-ternary": ["off"],
"no-undefined": ["off"],
"no-underscore-dangle": ["off"],
"no-unused-labels": ["error"],
"no-useless-concat": ["off"],
"no-var": ["error"],
"no-void": ["error"],
"nonblock-statement-body-position": ["error"],
"object-curly-newline": ["off"],
"object-shorthand": ["error", "always", {"avoidExplicitReturnArrows": true}],
"one-var-declaration-per-line": ["off"],
"operator-assignment": ["error"],
"padding-line-between-statements": ["off"],
"prefer-arrow-callback": ["off"],
"prefer-const": ["error"],
"prefer-destructuring": ["error", {"object": true}],
"prefer-numeric-literals": ["warn"],
"prefer-object-spread": ["error"],
"prefer-rest-params": ["error"],
"prefer-spread": ["error"],
"prefer-template": ["off"],
"quote-props": ["error", "as-needed"],
"radix": ["error", "as-needed"],
"require-atomic-updates": ["error"],
"require-await": ["error"],
"require-jsdoc": ["warn"],
"require-yield": ["error"],
"semi-style": ["error"],
"sort-imports": ["off"],
"sort-keys": ["off"],
"sort-vars": ["off"],
"strict": ["error"],
"switch-colon-spacing": ["error"],
"vars-on-top": ["warn"],
"wrap-regex": ["error"],
"semi": ["error", "always"],
"indent": ["error", 2, {"outerIIFEBody": 0}], "indent": ["error", 2, {"outerIIFEBody": 0}],
"object-property-newline": 0, "object-property-newline": ["off"],
"one-var": 0, "one-var": ["off"],
"no-var": 2,
"prefer-const": 2,
"no-extra-semi": 2,
"quote-props": [2, "as-needed"],
"object-curly-spacing": ["error", "never"], "object-curly-spacing": ["error", "never"],
"promise/catch-or-return": "error",
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"promise/always-return": "error",
"promise/no-native": "off",
"promise/no-nesting": "warn",
"promise/no-promise-in-callback": "warn",
"promise/no-callback-in-promise": "warn",
"promise/avoid-new": "warn",
"promise/no-new-statics": "error",
"promise/no-return-in-finally": "warn",
"promise/valid-params": "warn",
"promise/prefer-await-to-then": "error",
"promise/prefer-await-to-callbacks": "warn",
"import/no-unresolved": "error",
"import/named": "error",
"import/default": "error",
"import/namespace": "error",
"import/no-restricted-paths": "off",
"import/no-absolute-path": "error",
"import/no-dynamic-require": "error",
"import/no-internal-modules": "off",
"import/no-webpack-loader-syntax": "error",
"import/no-self-import": "error",
"import/no-cycle": "off",
"import/no-useless-path-segments": "error",
"import/no-relative-parent-imports": "off",
"import/export": "error",
"import/no-named-as-default": "error",
"import/no-named-as-default-member": "error",
"import/no-deprecated": "error",
"import/no-extraneous-dependencies": "error",
"import/no-mutable-exports": "error",
"import/unambiguous": "warn",
"import/no-commonjs": "error",
"import/no-amd": "error",
"import/no-nodejs-modules": "off",
"import/first": "error",
"import/exports-last": "off",
"import/no-duplicates": "error",
"import/no-namespace": "off",
"import/extensions": ["error", "always", {"ignorePackages": true}],
"import/order": ["error", {"groups": [
"builtin",
"external",
"internal",
["parent", "sibling", "index"]
]}],
"import/newline-after-import": "error",
"import/prefer-default-export": "off",
"import/max-dependencies": "off",
"import/no-unassigned-import": "off",
"import/no-named-default": "error",
"import/no-default-export": "off",
"import/no-named-export": "off",
"import/no-anonymous-default-export": "error",
"import/group-exports": "off",
"import/dynamic-import-chunkname": "off",
"jsdoc/check-param-names": 1, "jsdoc/check-param-names": 1,
"jsdoc/check-tag-names": 1, "jsdoc/check-tag-names": 1,
"jsdoc/check-types": 1, "jsdoc/check-types": 1,
@@ -61,7 +333,7 @@
"jsdoc/no-undefined-types": ["off"], "jsdoc/no-undefined-types": ["off"],
"jsdoc/valid-types": ["error"], "jsdoc/valid-types": ["error"],
"valid-jsdoc": ["off", { "valid-jsdoc": ["error", {
"prefer": { "prefer": {
"arg": "param", "arg": "param",
"argument": "param", "argument": "param",
@@ -81,6 +353,16 @@
"matchDescription": "^([A-Z][\\s\\S]*[.`?!])?$", "matchDescription": "^([A-Z][\\s\\S]*[.`?!])?$",
"requireParamDescription": false, "requireParamDescription": false,
"requireReturnDescription": false "requireReturnDescription": false
}],
"no-warning-comments": ["off"],
"default-case": ["off"],
"complexity": ["off"],
"require-unicode-regexp": ["off"],
"capitalized-comments": ["off"],
"no-magic-numbers": ["off"],
"max-len": ["off", {
"ignoreUrls": true,
"ignoreRegExpLiterals": true
}] }]
} }
} }

View File

@@ -16,7 +16,7 @@ import {NS} from './namespaces.js';
const $ = jQuery; const $ = jQuery;
const supportsSVG_ = (function () { const supportsSVG_ = (function () {
return !!document.createElementNS && !!document.createElementNS(NS.SVG, 'svg').createSVGRect; return Boolean(document.createElementNS && document.createElementNS(NS.SVG, 'svg').createSVGRect);
}()); }());
/** /**
@@ -29,7 +29,7 @@ const {userAgent} = navigator;
const svg = document.createElementNS(NS.SVG, 'svg'); const svg = document.createElementNS(NS.SVG, 'svg');
// Note: Browser sniffing should only be used if no other detection method is possible // Note: Browser sniffing should only be used if no other detection method is possible
const isOpera_ = !!window.opera; const isOpera_ = Boolean(window.opera);
const isWebkit_ = userAgent.includes('AppleWebKit'); const isWebkit_ = userAgent.includes('AppleWebKit');
const isGecko_ = userAgent.includes('Gecko/'); const isGecko_ = userAgent.includes('Gecko/');
const isIE_ = userAgent.includes('MSIE'); const isIE_ = userAgent.includes('MSIE');
@@ -39,11 +39,11 @@ const isMac_ = userAgent.includes('Macintosh');
const isTouch_ = 'ontouchstart' in window; const isTouch_ = 'ontouchstart' in window;
const supportsSelectors_ = (function () { const supportsSelectors_ = (function () {
return !!svg.querySelector; return Boolean(svg.querySelector);
}()); }());
const supportsXpath_ = (function () { const supportsXpath_ = (function () {
return !!document.evaluate; return Boolean(document.evaluate);
}()); }());
// segList functions (for FF1.5 and 2.0) // segList functions (for FF1.5 and 2.0)

View File

@@ -11,6 +11,15 @@
import RGBColor from './rgbcolor.js'; import RGBColor from './rgbcolor.js';
import {canvasRGBA} from '../external/stackblur-canvas/dist/stackblur-es.js'; import {canvasRGBA} from '../external/stackblur-canvas/dist/stackblur-es.js';
/**
* Whether a value is `null` or `undefined`.
* @param {Any} val
* @returns {boolean}
*/
const isNullish = (val) => {
return val === null || val === undefined;
};
let canvasRGBA_ = canvasRGBA; let canvasRGBA_ = canvasRGBA;
/** /**
@@ -63,7 +72,7 @@ export const setStackBlurCanvasRGBA = (cb) => {
*/ */
export const canvg = function (target, s, opts) { export const canvg = function (target, s, opts) {
// no parameters // no parameters
if (target == null && s == null && opts == null) { if (isNullish(target) && isNullish(s) && isNullish(opts)) {
const svgTags = document.querySelectorAll('svg'); const svgTags = document.querySelectorAll('svg');
return Promise.all([...svgTags].map((svgTag) => { return Promise.all([...svgTags].map((svgTag) => {
const c = document.createElement('canvas'); const c = document.createElement('canvas');
@@ -82,7 +91,7 @@ export const canvg = function (target, s, opts) {
} }
// store class on canvas // store class on canvas
if (target.svg != null) target.svg.stop(); if (!isNullish(target.svg)) target.svg.stop();
const svg = build(opts || {}); const svg = build(opts || {});
// on i.e. 8 for flash canvas, we can't assign the property so check for it // on i.e. 8 for flash canvas, we can't assign the property so check for it
if (!(target.childNodes.length === 1 && target.childNodes[0].nodeName === 'OBJECT')) { if (!(target.childNodes.length === 1 && target.childNodes[0].nodeName === 'OBJECT')) {
@@ -113,9 +122,9 @@ function build (opts) {
svg.FRAMERATE = 30; svg.FRAMERATE = 30;
svg.MAX_VIRTUAL_PIXELS = 30000; svg.MAX_VIRTUAL_PIXELS = 30000;
svg.log = function (msg) {}; svg.log = function (msg) { /* */ };
if (svg.opts.log === true && typeof console !== 'undefined') { if (svg.opts.log === true && typeof console !== 'undefined') {
svg.log = function (msg) { console.log(msg); }; svg.log = function (msg) { console.log(msg); }; // eslint-disable-line no-console
} }
// globals // globals
@@ -139,7 +148,7 @@ function build (opts) {
width () { return this.Current().width; }, width () { return this.Current().width; },
height () { return this.Current().height; }, height () { return this.Current().height; },
ComputeSize (d) { ComputeSize (d) {
if (d != null && typeof d === 'number') return d; if (!isNullish(d) && typeof d === 'number') return d;
if (d === 'x') return this.width(); if (d === 'x') return this.width();
if (d === 'y') return this.height(); if (d === 'y') return this.height();
return Math.sqrt( return Math.sqrt(
@@ -190,13 +199,12 @@ function build (opts) {
if (window.DOMParser) { if (window.DOMParser) {
const parser = new DOMParser(); const parser = new DOMParser();
return parser.parseFromString(xml, 'text/xml'); return parser.parseFromString(xml, 'text/xml');
} else {
xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
const xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
xmlDoc.async = 'false';
xmlDoc.loadXML(xml);
return xmlDoc;
} }
xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
const xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM');
xmlDoc.async = 'false';
xmlDoc.loadXML(xml);
return xmlDoc;
}; };
// text extensions // text extensions
@@ -226,7 +234,7 @@ function build (opts) {
} }
hasValue () { hasValue () {
return (this.value != null && this.value !== ''); return (!isNullish(this.value) && this.value !== '');
} }
// return the numerical value of the property // return the numerical value of the property
@@ -234,8 +242,8 @@ function build (opts) {
if (!this.hasValue()) return 0; if (!this.hasValue()) return 0;
let n = parseFloat(this.value); let n = parseFloat(this.value);
if ((this.value + '').match(/%$/)) { if (String(this.value).match(/%$/)) {
n = n / 100.0; n /= 100.0;
} }
return n; return n;
} }
@@ -254,7 +262,7 @@ function build (opts) {
// augment the current color value with the opacity // augment the current color value with the opacity
addOpacity (opacityProp) { addOpacity (opacityProp) {
let newValue = this.value; let newValue = this.value;
if (opacityProp.value != null && opacityProp.value !== '' && typeof this.value === 'string') { // can only add opacity to colors, not patterns if (!isNullish(opacityProp.value) && opacityProp.value !== '' && typeof this.value === 'string') { // can only add opacity to colors, not patterns
const color = new RGBColor(this.value); const color = new RGBColor(this.value);
if (color.ok) { if (color.ok) {
newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')'; newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';
@@ -280,12 +288,12 @@ function build (opts) {
let def = this.getDefinition(); let def = this.getDefinition();
// gradient // gradient
if (def != null && def.createGradient) { if (!isNullish(def) && def.createGradient) {
return def.createGradient(svg.ctx, e, opacityProp); return def.createGradient(svg.ctx, e, opacityProp);
} }
// pattern // pattern
if (def != null && def.createPattern) { if (!isNullish(def) && def.createPattern) {
if (def.getHrefAttribute().hasValue()) { if (def.getHrefAttribute().hasValue()) {
const pt = def.attribute('patternTransform'); const pt = def.attribute('patternTransform');
def = def.getHrefAttribute().getDefinition(); def = def.getHrefAttribute().getDefinition();
@@ -312,14 +320,13 @@ function build (opts) {
} }
getUnits () { getUnits () {
const s = this.value + ''; return String(this.value).replace(/[0-9.-]/g, '');
return s.replace(/[0-9.-]/g, '');
} }
// get the length as pixels // get the length as pixels
toPixels (viewPort, processPercent) { toPixels (viewPort, processPercent) {
if (!this.hasValue()) return 0; if (!this.hasValue()) return 0;
const s = this.value + ''; const s = String(this.value);
if (s.match(/em$/)) return this.numValue() * this.getEM(viewPort); if (s.match(/em$/)) return this.numValue() * this.getEM(viewPort);
if (s.match(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0; if (s.match(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0;
if (s.match(/px$/)) return this.numValue(); if (s.match(/px$/)) return this.numValue();
@@ -338,7 +345,7 @@ function build (opts) {
// get the time as milliseconds // get the time as milliseconds
toMilliseconds () { toMilliseconds () {
if (!this.hasValue()) return 0; if (!this.hasValue()) return 0;
const s = this.value + ''; const s = String(this.value);
if (s.match(/s$/)) return this.numValue() * 1000; if (s.match(/s$/)) return this.numValue() * 1000;
if (s.match(/ms$/)) return this.numValue(); if (s.match(/ms$/)) return this.numValue();
return this.numValue(); return this.numValue();
@@ -348,7 +355,7 @@ function build (opts) {
// get the angle as radians // get the angle as radians
toRadians () { toRadians () {
if (!this.hasValue()) return 0; if (!this.hasValue()) return 0;
const s = this.value + ''; const s = String(this.value);
if (s.match(/deg$/)) return this.numValue() * (Math.PI / 180.0); if (s.match(/deg$/)) return this.numValue() * (Math.PI / 180.0);
if (s.match(/grad$/)) return this.numValue() * (Math.PI / 200.0); if (s.match(/grad$/)) return this.numValue() * (Math.PI / 200.0);
if (s.match(/rad$/)) return this.numValue(); if (s.match(/rad$/)) return this.numValue();
@@ -368,7 +375,7 @@ function build (opts) {
Weights: 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit', Weights: 'normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900|inherit',
CreateFont (fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { CreateFont (fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
const f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font); const f = !isNullish(inherit) ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
return { return {
fontFamily: fontFamily || f.fontFamily, fontFamily: fontFamily || f.fontFamily,
fontSize: fontSize || f.fontSize, fontSize: fontSize || f.fontSize,
@@ -474,7 +481,7 @@ function build (opts) {
height () { return this.y2 - this.y1; } height () { return this.y2 - this.y1; }
addPoint (x, y) { addPoint (x, y) {
if (x != null) { if (!isNullish(x)) {
if (isNaN(this.x1) || isNaN(this.x2)) { if (isNaN(this.x1) || isNaN(this.x2)) {
this.x1 = x; this.x1 = x;
this.x2 = x; this.x2 = x;
@@ -483,7 +490,7 @@ function build (opts) {
if (x > this.x2) this.x2 = x; if (x > this.x2) this.x2 = x;
} }
if (y != null) { if (!isNullish(y)) {
if (isNaN(this.y1) || isNaN(this.y2)) { if (isNaN(this.y1) || isNaN(this.y2)) {
this.y1 = y; this.y1 = y;
this.y2 = y; this.y2 = y;
@@ -710,7 +717,7 @@ function build (opts) {
else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax); else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax);
// translate // translate
ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY); ctx.translate(isNullish(minX) ? 0 : -minX, isNullish(minY) ? 0 : -minY);
}; };
// elements // elements
@@ -724,7 +731,7 @@ function build (opts) {
this.attributes = {}; this.attributes = {};
this.styles = {}; this.styles = {};
this.children = []; this.children = [];
if (node != null && node.nodeType === 1) { // ELEMENT_NODE if (!isNullish(node) && node.nodeType === 1) { // ELEMENT_NODE
// add children // add children
[...node.childNodes].forEach((childNode) => { [...node.childNodes].forEach((childNode) => {
if (childNode.nodeType === 1) { if (childNode.nodeType === 1) {
@@ -749,7 +756,7 @@ function build (opts) {
}); });
// add tag styles // add tag styles
let styles = svg.Styles[node.nodeName]; let styles = svg.Styles[node.nodeName];
if (styles != null) { if (!isNullish(styles)) {
for (const name in styles) { for (const name in styles) {
this.styles[name] = styles[name]; this.styles[name] = styles[name];
} }
@@ -760,13 +767,13 @@ function build (opts) {
const classes = svg.compressSpaces(this.attribute('class').value).split(' '); const classes = svg.compressSpaces(this.attribute('class').value).split(' ');
classes.forEach((clss) => { classes.forEach((clss) => {
styles = svg.Styles['.' + clss]; styles = svg.Styles['.' + clss];
if (styles != null) { if (!isNullish(styles)) {
for (const name in styles) { for (const name in styles) {
this.styles[name] = styles[name]; this.styles[name] = styles[name];
} }
} }
styles = svg.Styles[node.nodeName + '.' + clss]; styles = svg.Styles[node.nodeName + '.' + clss];
if (styles != null) { if (!isNullish(styles)) {
for (const name in styles) { for (const name in styles) {
this.styles[name] = styles[name]; this.styles[name] = styles[name];
} }
@@ -777,7 +784,7 @@ function build (opts) {
// add id styles // add id styles
if (this.attribute('id').hasValue()) { if (this.attribute('id').hasValue()) {
const styles = svg.Styles['#' + this.attribute('id').value]; const styles = svg.Styles['#' + this.attribute('id').value];
if (styles != null) { if (!isNullish(styles)) {
for (const name in styles) { for (const name in styles) {
this.styles[name] = styles[name]; this.styles[name] = styles[name];
} }
@@ -799,7 +806,7 @@ function build (opts) {
// add id // add id
if (this.attribute('id').hasValue()) { if (this.attribute('id').hasValue()) {
if (svg.Definitions[this.attribute('id').value] == null) { if (isNullish(svg.Definitions[this.attribute('id').value])) {
svg.Definitions[this.attribute('id').value] = this; svg.Definitions[this.attribute('id').value] = this;
} }
} }
@@ -809,7 +816,7 @@ function build (opts) {
// get or create attribute // get or create attribute
attribute (name, createIfNotExists) { attribute (name, createIfNotExists) {
let a = this.attributes[name]; let a = this.attributes[name];
if (a != null) return a; if (!isNullish(a)) return a;
if (createIfNotExists === true) { a = new svg.Property(name, ''); this.attributes[name] = a; } if (createIfNotExists === true) { a = new svg.Property(name, ''); this.attributes[name] = a; }
return a || svg.EmptyProperty; return a || svg.EmptyProperty;
@@ -827,19 +834,19 @@ function build (opts) {
// get or create style, crawls up node tree // get or create style, crawls up node tree
style (name, createIfNotExists, skipAncestors) { style (name, createIfNotExists, skipAncestors) {
let s = this.styles[name]; let s = this.styles[name];
if (s != null) return s; if (!isNullish(s)) return s;
const a = this.attribute(name); const a = this.attribute(name);
if (a != null && a.hasValue()) { if (!isNullish(a) && a.hasValue()) {
this.styles[name] = a; // move up to me to cache this.styles[name] = a; // move up to me to cache
return a; return a;
} }
if (skipAncestors !== true) { if (skipAncestors !== true) {
const p = this.parent; const p = this.parent;
if (p != null) { if (!isNullish(p)) {
const ps = p.style(name); const ps = p.style(name);
if (ps != null && ps.hasValue()) { if (!isNullish(ps) && ps.hasValue()) {
return ps; return ps;
} }
} }
@@ -860,10 +867,10 @@ function build (opts) {
ctx.save(); ctx.save();
if (this.attribute('mask').hasValue()) { // mask if (this.attribute('mask').hasValue()) { // mask
const mask = this.attribute('mask').getDefinition(); const mask = this.attribute('mask').getDefinition();
if (mask != null) mask.apply(ctx, this); if (!isNullish(mask)) mask.apply(ctx, this);
} else if (this.style('filter').hasValue()) { // filter } else if (this.style('filter').hasValue()) { // filter
const filter = this.style('filter').getDefinition(); const filter = this.style('filter').getDefinition();
if (filter != null) filter.apply(ctx, this); if (!isNullish(filter)) filter.apply(ctx, this);
} else { } else {
this.setContext(ctx); this.setContext(ctx);
this.renderChildren(ctx); this.renderChildren(ctx);
@@ -903,7 +910,7 @@ function build (opts) {
// fill // fill
if (this.style('fill').isUrlDefinition()) { if (this.style('fill').isUrlDefinition()) {
const fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity')); const fs = this.style('fill').getFillStyleDefinition(this, this.style('fill-opacity'));
if (fs != null) ctx.fillStyle = fs; if (!isNullish(fs)) ctx.fillStyle = fs;
} else if (this.style('fill').hasValue()) { } else if (this.style('fill').hasValue()) {
const fillStyle = this.style('fill'); const fillStyle = this.style('fill');
if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value; if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value;
@@ -918,7 +925,7 @@ function build (opts) {
// stroke // stroke
if (this.style('stroke').isUrlDefinition()) { if (this.style('stroke').isUrlDefinition()) {
const fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity')); const fs = this.style('stroke').getFillStyleDefinition(this, this.style('stroke-opacity'));
if (fs != null) ctx.strokeStyle = fs; if (!isNullish(fs)) ctx.strokeStyle = fs;
} else if (this.style('stroke').hasValue()) { } else if (this.style('stroke').hasValue()) {
const strokeStyle = this.style('stroke'); const strokeStyle = this.style('stroke');
if (strokeStyle.value === 'currentColor') strokeStyle.value = this.style('color').value; if (strokeStyle.value === 'currentColor') strokeStyle.value = this.style('color').value;
@@ -963,7 +970,8 @@ function build (opts) {
this.style('font-variant').value, this.style('font-variant').value,
this.style('font-weight').value, this.style('font-weight').value,
this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '', this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',
this.style('font-family').value).toString(); this.style('font-family').value
).toString();
} }
// transform // transform
@@ -975,7 +983,7 @@ function build (opts) {
// clip // clip
if (this.style('clip-path', false, true).hasValue()) { if (this.style('clip-path', false, true).hasValue()) {
const clip = this.style('clip-path', false, true).getDefinition(); const clip = this.style('clip-path', false, true).getDefinition();
if (clip != null) clip.apply(ctx); if (!isNullish(clip)) clip.apply(ctx);
} }
// opacity // opacity
@@ -987,7 +995,7 @@ function build (opts) {
svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase { svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase {
path (ctx) { path (ctx) {
if (ctx != null) ctx.beginPath(); if (!isNullish(ctx)) ctx.beginPath();
return new svg.BoundingBox(); return new svg.BoundingBox();
} }
@@ -1004,7 +1012,7 @@ function build (opts) {
if (ctx.strokeStyle !== '') ctx.stroke(); if (ctx.strokeStyle !== '') ctx.stroke();
const markers = this.getMarkers(); const markers = this.getMarkers();
if (markers != null) { if (!isNullish(markers)) {
if (this.style('marker-start').isUrlDefinition()) { if (this.style('marker-start').isUrlDefinition()) {
const marker = this.style('marker-start').getDefinition(); const marker = this.style('marker-start').getDefinition();
marker.render(ctx, markers[0][0], markers[0][1]); marker.render(ctx, markers[0][0], markers[0][1]);
@@ -1123,7 +1131,7 @@ function build (opts) {
if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry; if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
rx = Math.min(rx, width / 2.0); rx = Math.min(rx, width / 2.0);
ry = Math.min(ry, height / 2.0); ry = Math.min(ry, height / 2.0);
if (ctx != null) { if (!isNullish(ctx)) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x + rx, y); ctx.moveTo(x + rx, y);
ctx.lineTo(x + width - rx, y); ctx.lineTo(x + width - rx, y);
@@ -1148,7 +1156,7 @@ function build (opts) {
const cy = this.attribute('cy').toPixels('y'); const cy = this.attribute('cy').toPixels('y');
const r = this.attribute('r').toPixels(); const r = this.attribute('r').toPixels();
if (ctx != null) { if (!isNullish(ctx)) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2, true); ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
ctx.closePath(); ctx.closePath();
@@ -1167,7 +1175,7 @@ function build (opts) {
const cx = this.attribute('cx').toPixels('x'); const cx = this.attribute('cx').toPixels('x');
const cy = this.attribute('cy').toPixels('y'); const cy = this.attribute('cy').toPixels('y');
if (ctx != null) { if (!isNullish(ctx)) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(cx, cy - ry); ctx.moveTo(cx, cy - ry);
ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy); ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
@@ -1186,13 +1194,14 @@ function build (opts) {
getPoints () { getPoints () {
return [ return [
new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')), new svg.Point(this.attribute('x1').toPixels('x'), this.attribute('y1').toPixels('y')),
new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))]; new svg.Point(this.attribute('x2').toPixels('x'), this.attribute('y2').toPixels('y'))
];
} }
path (ctx) { path (ctx) {
const points = this.getPoints(); const points = this.getPoints();
if (ctx != null) { if (!isNullish(ctx)) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y); ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y); ctx.lineTo(points[1].x, points[1].y);
@@ -1218,14 +1227,14 @@ function build (opts) {
path (ctx) { path (ctx) {
const {x, y} = this.points[0]; const {x, y} = this.points[0];
const bb = new svg.BoundingBox(x, y); const bb = new svg.BoundingBox(x, y);
if (ctx != null) { if (!isNullish(ctx)) {
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(x, y); ctx.moveTo(x, y);
} }
for (let i = 1; i < this.points.length; i++) { for (let i = 1; i < this.points.length; i++) {
const {x, y} = this.points[i]; const {x, y} = this.points[i];
bb.addPoint(x, y); bb.addPoint(x, y);
if (ctx != null) ctx.lineTo(x, y); if (!isNullish(ctx)) ctx.lineTo(x, y);
} }
return bb; return bb;
} }
@@ -1244,7 +1253,7 @@ function build (opts) {
svg.Element.polygon = class extends svg.Element.polyline { svg.Element.polygon = class extends svg.Element.polyline {
path (ctx) { path (ctx) {
const bb = super.path(ctx); const bb = super.path(ctx);
if (ctx != null) { if (!isNullish(ctx)) {
ctx.lineTo(this.points[0].x, this.points[0].y); ctx.lineTo(this.points[0].x, this.points[0].y);
ctx.closePath(); ctx.closePath();
} }
@@ -1289,7 +1298,7 @@ function build (opts) {
isCommandOrEnd () { isCommandOrEnd () {
if (this.isEnd()) return true; if (this.isEnd()) return true;
return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null; return !isNullish(this.tokens[this.i + 1].match(/^[A-Za-z]$/));
}, },
isRelativeCommand () { isRelativeCommand () {
@@ -1363,10 +1372,10 @@ function build (opts) {
addMarker (p, from, priorTo) { addMarker (p, from, priorTo) {
// if the last angle isn't filled in because we didn't have this point yet ... // if the last angle isn't filled in because we didn't have this point yet ...
if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length - 1] == null) { if (!isNullish(priorTo) && this.angles.length > 0 && isNullish(this.angles[this.angles.length - 1])) {
this.angles[this.angles.length - 1] = this.points[this.points.length - 1].angleTo(priorTo); this.angles[this.angles.length - 1] = this.points[this.points.length - 1].angleTo(priorTo);
} }
this.addMarkerAngle(p, from == null ? null : from.angleTo(p)); this.addMarkerAngle(p, isNullish(from) ? null : from.angleTo(p));
}, },
addMarkerAngle (p, a) { addMarkerAngle (p, a) {
@@ -1377,9 +1386,9 @@ function build (opts) {
getMarkerPoints () { return this.points; }, getMarkerPoints () { return this.points; },
getMarkerAngles () { getMarkerAngles () {
for (let i = 0; i < this.angles.length; i++) { for (let i = 0; i < this.angles.length; i++) {
if (this.angles[i] == null) { if (isNullish(this.angles[i])) {
for (let j = i + 1; j < this.angles.length; j++) { for (let j = i + 1; j < this.angles.length; j++) {
if (this.angles[j] != null) { if (!isNullish(this.angles[j])) {
this.angles[i] = this.angles[j]; this.angles[i] = this.angles[j];
break; break;
} }
@@ -1396,7 +1405,7 @@ function build (opts) {
pp.reset(); pp.reset();
const bb = new svg.BoundingBox(); const bb = new svg.BoundingBox();
if (ctx != null) ctx.beginPath(); if (!isNullish(ctx)) ctx.beginPath();
while (!pp.isEnd()) { while (!pp.isEnd()) {
pp.nextCommand(); pp.nextCommand();
switch (pp.command) { switch (pp.command) {
@@ -1405,13 +1414,13 @@ function build (opts) {
const p = pp.getAsCurrentPoint(); const p = pp.getAsCurrentPoint();
pp.addMarker(p); pp.addMarker(p);
bb.addPoint(p.x, p.y); bb.addPoint(p.x, p.y);
if (ctx != null) ctx.moveTo(p.x, p.y); if (!isNullish(ctx)) ctx.moveTo(p.x, p.y);
pp.start = pp.current; pp.start = pp.current;
while (!pp.isCommandOrEnd()) { while (!pp.isCommandOrEnd()) {
const p = pp.getAsCurrentPoint(); const p = pp.getAsCurrentPoint();
pp.addMarker(p, pp.start); pp.addMarker(p, pp.start);
bb.addPoint(p.x, p.y); bb.addPoint(p.x, p.y);
if (ctx != null) ctx.lineTo(p.x, p.y); if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);
} }
break; break;
case 'L': case 'L':
@@ -1421,7 +1430,7 @@ function build (opts) {
const p = pp.getAsCurrentPoint(); const p = pp.getAsCurrentPoint();
pp.addMarker(p, c); pp.addMarker(p, c);
bb.addPoint(p.x, p.y); bb.addPoint(p.x, p.y);
if (ctx != null) ctx.lineTo(p.x, p.y); if (!isNullish(ctx)) ctx.lineTo(p.x, p.y);
} }
break; break;
case 'H': case 'H':
@@ -1431,7 +1440,7 @@ function build (opts) {
pp.addMarker(newP, pp.current); pp.addMarker(newP, pp.current);
pp.current = newP; pp.current = newP;
bb.addPoint(pp.current.x, pp.current.y); bb.addPoint(pp.current.x, pp.current.y);
if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y); if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);
} }
break; break;
case 'V': case 'V':
@@ -1441,7 +1450,7 @@ function build (opts) {
pp.addMarker(newP, pp.current); pp.addMarker(newP, pp.current);
pp.current = newP; pp.current = newP;
bb.addPoint(pp.current.x, pp.current.y); bb.addPoint(pp.current.x, pp.current.y);
if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y); if (!isNullish(ctx)) ctx.lineTo(pp.current.x, pp.current.y);
} }
break; break;
case 'C': case 'C':
@@ -1453,7 +1462,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint(); const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, p1); pp.addMarker(cp, cntrl, p1);
bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y); bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y); if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
} }
break; break;
case 'S': case 'S':
@@ -1465,7 +1474,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint(); const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, p1); pp.addMarker(cp, cntrl, p1);
bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y); bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y); if (!isNullish(ctx)) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
} }
break; break;
case 'Q': case 'Q':
@@ -1476,7 +1485,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint(); const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, cntrl); pp.addMarker(cp, cntrl, cntrl);
bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y); bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y); if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
} }
break; break;
case 'T': case 'T':
@@ -1488,7 +1497,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint(); const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, cntrl); pp.addMarker(cp, cntrl, cntrl);
bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y); bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y); if (!isNullish(ctx)) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
} }
break; break;
case 'A': case 'A':
@@ -1560,7 +1569,7 @@ function build (opts) {
pp.addMarkerAngle(cp, ah - dir * Math.PI); pp.addMarkerAngle(cp, ah - dir * Math.PI);
bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
if (ctx != null) { if (!isNullish(ctx)) {
const r = rx > ry ? rx : ry; const r = rx > ry ? rx : ry;
const sx = rx > ry ? 1 : rx / ry; const sx = rx > ry ? 1 : rx / ry;
const sy = rx > ry ? ry / rx : 1; const sy = rx > ry ? ry / rx : 1;
@@ -1577,7 +1586,7 @@ function build (opts) {
break; break;
case 'Z': case 'Z':
case 'z': case 'z':
if (ctx != null) ctx.closePath(); if (!isNullish(ctx)) ctx.closePath();
pp.current = pp.start; pp.current = pp.start;
} }
} }
@@ -1604,10 +1613,10 @@ function build (opts) {
// render me using a temporary svg element // render me using a temporary svg element
const tempSvg = new svg.Element.svg(); const tempSvg = new svg.Element.svg();
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value); tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);
tempSvg.attributes['width'] = new svg.Property('width', width + 'px'); tempSvg.attributes.width = new svg.Property('width', width + 'px');
tempSvg.attributes['height'] = new svg.Property('height', height + 'px'); tempSvg.attributes.height = new svg.Property('height', height + 'px');
tempSvg.attributes['transform'] = new svg.Property('transform', this.attribute('patternTransform').value); tempSvg.attributes.transform = new svg.Property('transform', this.attribute('patternTransform').value);
tempSvg.children = this.children; tempSvg.children = this.children;
const c = document.createElement('canvas'); const c = document.createElement('canvas');
@@ -1641,13 +1650,13 @@ function build (opts) {
// render me using a temporary svg element // render me using a temporary svg element
const tempSvg = new svg.Element.svg(); const tempSvg = new svg.Element.svg();
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value); tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);
tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value); tempSvg.attributes.refX = new svg.Property('refX', this.attribute('refX').value);
tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value); tempSvg.attributes.refY = new svg.Property('refY', this.attribute('refY').value);
tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value); tempSvg.attributes.width = new svg.Property('width', this.attribute('markerWidth').value);
tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value); tempSvg.attributes.height = new svg.Property('height', this.attribute('markerHeight').value);
tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black')); tempSvg.attributes.fill = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none')); tempSvg.attributes.stroke = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
tempSvg.children = this.children; tempSvg.children = this.children;
tempSvg.render(ctx); tempSvg.render(ctx);
@@ -1698,7 +1707,7 @@ function build (opts) {
}; };
const g = this.getGradient(ctx, element); const g = this.getGradient(ctx, element);
if (g == null) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color); if (isNullish(g)) return addParentOpacity(stopsContainer.stops[stopsContainer.stops.length - 1].color);
stopsContainer.stops.forEach(({offset, color}) => { stopsContainer.stops.forEach(({offset, color}) => {
g.addColorStop(offset, addParentOpacity(color)); g.addColorStop(offset, addParentOpacity(color));
}); });
@@ -1708,20 +1717,20 @@ function build (opts) {
const rootView = svg.ViewPort.viewPorts[0]; const rootView = svg.ViewPort.viewPorts[0];
const rect = new svg.Element.rect(); const rect = new svg.Element.rect();
rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS / 3.0); rect.attributes.x = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS / 3.0);
rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS / 3.0); rect.attributes.y = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS / 3.0);
rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS); rect.attributes.width = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS); rect.attributes.height = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
const group = new svg.Element.g(); const group = new svg.Element.g();
group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value); group.attributes.transform = new svg.Property('transform', this.attribute('gradientTransform').value);
group.children = [rect]; group.children = [rect];
const tempSvg = new svg.Element.svg(); const tempSvg = new svg.Element.svg();
tempSvg.attributes['x'] = new svg.Property('x', 0); tempSvg.attributes.x = new svg.Property('x', 0);
tempSvg.attributes['y'] = new svg.Property('y', 0); tempSvg.attributes.y = new svg.Property('y', 0);
tempSvg.attributes['width'] = new svg.Property('width', rootView.width); tempSvg.attributes.width = new svg.Property('width', rootView.width);
tempSvg.attributes['height'] = new svg.Property('height', rootView.height); tempSvg.attributes.height = new svg.Property('height', rootView.height);
tempSvg.children = [group]; tempSvg.children = [group];
const c = document.createElement('canvas'); const c = document.createElement('canvas');
@@ -1867,7 +1876,7 @@ function build (opts) {
update (delta) { update (delta) {
// set initial value // set initial value
if (this.initialValue == null) { if (isNullish(this.initialValue)) {
this.initialValue = this.getProperty().value; this.initialValue = this.getProperty().value;
this.initialUnits = this.getProperty().getUnits(); this.initialUnits = this.getProperty().getUnits();
} }
@@ -1949,7 +1958,7 @@ function build (opts) {
const r = from.r + (to.r - from.r) * p.progress; const r = from.r + (to.r - from.r) * p.progress;
const g = from.g + (to.g - from.g) * p.progress; const g = from.g + (to.g - from.g) * p.progress;
const b = from.b + (to.b - from.b) * p.progress; const b = from.b + (to.b - from.b) * p.progress;
return 'rgb(' + parseInt(r, 10) + ',' + parseInt(g, 10) + ',' + parseInt(b, 10) + ')'; return 'rgb(' + parseInt(r) + ',' + parseInt(g) + ',' + parseInt(b) + ')';
} }
return this.attribute('from').value; return this.attribute('from').value;
} }
@@ -2048,8 +2057,8 @@ function build (opts) {
super.setContext(ctx); super.setContext(ctx);
let textBaseline = this.style('dominant-baseline').toTextBaseline(); let textBaseline = this.style('dominant-baseline').toTextBaseline();
if (textBaseline == null) textBaseline = this.style('alignment-baseline').toTextBaseline(); if (isNullish(textBaseline)) textBaseline = this.style('alignment-baseline').toTextBaseline();
if (textBaseline != null) ctx.textBaseline = textBaseline; if (!isNullish(textBaseline)) ctx.textBaseline = textBaseline;
} }
getBoundingBox () { getBoundingBox () {
@@ -2124,18 +2133,18 @@ function build (opts) {
if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial'; if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial';
if (typeof font.glyphs[c] !== 'undefined') { if (typeof font.glyphs[c] !== 'undefined') {
glyph = font.glyphs[c][arabicForm]; glyph = font.glyphs[c][arabicForm];
if (glyph == null && font.glyphs[c].type === 'glyph') glyph = font.glyphs[c]; if (isNullish(glyph) && font.glyphs[c].type === 'glyph') glyph = font.glyphs[c];
} }
} else { } else {
glyph = font.glyphs[c]; glyph = font.glyphs[c];
} }
if (glyph == null) glyph = font.missingGlyph; if (isNullish(glyph)) glyph = font.missingGlyph;
return glyph; return glyph;
} }
renderChildren (ctx) { renderChildren (ctx) {
const customFont = this.parent.style('font-family').getDefinition(); const customFont = this.parent.style('font-family').getDefinition();
if (customFont != null) { if (!isNullish(customFont)) {
const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize); const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
const fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle); const fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
let text = this.getText(); let text = this.getText();
@@ -2182,7 +2191,7 @@ function build (opts) {
measureText (ctx) { measureText (ctx) {
const customFont = this.parent.style('font-family').getDefinition(); const customFont = this.parent.style('font-family').getDefinition();
if (customFont != null) { if (!isNullish(customFont)) {
const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize); const fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
let measure = 0; let measure = 0;
let text = this.getText(); let text = this.getText();
@@ -2225,7 +2234,8 @@ function build (opts) {
svg.Element.tref = class extends svg.Element.TextElementBase { svg.Element.tref = class extends svg.Element.TextElementBase {
getText () { getText () {
const element = this.getHrefAttribute().getDefinition(); const element = this.getHrefAttribute().getDefinition();
if (element != null) return element.children[0].getText(); if (!isNullish(element)) return element.children[0].getText();
return undefined;
} }
}; };
@@ -2290,13 +2300,12 @@ function build (opts) {
if (svg.opts.useCORS === true) { if (svg.opts.useCORS === true) {
this.img.crossOrigin = 'Anonymous'; this.img.crossOrigin = 'Anonymous';
} }
const self = this; this.img.onload = () => {
this.img.onload = function () { this.loaded = true;
self.loaded = true;
}; };
this.img.onerror = function () { this.img.onerror = () => {
svg.log('ERROR: image "' + href + '" not found'); svg.log('ERROR: image "' + href + '" not found');
self.loaded = true; this.loaded = true;
}; };
this.img.src = href; this.img.src = href;
} else { } else {
@@ -2387,14 +2396,14 @@ function build (opts) {
const prop = cssProp.indexOf(':'); const prop = cssProp.indexOf(':');
const name = cssProp.substr(0, prop); const name = cssProp.substr(0, prop);
const value = cssProp.substr(prop + 1, cssProp.length - prop); const value = cssProp.substr(prop + 1, cssProp.length - prop);
if (name != null && value != null) { if (!isNullish(name) && !isNullish(value)) {
props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value)); props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
} }
}); });
svg.Styles[cssClass] = props; svg.Styles[cssClass] = props;
if (cssClass === '@font-face') { if (cssClass === '@font-face') {
const fontFamily = props['font-family'].value.replace(/"/g, ''); const fontFamily = props['font-family'].value.replace(/"/g, '');
const srcs = props['src'].value.split(','); const srcs = props.src.value.split(',');
srcs.forEach((src) => { srcs.forEach((src) => {
if (src.includes('format("svg")')) { if (src.includes('format("svg")')) {
const urlStart = src.indexOf('url'); const urlStart = src.indexOf('url');
@@ -2433,31 +2442,31 @@ function build (opts) {
path (ctx) { path (ctx) {
const {_el: element} = this; const {_el: element} = this;
if (element != null) element.path(ctx); if (!isNullish(element)) element.path(ctx);
} }
getBoundingBox () { getBoundingBox () {
const {_el: element} = this; const {_el: element} = this;
if (element != null) return element.getBoundingBox(); if (!isNullish(element)) return element.getBoundingBox();
} }
renderChildren (ctx) { renderChildren (ctx) {
const {_el: element} = this; const {_el: element} = this;
if (element != null) { if (!isNullish(element)) {
let tempSvg = element; let tempSvg = element;
if (element.type === 'symbol') { if (element.type === 'symbol') {
// render me using a temporary svg element in symbol cases (https://www.w3.org/TR/SVG/struct.html#UseElement) // render me using a temporary svg element in symbol cases (https://www.w3.org/TR/SVG/struct.html#UseElement)
tempSvg = new svg.Element.svg(); tempSvg = new svg.Element.svg();
tempSvg.type = 'svg'; tempSvg.type = 'svg';
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', element.attribute('viewBox').value); tempSvg.attributes.viewBox = new svg.Property('viewBox', element.attribute('viewBox').value);
tempSvg.attributes['preserveAspectRatio'] = new svg.Property('preserveAspectRatio', element.attribute('preserveAspectRatio').value); tempSvg.attributes.preserveAspectRatio = new svg.Property('preserveAspectRatio', element.attribute('preserveAspectRatio').value);
tempSvg.attributes['overflow'] = new svg.Property('overflow', element.attribute('overflow').value); tempSvg.attributes.overflow = new svg.Property('overflow', element.attribute('overflow').value);
tempSvg.children = element.children; tempSvg.children = element.children;
} }
if (tempSvg.type === 'svg') { if (tempSvg.type === 'svg') {
// if symbol or svg, inherit width/height from me // if symbol or svg, inherit width/height from me
if (this.attribute('width').hasValue()) tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value); if (this.attribute('width').hasValue()) tempSvg.attributes.width = new svg.Property('width', this.attribute('width').value);
if (this.attribute('height').hasValue()) tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value); if (this.attribute('height').hasValue()) tempSvg.attributes.height = new svg.Property('height', this.attribute('height').value);
} }
const oldParent = tempSvg.parent; const oldParent = tempSvg.parent;
tempSvg.parent = null; tempSvg.parent = null;
@@ -2762,14 +2771,14 @@ function build (opts) {
// bind mouse // bind mouse
if (svg.opts.ignoreMouse !== true) { if (svg.opts.ignoreMouse !== true) {
ctx.canvas.onclick = function (e) { ctx.canvas.onclick = function (e) {
const args = e != null const args = !isNullish(e)
? [e.clientX, e.clientY] ? [e.clientX, e.clientY]
: [event.clientX, event.clientY]; : [event.clientX, event.clientY];
const {x, y} = mapXY(new svg.Point(...args)); const {x, y} = mapXY(new svg.Point(...args));
svg.Mouse.onclick(x, y); svg.Mouse.onclick(x, y);
}; };
ctx.canvas.onmousemove = function (e) { ctx.canvas.onmousemove = function (e) {
const args = e != null const args = !isNullish(e)
? [e.clientX, e.clientY] ? [e.clientX, e.clientY]
: [event.clientX, event.clientY]; : [event.clientX, event.clientY];
const {x, y} = mapXY(new svg.Point(...args)); const {x, y} = mapXY(new svg.Point(...args));
@@ -2812,17 +2821,17 @@ function build (opts) {
} }
svg.ViewPort.SetCurrent(cWidth, cHeight); svg.ViewPort.SetCurrent(cWidth, cHeight);
if (svg.opts.offsetX != null) { if (!isNullish(svg.opts.offsetX)) {
e.attribute('x', true).value = svg.opts.offsetX; e.attribute('x', true).value = svg.opts.offsetX;
} }
if (svg.opts.offsetY != null) { if (!isNullish(svg.opts.offsetY)) {
e.attribute('y', true).value = svg.opts.offsetY; e.attribute('y', true).value = svg.opts.offsetY;
} }
if (svg.opts.scaleWidth != null || svg.opts.scaleHeight != null) { if (!isNullish(svg.opts.scaleWidth) || !isNullish(svg.opts.scaleHeight)) {
const viewBox = svg.ToNumberArray(e.attribute('viewBox').value); const viewBox = svg.ToNumberArray(e.attribute('viewBox').value);
let xRatio = null, yRatio = null; let xRatio = null, yRatio = null;
if (svg.opts.scaleWidth != null) { if (!isNullish(svg.opts.scaleWidth)) {
if (e.attribute('width').hasValue()) { if (e.attribute('width').hasValue()) {
xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth; xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth;
} else if (!isNaN(viewBox[2])) { } else if (!isNaN(viewBox[2])) {
@@ -2830,7 +2839,7 @@ function build (opts) {
} }
} }
if (svg.opts.scaleHeight != null) { if (!isNullish(svg.opts.scaleHeight)) {
if (e.attribute('height').hasValue()) { if (e.attribute('height').hasValue()) {
yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight; yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight;
} else if (!isNaN(viewBox[3])) { } else if (!isNaN(viewBox[3])) {
@@ -2838,8 +2847,8 @@ function build (opts) {
} }
} }
if (xRatio == null) { xRatio = yRatio; } if (isNullish(xRatio)) { xRatio = yRatio; }
if (yRatio == null) { yRatio = xRatio; } if (isNullish(yRatio)) { yRatio = xRatio; }
e.attribute('width', true).value = svg.opts.scaleWidth; e.attribute('width', true).value = svg.opts.scaleWidth;
e.attribute('height', true).value = svg.opts.scaleHeight; e.attribute('height', true).value = svg.opts.scaleHeight;

View File

@@ -158,9 +158,9 @@ const colorDefs = [
example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'], example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
process (bits) { process (bits) {
return [ return [
parseInt(bits[1], 10), parseInt(bits[1]),
parseInt(bits[2], 10), parseInt(bits[2]),
parseInt(bits[3], 10) parseInt(bits[3])
]; ];
} }
}, },
@@ -279,8 +279,7 @@ export default class RGBColor {
margin: 3px; margin: 3px;
border: 1px solid black; border: 1px solid black;
background: ${listColor.toHex()}; background: ${listColor.toHex()};
color: ${listColor.toHex()};` color: ${listColor.toHex()};`;
;
exampleDiv.append('test'); exampleDiv.append('test');
const listItemValue = ` ${examples[i]} -> ${listColor.toRGB()} -> ${listColor.toHex()}`; const listItemValue = ` ${examples[i]} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;
listItem.append(exampleDiv, listItemValue); listItem.append(exampleDiv, listItemValue);

View File

@@ -93,9 +93,9 @@ const injectExtendedContextMenuItemIntoDom = function (menuItem) {
* @returns {undefined} * @returns {undefined}
*/ */
export const injectExtendedContextMenuItemsIntoDom = function () { export const injectExtendedContextMenuItemsIntoDom = function () {
for (const menuItem in contextMenuExtensions) { Object.values(contextMenuExtensions).forEach((menuItem) => {
injectExtendedContextMenuItemIntoDom(contextMenuExtensions[menuItem]); injectExtendedContextMenuItemIntoDom(menuItem);
} });
}; };
/** /**
* @function module:contextmenu.resetCustomMenus * @function module:contextmenu.resetCustomMenus

View File

@@ -74,8 +74,7 @@ function jQueryContextMenu ($) {
// Add contextMenu class // Add contextMenu class
menu.addClass('contextMenu'); menu.addClass('contextMenu');
// Simulate a true right click // Simulate a true right click
$(this).bind('mousedown', function (e) { $(this).bind('mousedown', function (evt) {
const evt = e;
$(this).mouseup(function (e) { $(this).mouseup(function (e) {
const srcElement = $(this); const srcElement = $(this);
srcElement.unbind('mouseup'); srcElement.unbind('mouseup');
@@ -109,8 +108,8 @@ function jQueryContextMenu ($) {
}); });
// Keyboard // Keyboard
doc.keypress(function (e) { doc.keypress(function (ev) {
switch (e.keyCode) { switch (ev.keyCode) {
case 38: // up case 38: // up
if (!menu.find('LI.hover').length) { if (!menu.find('LI.hover').length) {
menu.find('LI:last').addClass('hover'); menu.find('LI:last').addClass('hover');
@@ -143,7 +142,9 @@ function jQueryContextMenu ($) {
$('.contextMenu').hide(); $('.contextMenu').hide();
// Callback // Callback
if (callback) { if (callback) {
callback($(this).attr('href').substr(1), $(srcElement), {x: x - offset.left, y: y - offset.top, docX: x, docY: y}); callback($(this).attr('href').substr(1), $(srcElement), {
x: x - offset.left, y: y - offset.top, docX: x, docY: y
});
} }
return false; return false;
}); });

View File

@@ -17,8 +17,10 @@ import {getTransformList} from './svgtransformlist.js';
const $ = jQuery; const $ = jQuery;
// this is how we map paths to our preferred relative segment types // this is how we map paths to our preferred relative segment types
const pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', const pathMap = [
'H', 'h', 'V', 'v', 'S', 's', 'T', 't']; 0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
];
/** /**
* @interface module:coords.EditorContext * @interface module:coords.EditorContext
@@ -59,9 +61,9 @@ export const remapElement = function (selected, changes, m) {
doSnapping = editorContext_.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg', doSnapping = editorContext_.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg',
finishUp = function () { finishUp = function () {
if (doSnapping) { if (doSnapping) {
for (const o in changes) { Object.entries(changes).forEach(([o, value]) => {
changes[o] = snapToGrid(changes[o]); changes[o] = snapToGrid(value);
} });
} }
assignAttributes(selected, changes, 1000, true); assignAttributes(selected, changes, 1000, true);
}, },
@@ -297,8 +299,8 @@ export const remapElement = function (selected, changes, m) {
break; break;
case 11: // relative elliptical arc (a) case 11: // relative elliptical arc (a)
case 10: // absolute elliptical arc (A) case 10: // absolute elliptical arc (A)
dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + (+seg.largeArcFlag) + dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) +
' ' + (+seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '; ' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
break; break;
case 17: // relative smooth cubic (s) case 17: // relative smooth cubic (s)
case 16: // absolute smooth cubic (S) case 16: // absolute smooth cubic (S)

View File

@@ -139,7 +139,7 @@ export class Drawing {
const n = this.svgElem_.getAttributeNS(NS.SE, 'nonce'); const n = this.svgElem_.getAttributeNS(NS.SE, 'nonce');
// If already set in the DOM, use the nonce throughout the document // If already set in the DOM, use the nonce throughout the document
// else, if randomizeIds(true) has been called, create and set the nonce. // else, if randomizeIds(true) has been called, create and set the nonce.
if (!!n && randIds !== RandomizeModes.NEVER_RANDOMIZE) { if (n && randIds !== RandomizeModes.NEVER_RANDOMIZE) {
this.nonce_ = n; this.nonce_ = n;
} else if (randIds === RandomizeModes.ALWAYS_RANDOMIZE) { } else if (randIds === RandomizeModes.ALWAYS_RANDOMIZE) {
this.setNonce(Math.floor(Math.random() * 100001)); this.setNonce(Math.floor(Math.random() * 100001));
@@ -253,7 +253,7 @@ export class Drawing {
return false; return false;
} }
// extract the obj_num of this id // extract the obj_num of this id
const num = parseInt(id.substr(front.length), 10); const num = parseInt(id.substr(front.length));
// if we didn't get a positive number or we already released this number // if we didn't get a positive number or we already released this number
// then return false. // then return false.
@@ -669,8 +669,8 @@ export class Drawing {
* @returns {Element} * @returns {Element}
*/ */
copyElem (el) { copyElem (el) {
const self = this; const that = this;
const getNextIdClosure = function () { return self.getNextId(); }; const getNextIdClosure = function () { return that.getNextId(); };
return utilCopyElem(el, getNextIdClosure); return utilCopyElem(el, getNextIdClosure);
} }
} }

View File

@@ -22,11 +22,11 @@ let cbid = 0;
*/ */
function getCallbackSetter (funcName) { function getCallbackSetter (funcName) {
return function (...args) { return function (...args) {
const t = this, // New callback const that = this, // New callback
cbid = t.send(funcName, args, function () {}); // The callback (currently it's nothing, but will be set later) callbackID = this.send(funcName, args, function () { /* */ }); // The callback (currently it's nothing, but will be set later)
return function (newCallback) { return function (newCallback) {
t.callbacks[cbid] = newCallback; // Set callback that.callbacks[callbackID] = newCallback; // Set callback
}; };
}; };
} }
@@ -39,14 +39,14 @@ function getCallbackSetter (funcName) {
* @param {JSON} data * @param {JSON} data
* @returns {undefined} * @returns {undefined}
*/ */
function addCallback (t, {result, error, id: cbid}) { function addCallback (t, {result, error, id: callbackID}) {
if (typeof cbid === 'number' && t.callbacks[cbid]) { if (typeof callbackID === 'number' && t.callbacks[callbackID]) {
// These should be safe both because we check `cbid` is numeric and // These should be safe both because we check `cbid` is numeric and
// because the calls are from trusted origins // because the calls are from trusted origins
if (result) { if (result) {
t.callbacks[cbid](result); // lgtm [js/remote-property-injection] t.callbacks[callbackID](result); // lgtm [js/remote-property-injection]
} else { } else {
t.callbacks[cbid](error, 'error'); // lgtm [js/remote-property-injection] t.callbacks[callbackID](error, 'error'); // lgtm [js/remote-property-injection]
} }
} }
} }
@@ -67,7 +67,7 @@ function messageListener (e) {
e.source !== this.frame.contentWindow || e.source !== this.frame.contentWindow ||
(!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin)) (!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin))
) { ) {
console.log(`The origin ${e.origin} was not whitelisted as an origin from which responses may be received by this ${window.origin} script.`); console.log(`The origin ${e.origin} was not whitelisted as an origin from which responses may be received by this ${window.origin} script.`); // eslint-disable-line no-console
return; return;
} }
addCallback(this, data); addCallback(this, data);
@@ -136,7 +136,7 @@ class EmbeddedSVGEdit {
* If supplied, it should probably be the same as svgEditor's allowedOrigins * If supplied, it should probably be the same as svgEditor's allowedOrigins
*/ */
constructor (frame, allowedOrigins) { constructor (frame, allowedOrigins) {
const t = this; const that = this;
this.allowedOrigins = allowedOrigins || []; this.allowedOrigins = allowedOrigins || [];
// Initialize communication // Initialize communication
this.frame = frame; this.frame = frame;
@@ -326,7 +326,7 @@ class EmbeddedSVGEdit {
const keyboardEvent = new KeyboardEvent(e.type, { const keyboardEvent = new KeyboardEvent(e.type, {
key, keyCode, charCode, which key, keyCode, charCode, which
}); });
t.frame.contentDocument.dispatchEvent(keyboardEvent); that.frame.contentDocument.dispatchEvent(keyboardEvent);
} }
}); });
} }
@@ -338,11 +338,11 @@ class EmbeddedSVGEdit {
* @returns {Integer} * @returns {Integer}
*/ */
send (name, args, callback) { send (name, args, callback) {
const t = this; const that = this;
cbid++; cbid++;
this.callbacks[cbid] = callback; this.callbacks[cbid] = callback;
setTimeout((function (cbid) { setTimeout((function (callbackID) {
return function () { // Delay for the callback to be set in case its synchronous return function () { // Delay for the callback to be set in case its synchronous
/* /*
* Todo: Handle non-JSON arguments and return values (undefined, * Todo: Handle non-JSON arguments and return values (undefined,
@@ -354,8 +354,8 @@ class EmbeddedSVGEdit {
// We accept and post strings for the sake of IE9 support // We accept and post strings for the sake of IE9 support
let sameOriginWithGlobal = false; let sameOriginWithGlobal = false;
try { try {
sameOriginWithGlobal = window.location.origin === t.frame.contentWindow.location.origin && sameOriginWithGlobal = window.location.origin === that.frame.contentWindow.location.origin &&
t.frame.contentWindow.svgEditor.canvas; that.frame.contentWindow.svgEditor.canvas;
} catch (err) {} } catch (err) {}
if (sameOriginWithGlobal) { if (sameOriginWithGlobal) {
@@ -365,17 +365,17 @@ class EmbeddedSVGEdit {
// of the current JSON-based communication API (e.g., not passing // of the current JSON-based communication API (e.g., not passing
// callbacks). We might be able to address these shortcomings; see // callbacks). We might be able to address these shortcomings; see
// the todo elsewhere in this file. // the todo elsewhere in this file.
const message = {id: cbid}, const message = {id: callbackID},
{svgEditor: {canvas: svgCanvas}} = t.frame.contentWindow; {svgEditor: {canvas: svgCanvas}} = that.frame.contentWindow;
try { try {
message.result = svgCanvas[name].apply(svgCanvas, args); message.result = svgCanvas[name](...args);
} catch (err) { } catch (err) {
message.error = err.message; message.error = err.message;
} }
addCallback(t, message); addCallback(that, message);
} else { // Requires the ext-xdomain-messaging.js extension } else { // Requires the ext-xdomain-messaging.js extension
t.frame.contentWindow.postMessage(JSON.stringify({ that.frame.contentWindow.postMessage(JSON.stringify({
namespace: 'svgCanvas', id: cbid, name, args namespace: 'svgCanvas', id: callbackID, name, args
}), '*'); }), '*');
} }
}; };

View File

@@ -67,7 +67,7 @@ export default {
} }
function getOffset (side, line) { function getOffset (side, line) {
const giveOffset = !!line.getAttribute('marker-' + side); const giveOffset = line.getAttribute('marker-' + side);
// const giveOffset = $(line).data(side+'_off'); // const giveOffset = $(line).data(side+'_off');
// TODO: Make this number (5) be based on marker width/height // TODO: Make this number (5) be based on marker width/height
@@ -170,7 +170,7 @@ export default {
['start', 'end'].forEach(function (pos, i) { ['start', 'end'].forEach(function (pos, i) {
const key = 'c_' + pos; const key = 'c_' + pos;
let part = elData(this, key); let part = elData(this, key);
if (part == null) { if (part === null || part === undefined) { // Does this ever return nullish values?
part = document.getElementById( part = document.getElementById(
this.attributes['se:connector'].value.split(' ')[i] this.attributes['se:connector'].value.split(' ')[i]
); );
@@ -178,7 +178,7 @@ export default {
elData(this, pos + '_bb', svgCanvas.getStrokedBBox([part])); elData(this, pos + '_bb', svgCanvas.getStrokedBBox([part]));
} else part = document.getElementById(part); } else part = document.getElementById(part);
parts.push(part); parts.push(part);
}.bind(this)); }, this);
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
const cElem = parts[i]; const cElem = parts[i];
@@ -262,15 +262,15 @@ export default {
(function () { (function () {
const gse = svgCanvas.groupSelectedElements; const gse = svgCanvas.groupSelectedElements;
svgCanvas.groupSelectedElements = function () { svgCanvas.groupSelectedElements = function (...args) {
svgCanvas.removeFromSelection($(connSel).toArray()); svgCanvas.removeFromSelection($(connSel).toArray());
return gse.apply(this, arguments); return gse.apply(this, args);
}; };
const mse = svgCanvas.moveSelectedElements; const mse = svgCanvas.moveSelectedElements;
svgCanvas.moveSelectedElements = function () { svgCanvas.moveSelectedElements = function (...args) {
const cmd = mse.apply(this, arguments); const cmd = mse.apply(this, args);
updateConnectors(); updateConnectors();
return cmd; return cmd;
}; };
@@ -331,7 +331,7 @@ export default {
buttons: strings.buttons.map((button, i) => { buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button); return Object.assign(buttons[i], button);
}), }),
async addLangData ({lang, importLocale}) { /* async */ addLangData ({lang, importLocale}) {
return { return {
data: strings.langList data: strings.langList
}; };
@@ -548,8 +548,8 @@ export default {
const end = elem.getAttribute('marker-end'); const end = elem.getAttribute('marker-end');
curLine = elem; curLine = elem;
$(elem) $(elem)
.data('start_off', !!start) .data('start_off', Boolean(start))
.data('end_off', !!end); .data('end_off', Boolean(end));
if (elem.tagName === 'line' && mid) { if (elem.tagName === 'line' && mid) {
// Convert to polyline to accept mid-arrow // Convert to polyline to accept mid-arrow

View File

@@ -16,7 +16,7 @@ export default {
// https://code.google.com/p/chromium/issues/detail?id=565120. // https://code.google.com/p/chromium/issues/detail?id=565120.
if (isChrome()) { if (isChrome()) {
const verIndex = navigator.userAgent.indexOf('Chrome/') + 7; const verIndex = navigator.userAgent.indexOf('Chrome/') + 7;
const chromeVersion = parseInt(navigator.userAgent.substring(verIndex), 10); const chromeVersion = parseInt(navigator.userAgent.substring(verIndex));
if (chromeVersion < 49) { if (chromeVersion < 49) {
return; return;
} }

View File

@@ -24,7 +24,7 @@ export default {
* @throws {Error} Unexpected event type * @throws {Error} Unexpected event type
* @returns {undefined} * @returns {undefined}
*/ */
(win, {data, origin}) => { (win, {data, origin}) => { // eslint-disable-line no-shadow
// console.log('data, origin', data, origin); // console.log('data, origin', data, origin);
let type, content; let type, content;
try { try {
@@ -78,17 +78,18 @@ export default {
if (!pathID) { // Not ready yet as haven't received first payload if (!pathID) { // Not ready yet as haven't received first payload
return; return;
} }
window.postMessage({ window.postMessage(
webappfind: { {
type: saveMessage, webappfind: {
pathID, type: saveMessage,
content: svgEditor.canvas.getSvgString() pathID,
} content: svgEditor.canvas.getSvgString()
}, window.location.origin === 'null' }
// Avoid "null" string error for `file:` protocol (even }, window.location.origin === 'null'
// though file protocol not currently supported by add-on) // Avoid "null" string error for `file:` protocol (even
? '*' // though file protocol not currently supported by add-on)
: window.location.origin ? '*'
: window.location.origin
); );
} }
} }

View File

@@ -1,12 +1,21 @@
import {jml, body, nbsp} from '../../external/jamilih/jml-es.js'; import {jml, body, nbsp} from '../../external/jamilih/jml-es.js';
import $ from '../../external/query-result/esm/index.js'; import $ from '../../external/query-result/esm/index.js';
import {manipulation} from '../../external/qr-manipulation/dist/index-es.js'; import {manipulation} from '../../external/qr-manipulation/dist/index-es.js';
manipulation($, jml); manipulation($, jml);
const baseAPIURL = 'https://openclipart.org/search/json/'; const baseAPIURL = 'https://openclipart.org/search/json/';
/**
* Shows results after query submission.
* @param {string} url
* @returns {undefined}
*/
async function processResults (url) { async function processResults (url) {
/**
* @param {string} query
* @returns {external:JamilihArray}
*/
function queryLink (query) { function queryLink (query) {
return ['a', { return ['a', {
href: 'javascript: void(0);', href: 'javascript: void(0);',
@@ -22,7 +31,7 @@ async function processResults (url) {
const r = await fetch(url); const r = await fetch(url);
const json = await r.json(); const json = await r.json();
console.log('json', json); // console.log('json', json);
if (!json || json.msg !== 'success') { if (!json || json.msg !== 'success') {
alert('There was a problem downloading the results'); alert('There was a problem downloading the results');
@@ -74,7 +83,7 @@ async function processResults (url) {
async click (e) { async click (e) {
e.preventDefault(); e.preventDefault();
const {value: svgURL, id} = this.dataset; const {value: svgURL, id} = this.dataset;
console.log('this', id, svgURL); // console.log('this', id, svgURL);
const post = (message) => { const post = (message) => {
// Todo: Make origin customizable as set by opening window // Todo: Make origin customizable as set by opening window
// Todo: If dropping IE9, avoid stringifying // Todo: If dropping IE9, avoid stringifying
@@ -90,7 +99,7 @@ async function processResults (url) {
}); });
const result = await fetch(svgURL); const result = await fetch(svgURL);
const svg = await result.text(); const svg = await result.text();
console.log('h', svgURL, svg); // console.log('url and svg', svgURL, svg);
post({ post({
href: svgURL, href: svgURL,
data: svg data: svg

View File

@@ -5,12 +5,23 @@
* @module importModule * @module importModule
*/ */
/**
* Converts a possible relative URL into an absolute one.
* @param {string} url
* @returns {string}
*/
function toAbsoluteURL (url) { function toAbsoluteURL (url) {
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute('href', url); // <a href="hoge.html"> a.setAttribute('href', url); // <a href="hoge.html">
return a.cloneNode(false).href; // -> "http://example.com/hoge.html" return a.cloneNode(false).href; // -> "http://example.com/hoge.html"
} }
/**
* Add any of the whitelisted attributes to the script tag.
* @param {HTMLScriptElement} script
* @param {PlainObject.<string, string>} atts
* @returns {undefined}
*/
function addScriptAtts (script, atts) { function addScriptAtts (script, atts) {
['id', 'class', 'type'].forEach((prop) => { ['id', 'class', 'type'].forEach((prop) => {
if (prop in atts) { if (prop in atts) {
@@ -27,18 +38,19 @@ function addScriptAtts (script, atts) {
*/ */
/** /**
* @function module:importModule.importSetGlobalDefault * @function module:importModule.importSetGlobalDefault
* @param {string} url * @param {string|string[]} url
* @param {module:importModule.ImportConfig} config * @param {module:importModule.ImportConfig} config
* @returns {*} The return 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 async function importSetGlobalDefault (url, config) { export function importSetGlobalDefault (url, config) {
return importSetGlobal(url, {...config, returnDefault: true}); return importSetGlobal(url, {...config, returnDefault: true});
} }
/** /**
* @function module:importModule.importSetGlobal * @function module:importModule.importSetGlobal
* @param {string} url * @param {string|string[]} url
* @param {module:importModule.ImportConfig} config * @param {module:importModule.ImportConfig} config
* @returns {ArbitraryModule|*} The return depends on the export of the targeted module. * @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, returnDefault}) { export async function importSetGlobal (url, {global, returnDefault}) {
// Todo: Replace calls to this function with `import()` when supported // Todo: Replace calls to this function with `import()` when supported
@@ -51,14 +63,21 @@ export async function importSetGlobal (url, {global, returnDefault}) {
await importScript(url); await importScript(url);
return window[global]; return window[global];
} }
// Addition by Brett /**
*
* @author Brett Zamir (other items are from `dynamic-import-polyfill`)
* @param {string|string[]} url
* @param {Object} [atts={}]
* @returns {Promise} Resolves to `undefined` or rejects with an `Error` upon a
* script loading error
*/
export function importScript (url, atts = {}) { export function importScript (url, atts = {}) {
if (Array.isArray(url)) { if (Array.isArray(url)) {
return Promise.all(url.map((u) => { return Promise.all(url.map((u) => {
return importScript(u, atts); return importScript(u, atts);
})); }));
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
const script = document.createElement('script'); const script = document.createElement('script');
const destructor = () => { const destructor = () => {
script.onerror = null; script.onerror = null;
@@ -82,13 +101,22 @@ export function importScript (url, atts = {}) {
}); });
} }
/**
*
* @param {string|string[]} url
* @param {Object} [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} = {}) { export function importModule (url, atts = {}, {returnDefault = false} = {}) {
if (Array.isArray(url)) { if (Array.isArray(url)) {
return Promise.all(url.map((u) => { return Promise.all(url.map((u) => {
return importModule(u, atts); return importModule(u, atts);
})); }));
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
const vector = '$importModule$' + Math.random().toString(32).slice(2); const vector = '$importModule$' + Math.random().toString(32).slice(2);
const script = document.createElement('script'); const script = document.createElement('script');
const destructor = () => { const destructor = () => {

View File

@@ -5,7 +5,7 @@
* @copyright 2010 Jeff Schiller * @copyright 2010 Jeff Schiller
*/ */
import {getHref, setHref, getRotationAngle} from './utilities.js'; import {getHref, setHref, getRotationAngle, isNullish} from './utilities.js';
import {removeElementFromListMap} from './svgtransformlist.js'; import {removeElementFromListMap} from './svgtransformlist.js';
/** /**
@@ -285,9 +285,9 @@ export class RemoveElementCommand extends Command {
} }
removeElementFromListMap(this.elem); removeElementFromListMap(this.elem);
if (this.nextSibling == null) { if (isNullish(this.nextSibling)) {
if (window.console) { if (window.console) {
console.log('Error: reference element was lost'); console.log('Error: reference element was lost'); // eslint-disable-line no-console
} }
} }
this.parent.insertBefore(this.elem, this.nextSibling); // Don't use `before` or `prepend` as `this.nextSibling` may be `null` this.parent.insertBefore(this.elem, this.nextSibling); // Don't use `before` or `prepend` as `this.nextSibling` may be `null`
@@ -670,7 +670,7 @@ export class UndoManager {
const oldValues = new Array(i), elements = new Array(i); const oldValues = new Array(i), elements = new Array(i);
while (i--) { while (i--) {
const elem = elems[i]; const elem = elems[i];
if (elem == null) { continue; } if (isNullish(elem)) { continue; }
elements[i] = elem; elements[i] = elem;
oldValues[i] = elem.getAttribute(attrName); oldValues[i] = elem.getAttribute(attrName);
} }
@@ -695,7 +695,7 @@ export class UndoManager {
let i = changeset.elements.length; let i = changeset.elements.length;
while (i--) { while (i--) {
const elem = changeset.elements[i]; const elem = changeset.elements[i];
if (elem == null) { continue; } if (isNullish(elem)) { continue; }
const changes = {}; const changes = {};
changes[attrName] = changeset.oldValues[i]; changes[attrName] = changeset.oldValues[i];
if (changes[attrName] !== elem.getAttribute(attrName)) { if (changes[attrName] !== elem.getAttribute(attrName)) {

View File

@@ -77,8 +77,8 @@ class HistoryRecordingService {
if (this.currentBatchCommand_) { if (this.currentBatchCommand_) {
const batchCommand = this.currentBatchCommand_; const batchCommand = this.currentBatchCommand_;
this.batchCommandStack_.pop(); this.batchCommandStack_.pop();
const {length} = this.batchCommandStack_; const {length: len} = this.batchCommandStack_;
this.currentBatchCommand_ = length ? this.batchCommandStack_[length - 1] : null; this.currentBatchCommand_ = len ? this.batchCommandStack_[len - 1] : null;
this.addCommand_(batchCommand); this.addCommand_(batchCommand);
} }
return this; return this;
@@ -141,7 +141,7 @@ class HistoryRecordingService {
* Private function to add a command to the history or current batch command. * Private function to add a command to the history or current batch command.
* @private * @private
* @param {Command} cmd * @param {Command} cmd
* @returns {module:history.HistoryRecordingService} * @returns {module:history.HistoryRecordingService|undefined}
*/ */
addCommand_ (cmd) { addCommand_ (cmd) {
if (!this.undoManager_) { return this; } if (!this.undoManager_) { return this; }
@@ -150,6 +150,7 @@ class HistoryRecordingService {
} else { } else {
this.undoManager_.addCommandToHistory(cmd); this.undoManager_.addCommandToHistory(cmd);
} }
return undefined;
} }
} }
/** /**

View File

@@ -18,7 +18,7 @@
* @param {external:jQuery} $ The jQuery object to which to add the plug-in * @param {external:jQuery} $ The jQuery object to which to add the plug-in
* @returns {external:jQuery} * @returns {external:jQuery}
*/ */
export default function ($) { export default function jQueryPluginSVG ($) {
const proxied = $.fn.attr, const proxied = $.fn.attr,
svgns = 'http://www.w3.org/2000/svg'; svgns = 'http://www.w3.org/2000/svg';
/** /**
@@ -32,7 +32,7 @@ export default function ($) {
*/ */
$.fn.attr = function (key, value) { $.fn.attr = function (key, value) {
const len = this.length; const len = this.length;
if (!len) { return proxied.apply(this, arguments); } if (!len) { return proxied.call(this, key, value); }
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
const elem = this[i]; const elem = this[i];
// set/get SVG attribute // set/get SVG attribute
@@ -70,7 +70,7 @@ export default function ($) {
return attr; return attr;
} }
} else { } else {
return proxied.apply(this, arguments); return proxied.call(this, key, value);
} }
} }
return this; return this;

View File

@@ -407,7 +407,8 @@ export default function ($) {
'<div class="jGraduate_OkCancel">' + '<div class="jGraduate_OkCancel">' +
'<input type="button" id="' + id + '_jGraduate_Ok" class="jGraduate_Ok" value="OK"/>' + '<input type="button" id="' + id + '_jGraduate_Ok" class="jGraduate_Ok" value="OK"/>' +
'<input type="button" id="' + id + '_jGraduate_Cancel" class="jGraduate_Cancel" value="Cancel"/>' + '<input type="button" id="' + id + '_jGraduate_Cancel" class="jGraduate_Cancel" value="Cancel"/>' +
'</div>'); '</div>'
);
// -------------- // --------------
// Set up all the SVG elements (the gradient, stops and rectangle) // Set up all the SVG elements (the gradient, stops and rectangle)
@@ -1234,7 +1235,7 @@ export default function ($) {
const sm = spreadMethodOpt.val(); const sm = spreadMethodOpt.val();
curGradient.setAttribute('spreadMethod', sm); curGradient.setAttribute('spreadMethod', sm);
} }
showFocus = type === 'rg' && curGradient.getAttribute('fx') != null && !(cx === fx && cy === fy); showFocus = type === 'rg' && curGradient.getAttribute('fx') !== null && !(cx === fx && cy === fy);
$('#' + id + '_jGraduate_focusCoord').toggle(showFocus); $('#' + id + '_jGraduate_focusCoord').toggle(showFocus);
if (showFocus) { if (showFocus) {
$('#' + id + '_jGraduate_match_ctr')[0].checked = false; $('#' + id + '_jGraduate_match_ctr')[0].checked = false;

File diff suppressed because it is too large Load Diff

View File

@@ -106,7 +106,7 @@ const svgElementToPdf = function (element, pdf, options) {
pdf.setFillColor(fillRGB.r, fillRGB.g, fillRGB.b); pdf.setFillColor(fillRGB.r, fillRGB.g, fillRGB.b);
} }
if (attributeIsNotEmpty(node, 'stroke-width')) { if (attributeIsNotEmpty(node, 'stroke-width')) {
pdf.setLineWidth(k * parseInt(node.getAttribute('stroke-width'), 10)); pdf.setLineWidth(k * parseInt(node.getAttribute('stroke-width')));
} }
const strokeColor = node.getAttribute('stroke'); const strokeColor = node.getAttribute('stroke');
if (attributeIsNotEmpty(strokeColor)) { if (attributeIsNotEmpty(strokeColor)) {
@@ -134,38 +134,38 @@ const svgElementToPdf = function (element, pdf, options) {
break; break;
case 'line': case 'line':
pdf.line( pdf.line(
k * parseInt(node.getAttribute('x1'), 10), k * parseInt(node.getAttribute('x1')),
k * parseInt(node.getAttribute('y1'), 10), k * parseInt(node.getAttribute('y1')),
k * parseInt(node.getAttribute('x2'), 10), k * parseInt(node.getAttribute('x2')),
k * parseInt(node.getAttribute('y2'), 10) k * parseInt(node.getAttribute('y2'))
); );
removeAttributes(node, pdfSvgAttr.line); removeAttributes(node, pdfSvgAttr.line);
break; break;
case 'rect': case 'rect':
pdf.rect( pdf.rect(
k * parseInt(node.getAttribute('x'), 10), k * parseInt(node.getAttribute('x')),
k * parseInt(node.getAttribute('y'), 10), k * parseInt(node.getAttribute('y')),
k * parseInt(node.getAttribute('width'), 10), k * parseInt(node.getAttribute('width')),
k * parseInt(node.getAttribute('height'), 10), k * parseInt(node.getAttribute('height')),
colorMode colorMode
); );
removeAttributes(node, pdfSvgAttr.rect); removeAttributes(node, pdfSvgAttr.rect);
break; break;
case 'ellipse': case 'ellipse':
pdf.ellipse( pdf.ellipse(
k * parseInt(node.getAttribute('cx'), 10), k * parseInt(node.getAttribute('cx')),
k * parseInt(node.getAttribute('cy'), 10), k * parseInt(node.getAttribute('cy')),
k * parseInt(node.getAttribute('rx'), 10), k * parseInt(node.getAttribute('rx')),
k * parseInt(node.getAttribute('ry'), 10), k * parseInt(node.getAttribute('ry')),
colorMode colorMode
); );
removeAttributes(node, pdfSvgAttr.ellipse); removeAttributes(node, pdfSvgAttr.ellipse);
break; break;
case 'circle': case 'circle':
pdf.circle( pdf.circle(
k * parseInt(node.getAttribute('cx'), 10), k * parseInt(node.getAttribute('cx')),
k * parseInt(node.getAttribute('cy'), 10), k * parseInt(node.getAttribute('cy')),
k * parseInt(node.getAttribute('r'), 10), k * parseInt(node.getAttribute('r')),
colorMode colorMode
); );
removeAttributes(node, pdfSvgAttr.circle); removeAttributes(node, pdfSvgAttr.circle);
@@ -216,7 +216,7 @@ const svgElementToPdf = function (element, pdf, options) {
} }
pdf.setFontType(fontType); pdf.setFontType(fontType);
const pdfFontSize = node.hasAttribute('font-size') const pdfFontSize = node.hasAttribute('font-size')
? parseInt(node.getAttribute('font-size'), 10) ? parseInt(node.getAttribute('font-size'))
: 16; : 16;
const getWidth = (node) => { const getWidth = (node) => {
@@ -247,8 +247,8 @@ const svgElementToPdf = function (element, pdf, options) {
case 'start': break; case 'start': break;
case 'default': node.setAttribute('text-anchor', 'start'); break; case 'default': node.setAttribute('text-anchor', 'start'); break;
} }
x = parseInt(node.getAttribute('x'), 10) - xOffset; x = parseInt(node.getAttribute('x')) - xOffset;
y = parseInt(node.getAttribute('y'), 10); y = parseInt(node.getAttribute('y'));
} }
// console.log('fontSize:', pdfFontSize, 'text:', node.textContent); // console.log('fontSize:', pdfFontSize, 'text:', node.textContent);
pdf.setFontSize(pdfFontSize).text( pdf.setFontSize(pdfFontSize).text(

View File

@@ -8,7 +8,7 @@
*/ */
import {NS} from './namespaces.js'; import {NS} from './namespaces.js';
import {toXml, walkTree} from './utilities.js'; import {toXml, walkTree, isNullish} from './utilities.js';
const $ = jQuery; const $ = jQuery;
@@ -117,7 +117,7 @@ class Layer {
*/ */
getOpacity () { getOpacity () {
const opacity = this.group_.getAttribute('opacity'); const opacity = this.group_.getAttribute('opacity');
if (opacity === null || opacity === undefined) { if (isNullish(opacity)) {
return 1; return 1;
} }
return parseFloat(opacity); return parseFloat(opacity);
@@ -212,7 +212,7 @@ Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)');
*/ */
function addLayerClass (elem) { function addLayerClass (elem) {
const classes = elem.getAttribute('class'); const classes = elem.getAttribute('class');
if (classes === null || classes === undefined || !classes.length) { if (isNullish(classes) || !classes.length) {
elem.setAttribute('class', Layer.CLASS_NAME); elem.setAttribute('class', Layer.CLASS_NAME);
} else if (!Layer.CLASS_REGEX.test(classes)) { } else if (!Layer.CLASS_REGEX.test(classes)) {
elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME); elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME);

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'Es sind nicht-gespeicherte Änderungen vorhanden.', unsavedChanges: 'Es sind nicht-gespeicherte Änderungen vorhanden.',
enterNewLinkURL: 'Geben Sie die neue URL ein', enterNewLinkURL: 'Geben Sie die neue URL ein',
errorLoadingSVG: 'Fehler: Kann SVG-Daten nicht laden', errorLoadingSVG: 'Fehler: Kann SVG-Daten nicht laden',
URLloadFail: 'Kann von dieser URL nicht laden', URLLoadFail: 'Kann von dieser URL nicht laden',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...", retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'Il y a des changements non sauvegardés.', unsavedChanges: 'Il y a des changements non sauvegardés.',
enterNewLinkURL: "Entrez la nouvelle URL de l'hyperlien", enterNewLinkURL: "Entrez la nouvelle URL de l'hyperlien",
errorLoadingSVG: 'Erreur : Impossible de charger les données SVG', errorLoadingSVG: 'Erreur : Impossible de charger les données SVG',
URLloadFail: "Impossible de charger l'URL", URLLoadFail: "Impossible de charger l'URL",
retrieving: 'Récupération de « %s »…', retrieving: 'Récupération de « %s »…',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\'...', retrieving: 'Retrieving \'%s\'...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -207,7 +207,7 @@ export default {
unsavedChanges: 'Wykryto niezapisane zmiany.', unsavedChanges: 'Wykryto niezapisane zmiany.',
enterNewLinkURL: 'Wpisz nowy adres URL hiperłącza', enterNewLinkURL: 'Wpisz nowy adres URL hiperłącza',
errorLoadingSVG: 'Błąd: Nie można załadować danych SVG', errorLoadingSVG: 'Błąd: Nie można załadować danych SVG',
URLloadFail: 'Nie można załadować z adresu URL', URLLoadFail: 'Nie można załadować z adresu URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -205,7 +205,7 @@ export default {
unsavedChanges: 'Existem alterações não salvas.', unsavedChanges: 'Existem alterações não salvas.',
enterNewLinkURL: 'Insira novo URL do hyperlink', enterNewLinkURL: 'Insira novo URL do hyperlink',
errorLoadingSVG: 'Erro: Impossível carregar dados SVG', errorLoadingSVG: 'Erro: Impossível carregar dados SVG',
URLloadFail: 'Impossível carregar deste URL', URLLoadFail: 'Impossível carregar deste URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -205,7 +205,7 @@ export default {
unsavedChanges: 'Sunt schimbări nesalvate.', unsavedChanges: 'Sunt schimbări nesalvate.',
enterNewLinkURL: 'IntroduAliniere în raport cu ...sceţi noul URL', enterNewLinkURL: 'IntroduAliniere în raport cu ...sceţi noul URL',
errorLoadingSVG: 'Eroare: Nu se pot încărca datele SVG', errorLoadingSVG: 'Eroare: Nu se pot încărca datele SVG',
URLloadFail: 'Nu se poate încărca de la URL', URLLoadFail: 'Nu se poate încărca de la URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'Sú tu neuložené zmeny.', unsavedChanges: 'Sú tu neuložené zmeny.',
enterNewLinkURL: 'Zadajte nové URL odkazu (hyperlink)', enterNewLinkURL: 'Zadajte nové URL odkazu (hyperlink)',
errorLoadingSVG: 'Chyba: Nedajú sa načítať SVG data', errorLoadingSVG: 'Chyba: Nedajú sa načítať SVG data',
URLloadFail: 'Nemožno čítať z URL', URLLoadFail: 'Nemožno čítať z URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -205,7 +205,7 @@ export default {
unsavedChanges: 'Obstajajo neshranjene spremembe.', unsavedChanges: 'Obstajajo neshranjene spremembe.',
enterNewLinkURL: 'Vnesite novo URL povezavo', enterNewLinkURL: 'Vnesite novo URL povezavo',
errorLoadingSVG: 'Napaka: Ne morem naložiti SVG podatkov', errorLoadingSVG: 'Napaka: Ne morem naložiti SVG podatkov',
URLloadFail: 'Ne morem naložiti z URL', URLLoadFail: 'Ne morem naložiti z URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: '存在未保存的修改.', unsavedChanges: '存在未保存的修改.',
enterNewLinkURL: '输入新建链接的URL地址', enterNewLinkURL: '输入新建链接的URL地址',
errorLoadingSVG: '错误: 无法加载SVG数据', errorLoadingSVG: '错误: 无法加载SVG数据',
URLloadFail: '无法从URL中加载', URLLoadFail: '无法从URL中加载',
retrieving: '检索 \'%s\'...', retrieving: '检索 \'%s\'...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.', unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL', enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data', errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL', URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...', retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser', popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred', exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

@@ -223,7 +223,7 @@ export const readLang = async function (langData) {
image_width: properties.image_width, image_width: properties.image_width,
layer_delete: layers.del, layer_delete: layers.del,
layer_down: layers.move_down, layer_down: layers.move_down,
layer_new: layers['new'], layer_new: layers.new,
layer_rename: layers.rename, layer_rename: layers.rename,
layer_moreopts: common.more_opts, layer_moreopts: common.more_opts,
layer_up: layers.move_up, layer_up: layers.move_up,

View File

@@ -21,6 +21,7 @@
import {NS} from './namespaces.js'; import {NS} from './namespaces.js';
import {getTransformList} from './svgtransformlist.js'; import {getTransformList} from './svgtransformlist.js';
import {isNullish} from './utilities.js';
// Constants // Constants
const NEAR_ZERO = 1e-14; const NEAR_ZERO = 1e-14;
@@ -151,14 +152,14 @@ export const transformBox = function (l, t, w, h, m) {
* @returns {SVGTransform} A single matrix transform object * @returns {SVGTransform} A single matrix transform object
*/ */
export const transformListToTransform = function (tlist, min, max) { export const transformListToTransform = function (tlist, min, max) {
if (tlist == null) { if (isNullish(tlist)) {
// Or should tlist = null have been prevented before this? // Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix()); return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
} }
min = min || 0; min = min || 0;
max = max || (tlist.numberOfItems - 1); max = max || (tlist.numberOfItems - 1);
min = parseInt(min, 10); min = parseInt(min);
max = parseInt(max, 10); max = parseInt(max);
if (min > max) { const temp = max; max = min; min = temp; } if (min > max) { const temp = max; max = min; min = temp; }
let m = svg.createSVGMatrix(); let m = svg.createSVGMatrix();
for (let i = min; i <= max; ++i) { for (let i = min; i <= max; ++i) {

View File

@@ -18,7 +18,7 @@ import {
} from './math.js'; } from './math.js';
import { import {
assignAttributes, getElem, getRotationAngle, getBBox, assignAttributes, getElem, getRotationAngle, getBBox,
getRefElem, findDefs, snapToGrid, getRefElem, findDefs, snapToGrid, isNullish,
getBBox as utilsGetBBox getBBox as utilsGetBBox
} from './utilities.js'; } from './utilities.js';
import { import {
@@ -77,7 +77,7 @@ export const setLinkControlPoints = function (lcp) {
* @type {null|module:path.Path} * @type {null|module:path.Path}
* @memberof module:path * @memberof module:path
*/ */
export let path = null; export let path = null; // eslint-disable-line import/no-mutable-exports
let editorContext_ = null; let editorContext_ = null;
@@ -240,8 +240,10 @@ export const init = function (editorContext) {
editorContext_ = editorContext; editorContext_ = editorContext;
pathFuncs = [0, 'ClosePath']; pathFuncs = [0, 'ClosePath'];
const pathFuncsStrs = ['Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc', const pathFuncsStrs = [
'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth']; 'Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc',
'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth'
];
$.each(pathFuncsStrs, function (i, s) { $.each(pathFuncsStrs, function (i, s) {
pathFuncs.push(s + 'Abs'); pathFuncs.push(s + 'Abs');
pathFuncs.push(s + 'Rel'); pathFuncs.push(s + 'Rel');
@@ -299,14 +301,14 @@ export const ptObjToArr = function (type, segItem) {
* @returns {module:math.XYObject} * @returns {module:math.XYObject}
*/ */
export const getGripPt = function (seg, altPt) { export const getGripPt = function (seg, altPt) {
const {path} = seg; const {path: pth} = seg;
let out = { let out = {
x: altPt ? altPt.x : seg.item.x, x: altPt ? altPt.x : seg.item.x,
y: altPt ? altPt.y : seg.item.y y: altPt ? altPt.y : seg.item.y
}; };
if (path.matrix) { if (pth.matrix) {
const pt = transformPoint(out.x, out.y, path.matrix); const pt = transformPoint(out.x, out.y, pth.matrix);
out = pt; out = pt;
} }
@@ -320,17 +322,17 @@ export const getGripPt = function (seg, altPt) {
/** /**
* @function module:path.getPointFromGrip * @function module:path.getPointFromGrip
* @param {module:math.XYObject} pt * @param {module:math.XYObject} pt
* @param {module:path.Path} path * @param {module:path.Path} pth
* @returns {module:math.XYObject} * @returns {module:math.XYObject}
*/ */
export const getPointFromGrip = function (pt, path) { export const getPointFromGrip = function (pt, pth) {
const out = { const out = {
x: pt.x, x: pt.x,
y: pt.y y: pt.y
}; };
if (path.matrix) { if (pth.matrix) {
pt = transformPoint(out.x, out.y, path.imatrix); pt = transformPoint(out.x, out.y, pth.imatrix);
out.x = pt.x; out.x = pt.x;
out.y = pt.y; out.y = pt.y;
} }
@@ -400,8 +402,8 @@ export const addPointGrip = function (index, x, y) {
export const getGripContainer = function () { export const getGripContainer = function () {
let c = getElem('pathpointgrip_container'); let c = getElem('pathpointgrip_container');
if (!c) { if (!c) {
const parent = getElem('selectorParentGroup'); const parentElement = getElem('selectorParentGroup');
c = parent.appendChild(document.createElementNS(NS.SVG, 'g')); c = parentElement.appendChild(document.createElementNS(NS.SVG, 'g'));
c.id = 'pathpointgrip_container'; c.id = 'pathpointgrip_container';
} }
return c; return c;
@@ -539,7 +541,7 @@ export const replacePathSeg = function (type, index, pts, elem) {
const pth = elem || path.elem; const pth = elem || path.elem;
const func = 'createSVGPathSeg' + pathFuncs[type]; const func = 'createSVGPathSeg' + pathFuncs[type];
const seg = pth[func].apply(pth, pts); const seg = pth[func](...pts);
if (supportsPathReplaceItem()) { if (supportsPathReplaceItem()) {
pth.pathSegList.replaceItem(seg, index); pth.pathSegList.replaceItem(seg, index);
@@ -600,9 +602,9 @@ export const getSegSelector = function (seg, update) {
const pts = ptObjToArr(seg.type, seg.item); // , true); const pts = ptObjToArr(seg.type, seg.item); // , true);
for (let i = 0; i < pts.length; i += 2) { for (let i = 0; i < pts.length; i += 2) {
const pt = getGripPt(seg, {x: pts[i], y: pts[i + 1]}); const point = getGripPt(seg, {x: pts[i], y: pts[i + 1]});
pts[i] = pt.x; pts[i] = point.x;
pts[i + 1] = pt.y; pts[i + 1] = point.y;
} }
replacePathSeg(seg.type, 1, pts, segLine); replacePathSeg(seg.type, 1, pts, segLine);
@@ -691,7 +693,7 @@ export class Segment {
*/ */
showCtrlPts (y) { showCtrlPts (y) {
for (const i in this.ctrlpts) { for (const i in this.ctrlpts) {
if (this.ctrlpts.hasOwnProperty(i)) { if ({}.hasOwnProperty.call(this.ctrlpts, i)) {
this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none'); this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none');
} }
} }
@@ -777,7 +779,8 @@ export class Segment {
const {item} = this; const {item} = this;
const curPts = this.ctrlpts const curPts = this.ctrlpts
? [item.x += dx, item.y += dy, ? [
item.x += dx, item.y += dy,
item.x1, item.y1, item.x2 += dx, item.y2 += dy item.x1, item.y1, item.x2 += dx, item.y2 += dy
] ]
: [item.x += dx, item.y += dy]; : [item.x += dx, item.y += dy];
@@ -786,16 +789,18 @@ export class Segment {
if (this.next && this.next.ctrlpts) { if (this.next && this.next.ctrlpts) {
const next = this.next.item; const next = this.next.item;
const nextPts = [next.x, next.y, const nextPts = [
next.x1 += dx, next.y1 += dy, next.x2, next.y2]; next.x, next.y,
next.x1 += dx, next.y1 += dy, next.x2, next.y2
];
replacePathSeg(this.next.type, this.next.index, nextPts); replacePathSeg(this.next.type, this.next.index, nextPts);
} }
if (this.mate) { if (this.mate) {
// The last point of a closed subpath has a 'mate', // The last point of a closed subpath has a 'mate',
// which is the 'M' segment of the subpath // which is the 'M' segment of the subpath
const {item} = this.mate; const {item: itm} = this.mate;
const pts = [item.x += dx, item.y += dy]; const pts = [itm.x += dx, itm.y += dy];
replacePathSeg(this.mate.type, this.mate.index, pts); replacePathSeg(this.mate.type, this.mate.index, pts);
// Has no grip, so does not need 'updating'? // Has no grip, so does not need 'updating'?
} }
@@ -826,9 +831,11 @@ export class Segment {
item['x' + anum] = pt.x + (pt.x - this.item['x' + num]); item['x' + anum] = pt.x + (pt.x - this.item['x' + num]);
item['y' + anum] = pt.y + (pt.y - this.item['y' + num]); item['y' + anum] = pt.y + (pt.y - this.item['y' + num]);
const pts = [item.x, item.y, const pts = [
item.x, item.y,
item.x1, item.y1, item.x1, item.y1,
item.x2, item.y2]; item.x2, item.y2
];
replacePathSeg(seg.type, seg.index, pts); replacePathSeg(seg.type, seg.index, pts);
seg.update(true); seg.update(true);
@@ -845,8 +852,10 @@ export class Segment {
item['x' + num] += dx; item['x' + num] += dx;
item['y' + num] += dy; item['y' + num] += dy;
const pts = [item.x, item.y, const pts = [
item.x1, item.y1, item.x2, item.y2]; item.x, item.y,
item.x1, item.y1, item.x2, item.y2
];
replacePathSeg(this.type, this.index, pts); replacePathSeg(this.type, this.index, pts);
this.update(true); this.update(true);
@@ -883,7 +892,7 @@ export class Path {
this.elem = elem; this.elem = elem;
this.segs = []; this.segs = [];
this.selected_pts = []; this.selected_pts = [];
path = this; path = this; // eslint-disable-line consistent-this
this.init(); this.init();
} }
@@ -941,7 +950,7 @@ export class Path {
seg.next.prev = seg; seg.next.prev = seg;
seg.mate = segs[startI]; seg.mate = segs[startI];
seg.addGrip(); seg.addGrip();
if (this.first_seg == null) { if (isNullish(this.first_seg)) {
this.first_seg = seg; this.first_seg = seg;
} }
} else if (!nextSeg) { } else if (!nextSeg) {
@@ -977,7 +986,7 @@ export class Path {
* @callback module:path.PathEachSegCallback * @callback module:path.PathEachSegCallback
* @this module:path.Segment * @this module:path.Segment
* @param {Integer} i The index of the seg being iterated * @param {Integer} i The index of the seg being iterated
* @returns {boolean} Will stop execution of `eachSeg` if returns `false` * @returns {boolean|undefined} Will stop execution of `eachSeg` if returns `false`
*/ */
/** /**
* @param {module:path.PathEachSegCallback} fn * @param {module:path.PathEachSegCallback} fn
@@ -1062,29 +1071,6 @@ export class Path {
} }
} }
/**
* @param {Integer} index
* @returns {boolean}
*/
subpathIsClosed (index) {
let closed = false;
// Check if subpath is already open
path.eachSeg(function (i) {
if (i <= index) { return true; }
if (this.type === 2) {
// Found M first, so open
return false;
}
if (this.type === 1) {
// Found Z first, so closed
closed = true;
return false;
}
});
return closed;
}
/** /**
* @param {Integer} index * @param {Integer} index
* @returns {undefined} * @returns {undefined}
@@ -1235,7 +1221,7 @@ export class Path {
*/ */
selectPt (pt, ctrlNum) { selectPt (pt, ctrlNum) {
this.clearSelection(); this.clearSelection();
if (pt == null) { if (isNullish(pt)) {
this.eachSeg(function (i) { this.eachSeg(function (i) {
// 'this' is the segment here. // 'this' is the segment here.
if (this.prev) { if (this.prev) {
@@ -1312,11 +1298,35 @@ export class Path {
grips[i] = seg.ptgrip; grips[i] = seg.ptgrip;
} }
const closedSubpath = this.subpathIsClosed(this.selected_pts[0]); const closedSubpath = Path.subpathIsClosed(this.selected_pts[0]);
editorContext_.addPtsToSelection({grips, closedSubpath}); editorContext_.addPtsToSelection({grips, closedSubpath});
} }
} }
/**
* @param {Integer} index
* @returns {boolean}
*/
Path.subpathIsClosed = function (index) {
let clsd = false;
// Check if subpath is already open
path.eachSeg(function (i) {
if (i <= index) { return true; }
if (this.type === 2) {
// Found M first, so open
return false;
}
if (this.type === 1) {
// Found Z first, so closed
clsd = true;
return false;
}
return true;
});
return clsd;
};
/** /**
* @function module:path.getPath_ * @function module:path.getPath_
* @param {SVGPathElement} elem * @param {SVGPathElement} elem
@@ -1408,7 +1418,7 @@ export const recalcRotatedPath = function () {
const rvals = getRotVals(seg.x, seg.y), const rvals = getRotVals(seg.x, seg.y),
points = [rvals.x, rvals.y]; points = [rvals.x, rvals.y];
if (seg.x1 != null && seg.x2 != null) { if (!isNullish(seg.x1) && !isNullish(seg.x2)) {
const cVals1 = getRotVals(seg.x1, seg.y1); const cVals1 = getRotVals(seg.x1, seg.y1);
const cVals2 = getRotVals(seg.x2, seg.y2); const cVals2 = getRotVals(seg.x2, seg.y2);
points.splice(points.length, 0, cVals1.x, cVals1.y, cVals2.x, cVals2.y); points.splice(points.length, 0, cVals1.x, cVals1.y, cVals2.x, cVals2.y);
@@ -1492,26 +1502,28 @@ export const reorientGrads = function (elem, m) {
* @name module:path.pathMap * @name module:path.pathMap
* @type {GenericArray} * @type {GenericArray}
*/ */
const pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', const pathMap = [
'H', 'h', 'V', 'v', 'S', 's', 'T', 't']; 0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
];
/** /**
* Convert a path to one with only absolute or relative values. * Convert a path to one with only absolute or relative values.
* @todo move to pathActions.js * @todo move to pathActions.js
* @function module:path.convertPath * @function module:path.convertPath
* @param {SVGPathElement} path - the path to convert * @param {SVGPathElement} pth - the path to convert
* @param {boolean} toRel - true of convert to relative * @param {boolean} toRel - true of convert to relative
* @returns {string} * @returns {string}
*/ */
export const convertPath = function (path, toRel) { export const convertPath = function (pth, toRel) {
const segList = path.pathSegList; const {pathSegList} = pth;
const len = segList.numberOfItems; const len = pathSegList.numberOfItems;
let curx = 0, cury = 0; let curx = 0, cury = 0;
let d = ''; let d = '';
let lastM = null; let lastM = null;
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
const seg = segList.getItem(i); const seg = pathSegList.getItem(i);
// if these properties are not in the segment, set them to zero // if these properties are not in the segment, set them to zero
let x = seg.x || 0, let x = seg.x || 0,
y = seg.y || 0, y = seg.y || 0,
@@ -1776,7 +1788,7 @@ export const pathActions = (function () {
* @param {Element} mouseTarget * @param {Element} mouseTarget
* @param {Float} startX * @param {Float} startX
* @param {Float} startY * @param {Float} startY
* @returns {undefined} * @returns {boolean|undefined}
*/ */
mouseDown (evt, mouseTarget, startX, startY) { mouseDown (evt, mouseTarget, startX, startY) {
let id; let id;
@@ -1922,7 +1934,7 @@ export const pathActions = (function () {
editorContext_.getMouseTarget(evt) editorContext_.getMouseTarget(evt)
)) { )) {
// Clicked outside canvas, so don't make point // Clicked outside canvas, so don't make point
console.log('Clicked outside canvas'); // console.log('Clicked outside canvas');
return false; return false;
} }
@@ -1967,11 +1979,11 @@ export const pathActions = (function () {
// keep = true; // keep = true;
} }
return; return undefined;
} }
// TODO: Make sure currentPath isn't null at this point // TODO: Make sure currentPath isn't null at this point
if (!path) { return; } if (!path) { return undefined; }
path.storeD(); path.storeD();
@@ -1979,7 +1991,7 @@ export const pathActions = (function () {
let curPt; let curPt;
if (id.substr(0, 14) === 'pathpointgrip_') { if (id.substr(0, 14) === 'pathpointgrip_') {
// Select this point // Select this point
curPt = path.cur_pt = parseInt(id.substr(14), 10); curPt = path.cur_pt = parseInt(id.substr(14));
path.dragging = [startX, startY]; path.dragging = [startX, startY];
const seg = path.segs[curPt]; const seg = path.segs[curPt];
@@ -2007,7 +2019,7 @@ export const pathActions = (function () {
// Start selection box // Start selection box
if (!path.dragging) { if (!path.dragging) {
let rubberBox = editorContext_.getRubberBox(); let rubberBox = editorContext_.getRubberBox();
if (rubberBox == null) { if (isNullish(rubberBox)) {
rubberBox = editorContext_.setRubberBox( rubberBox = editorContext_.setRubberBox(
editorContext_.selectorManager.getRubberBandBox() editorContext_.selectorManager.getRubberBandBox()
); );
@@ -2021,6 +2033,7 @@ export const pathActions = (function () {
display: 'inline' display: 'inline'
}, 100); }, 100);
} }
return undefined;
}, },
/** /**
* @param {Float} mouseX * @param {Float} mouseX
@@ -2127,7 +2140,7 @@ export const pathActions = (function () {
} else { } else {
path.selected_pts = []; path.selected_pts = [];
path.eachSeg(function (i) { path.eachSeg(function (i) {
const seg = this; const seg = this; // eslint-disable-line consistent-this
if (!seg.next && !seg.prev) { return; } if (!seg.next && !seg.prev) { return; }
// const {item} = seg; // const {item} = seg;
@@ -2150,12 +2163,18 @@ export const pathActions = (function () {
}); });
} }
}, },
/**
* @typedef module:path.keepElement
* @type {PlainObject}
* @property {boolean} keep
* @property {Element} element
*/
/** /**
* @param {Event} evt * @param {Event} evt
* @param {Element} element * @param {Element} element
* @param {Float} mouseX * @param {Float} mouseX
* @param {Float} mouseY * @param {Float} mouseY
* @returns {undefined} * @returns {module:path.keepElement|undefined}
*/ */
mouseUp (evt, element, mouseX, mouseY) { mouseUp (evt, element, mouseX, mouseY) {
const drawnPath = editorContext_.getDrawnPath(); const drawnPath = editorContext_.getDrawnPath();
@@ -2203,6 +2222,7 @@ export const pathActions = (function () {
pathActions.toSelectMode(evt.target); pathActions.toSelectMode(evt.target);
} }
hasMoved = false; hasMoved = false;
return undefined;
}, },
/** /**
* @param {Element} element * @param {Element} element
@@ -2273,8 +2293,8 @@ export const pathActions = (function () {
reorient () { reorient () {
const elem = editorContext_.getSelectedElements()[0]; const elem = editorContext_.getSelectedElements()[0];
if (!elem) { return; } if (!elem) { return; }
const angle = getRotationAngle(elem); const angl = getRotationAngle(elem);
if (angle === 0) { return; } if (angl === 0) { return; }
const batchCmd = new BatchCommand('Reorient path'); const batchCmd = new BatchCommand('Reorient path');
const changes = { const changes = {
@@ -2321,7 +2341,7 @@ export const pathActions = (function () {
* @returns {false|undefined} * @returns {false|undefined}
*/ */
resetOrientation (pth) { resetOrientation (pth) {
if (pth == null || pth.nodeName !== 'path') { return false; } if (isNullish(pth) || pth.nodeName !== 'path') { return false; }
const tlist = getTransformList(pth); const tlist = getTransformList(pth);
const m = transformListToTransform(tlist).matrix; const m = transformListToTransform(tlist).matrix;
tlist.clear(); tlist.clear();
@@ -2357,6 +2377,7 @@ export const pathActions = (function () {
} }
reorientGrads(pth, m); reorientGrads(pth, m);
return undefined;
}, },
/** /**
* @returns {undefined} * @returns {undefined}
@@ -2449,9 +2470,10 @@ export const pathActions = (function () {
openPt = false; openPt = false;
return false; return false;
} }
return true;
}); });
if (openPt == null) { if (isNullish(openPt)) {
// Single path, so close last seg // Single path, so close last seg
openPt = path.segs.length - 1; openPt = path.segs.length - 1;
} }

View File

@@ -5,12 +5,12 @@
* @license MIT * @license MIT
*/ */
import jqPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
import {NS} from './namespaces.js'; import {NS} from './namespaces.js';
import {convertToNum} from './units.js'; import {convertToNum} from './units.js';
import {isWebkit} from './browser.js'; import {isWebkit} from './browser.js';
import {getTransformList} from './svgtransformlist.js'; import {getTransformList} from './svgtransformlist.js';
import {getRotationAngle, getHref, getBBox, getRefElem} from './utilities.js'; import {getRotationAngle, getHref, getBBox, getRefElem, isNullish} from './utilities.js';
import {BatchCommand, ChangeElementCommand} from './history.js'; import {BatchCommand, ChangeElementCommand} from './history.js';
import {remapElement} from './coords.js'; import {remapElement} from './coords.js';
import { import {
@@ -18,7 +18,7 @@ import {
hasMatrixTransform hasMatrixTransform
} from './math.js'; } from './math.js';
const $ = jqPluginSVG(jQuery); const $ = jQueryPluginSVG(jQuery);
let context_; let context_;
@@ -75,7 +75,7 @@ export const updateClipPath = function (attr, tx, ty) {
* @returns {Command} Undo command object with the resulting change * @returns {Command} Undo command object with the resulting change
*/ */
export const recalculateDimensions = function (selected) { export const recalculateDimensions = function (selected) {
if (selected == null) { return null; } if (isNullish(selected)) { return null; }
// Firefox Issue - 1081 // Firefox Issue - 1081
if (selected.nodeName === 'svg' && navigator.userAgent.includes('Firefox/20')) { if (selected.nodeName === 'svg' && navigator.userAgent.includes('Firefox/20')) {
@@ -154,7 +154,8 @@ export const recalculateDimensions = function (selected) {
const m = matrixMultiply( const m = matrixMultiply(
tlist.getItem(k - 2).matrix, tlist.getItem(k - 2).matrix,
tlist.getItem(k - 1).matrix); tlist.getItem(k - 1).matrix
);
mt.setMatrix(m); mt.setMatrix(m);
tlist.removeItem(k - 2); tlist.removeItem(k - 2);
tlist.removeItem(k - 2); tlist.removeItem(k - 2);
@@ -241,7 +242,7 @@ export const recalculateDimensions = function (selected) {
// if we haven't created an initial array in polygon/polyline/path, then // if we haven't created an initial array in polygon/polyline/path, then
// make a copy of initial values and include the transform // make a copy of initial values and include the transform
if (initial == null) { if (isNullish(initial)) {
initial = $.extend(true, {}, changes); initial = $.extend(true, {}, changes);
$.each(initial, function (attr, val) { $.each(initial, function (attr, val) {
initial[attr] = convertToNum(attr, val); initial[attr] = convertToNum(attr, val);
@@ -795,15 +796,15 @@ export const recalculateDimensions = function (selected) {
// translation required to re-center it // translation required to re-center it
// Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M] // Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
} else if (operation === 3 && angle) { } else if (operation === 3 && angle) {
const m = transformListToTransform(tlist).matrix; const {matrix} = transformListToTransform(tlist);
const roldt = svgroot.createSVGTransform(); const roldt = svgroot.createSVGTransform();
roldt.setRotate(angle, oldcenter.x, oldcenter.y); roldt.setRotate(angle, oldcenter.x, oldcenter.y);
const rold = roldt.matrix; const rold = roldt.matrix;
const rnew = svgroot.createSVGTransform(); const rnew = svgroot.createSVGTransform();
rnew.setRotate(angle, newcenter.x, newcenter.y); rnew.setRotate(angle, newcenter.x, newcenter.y);
const rnewInv = rnew.matrix.inverse(); const rnewInv = rnew.matrix.inverse();
const mInv = m.inverse(); const mInv = matrix.inverse();
const extrat = matrixMultiply(mInv, rnewInv, rold, m); const extrat = matrixMultiply(mInv, rnewInv, rold, matrix);
remapElement(selected, changes, extrat); remapElement(selected, changes, extrat);
if (angle) { if (angle) {

View File

@@ -1,4 +1,5 @@
import {supportsSvg} from './browser.js'; import {supportsSvg} from './browser.js';
if (!supportsSvg()) { if (!supportsSvg()) {
window.location = 'browser-not-supported.html'; window.location = 'browser-not-supported.html';
} }

View File

@@ -143,7 +143,7 @@ export const sanitizeSvg = function (node) {
const attrNsURI = attr.namespaceURI; const attrNsURI = attr.namespaceURI;
// Check that an attribute with the correct localName in the correct namespace is on // Check that an attribute with the correct localName in the correct namespace is on
// our whitelist or is a namespace declaration for one of our allowed namespaces // our whitelist or is a namespace declaration for one of our allowed namespaces
if (!(allowedAttrsNS.hasOwnProperty(attrLocalName) && attrNsURI === allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS) && if (!({}.hasOwnProperty.call(allowedAttrsNS, attrLocalName) && attrNsURI === allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS) &&
!(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) { !(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) {
// TODO(codedread): Programmatically add the se: attributes to the NS-aware whitelist. // TODO(codedread): Programmatically add the se: attributes to the NS-aware whitelist.
// Bypassing the whitelist to allow se: prefixes. // Bypassing the whitelist to allow se: prefixes.
@@ -159,11 +159,12 @@ export const sanitizeSvg = function (node) {
switch (attrName) { switch (attrName) {
case 'transform': case 'transform':
case 'gradientTransform': case 'gradientTransform':
case 'patternTransform': case 'patternTransform': {
const val = attr.value.replace(/(\d)-/g, '$1 -'); const val = attr.value.replace(/(\d)-/g, '$1 -');
node.setAttribute(attrName, val); node.setAttribute(attrName, val);
break; break;
} }
}
} }
// For the style attribute, rewrite it in terms of XML presentational attributes // For the style attribute, rewrite it in terms of XML presentational attributes

View File

@@ -8,7 +8,7 @@
*/ */
import {isTouch, isWebkit} from './browser.js'; // , isOpera import {isTouch, isWebkit} from './browser.js'; // , isOpera
import {getRotationAngle, getBBox, getStrokedBBox} from './utilities.js'; import {getRotationAngle, getBBox, getStrokedBBox, isNullish} from './utilities.js';
import {transformListToTransform, transformBox, transformPoint} from './math.js'; import {transformListToTransform, transformBox, transformPoint} from './math.js';
import {getTransformList} from './svgtransformlist.js'; import {getTransformList} from './svgtransformlist.js';
@@ -88,30 +88,6 @@ export class Selector {
this.selectorGroup.setAttribute('display', 'inline'); this.selectorGroup.setAttribute('display', 'inline');
} }
/**
* Updates cursors for corner grips on rotation so arrows point the right way.
* @param {Float} angle - Current rotation angle in degrees
* @returns {undefined}
*/
updateGripCursors (angle) {
let dir;
const dirArr = [];
let steps = Math.round(angle / 45);
if (steps < 0) { steps += 8; }
for (dir in selectorManager_.selectorGrips) {
dirArr.push(dir);
}
while (steps > 0) {
dirArr.push(dirArr.shift());
steps--;
}
let i = 0;
for (dir in selectorManager_.selectorGrips) {
selectorManager_.selectorGrips[dir].setAttribute('style', ('cursor:' + dirArr[i] + '-resize'));
i++;
}
}
/** /**
* Show the resize grips of this selector. * Show the resize grips of this selector.
* @param {boolean} show - Indicates whether grips should be shown or not * @param {boolean} show - Indicates whether grips should be shown or not
@@ -124,7 +100,7 @@ export class Selector {
this.hasGrips = show; this.hasGrips = show;
if (elem && show) { if (elem && show) {
this.selectorGroup.append(selectorManager_.selectorGripsGroup); this.selectorGroup.append(selectorManager_.selectorGripsGroup);
this.updateGripCursors(getRotationAngle(elem)); Selector.updateGripCursors(getRotationAngle(elem));
} }
} }
@@ -245,11 +221,10 @@ export class Selector {
e: [nbax + nbaw, nbay + (nbah) / 2], e: [nbax + nbaw, nbay + (nbah) / 2],
s: [nbax + (nbaw) / 2, nbay + nbah] s: [nbax + (nbaw) / 2, nbay + nbah]
}; };
for (const dir in this.gripCoords) { Object.entries(this.gripCoords).forEach(([dir, coords]) => {
const coords = this.gripCoords[dir];
selectedGrips[dir].setAttribute('cx', coords[0]); selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]); selectedGrips[dir].setAttribute('cy', coords[1]);
} });
// we want to go 20 pixels in the negative transformed y direction, ignoring scale // we want to go 20 pixels in the negative transformed y direction, ignoring scale
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2); mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2);
@@ -262,6 +237,23 @@ export class Selector {
// } // }
} }
} }
/**
* Updates cursors for corner grips on rotation so arrows point the right way.
* @param {Float} angle - Current rotation angle in degrees
* @returns {undefined}
*/
Selector.updateGripCursors = function (angle) {
const dirArr = Object.keys(selectorManager_.selectorGrips);
let steps = Math.round(angle / 45);
if (steps < 0) { steps += 8; }
while (steps > 0) {
dirArr.push(dirArr.shift());
steps--;
}
Object.values(selectorManager_.selectorGrips).forEach((gripElement, i) => {
gripElement.setAttribute('style', ('cursor:' + dirArr[i] + '-resize'));
});
};
/** /**
* Manage all selector objects (selection boxes). * Manage all selector objects (selection boxes).
@@ -326,7 +318,7 @@ export class SelectorManager {
this.rubberBandBox = null; this.rubberBandBox = null;
// add the corner grips // add the corner grips
for (const dir in this.selectorGrips) { Object.keys(this.selectorGrips).forEach((dir) => {
const grip = svgFactory_.createSVGElement({ const grip = svgFactory_.createSVGElement({
element: 'circle', element: 'circle',
attr: { attr: {
@@ -346,7 +338,7 @@ export class SelectorManager {
$.data(grip, 'dir', dir); $.data(grip, 'dir', dir);
$.data(grip, 'type', 'resize'); $.data(grip, 'type', 'resize');
this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip); this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip);
} });
// add rotator elems // add rotator elems
this.rotateGripConnector = this.selectorGripsGroup.appendChild( this.rotateGripConnector = this.selectorGripsGroup.appendChild(
@@ -420,7 +412,7 @@ export class SelectorManager {
* @returns {Selector} The selector based on the given element * @returns {Selector} The selector based on the given element
*/ */
requestSelector (elem, bbox) { requestSelector (elem, bbox) {
if (elem == null) { return null; } if (isNullish(elem)) { return null; }
const N = this.selectors.length; const N = this.selectors.length;
// If we've already acquired one for this element, return it. // If we've already acquired one for this element, return it.
@@ -450,12 +442,12 @@ export class SelectorManager {
* @returns {undefined} * @returns {undefined}
*/ */
releaseSelector (elem) { releaseSelector (elem) {
if (elem == null) { return; } if (isNullish(elem)) { return; }
const N = this.selectors.length, const N = this.selectors.length,
sel = this.selectorMap[elem.id]; sel = this.selectorMap[elem.id];
if (!sel.locked) { if (!sel.locked) {
// TODO(codedread): Ensure this exists in this module. // TODO(codedread): Ensure this exists in this module.
console.log('WARNING! selector was released but was already unlocked'); console.log('WARNING! selector was released but was already unlocked'); // eslint-disable-line no-console
} }
for (let i = 0; i < N; ++i) { for (let i = 0; i < N; ++i) {
if (this.selectors[i] && this.selectors[i] === sel) { if (this.selectors[i] && this.selectors[i] === sel) {

View File

@@ -73,7 +73,7 @@
* @param {external:jQuery} $ The jQuery object to which to add the plug-in * @param {external:jQuery} $ The jQuery object to which to add the plug-in
* @returns {external:jQuery} * @returns {external:jQuery}
*/ */
export default function ($) { export default function jQueryPluginSpinButton ($) {
if (!$.loadingStylesheets) { if (!$.loadingStylesheets) {
$.loadingStylesheets = []; $.loadingStylesheets = [];
} }

View File

@@ -13,14 +13,14 @@ import {importSetGlobalDefault} from './external/dynamic-import-polyfill/importM
import SvgCanvas from './svgcanvas.js'; import SvgCanvas from './svgcanvas.js';
import Layer from './layer.js'; import Layer from './layer.js';
import jqPluginJSHotkeys from './js-hotkeys/jquery.hotkeys.min.js'; import jQueryPluginJSHotkeys from './js-hotkeys/jquery.hotkeys.min.js';
import jqPluginBBQ from './jquerybbq/jquery.bbq.min.js'; import jQueryPluginBBQ from './jquerybbq/jquery.bbq.min.js';
import jqPluginSVGIcons from './svgicons/jQuery.svgIcons.js'; import jQueryPluginSVGIcons from './svgicons/jQuery.svgIcons.js';
import jqPluginJGraduate from './jgraduate/jQuery.jGraduate.js'; import jQueryPluginJGraduate from './jgraduate/jQuery.jGraduate.js';
import jqPluginSpinBtn from './spinbtn/jQuery.SpinButton.js'; import jQueryPluginSpinButton from './spinbtn/jQuery.SpinButton.js';
import jqPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
import jqPluginContextMenu from './contextmenu/jQuery.contextMenu.js'; import jQueryPluginContextMenu from './contextmenu/jQuery.contextMenu.js';
import jqPluginJPicker from './jgraduate/jQuery.jPicker.js'; import jQueryPluginJPicker from './jgraduate/jQuery.jPicker.js';
import { import {
readLang, putLocale, readLang, putLocale,
setStrings, setStrings,
@@ -46,9 +46,9 @@ import loadStylesheets from './external/load-stylesheets/index-es.js';
const editor = {}; const editor = {};
const $ = [ const $ = [
jqPluginJSHotkeys, jqPluginBBQ, jqPluginSVGIcons, jqPluginJGraduate, jQueryPluginJSHotkeys, jQueryPluginBBQ, jQueryPluginSVGIcons, jQueryPluginJGraduate,
jqPluginSpinBtn, jqPluginSVG, jqPluginContextMenu, jqPluginJPicker jQueryPluginSpinButton, jQueryPluginSVG, jQueryPluginContextMenu, jQueryPluginJPicker
].reduce(($, cb) => cb($), jQuery); ].reduce((jq, func) => func(jq), jQuery);
/* /*
if (!$.loadingStylesheets) { if (!$.loadingStylesheets) {
@@ -337,11 +337,16 @@ function getImportLocale ({defaultLang, defaultName}) {
* @param {string} [localeInfo.lang=defaultLang] Defaults to `defaultLang` 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 importLocale ({name = defaultName, lang = defaultLang} = {}) { return async function importLocaleDefaulting ({name = defaultName, lang = defaultLang} = {}) {
async function importLocale (lang) { /**
const url = `${curConfig.extPath}ext-locale/${name}/${lang}.js`; *
* @param {string} language
* @returns {Promise} Resolves to {@link module:locale.LocaleStrings}
*/
function importLocale (language) {
const url = `${curConfig.extPath}ext-locale/${name}/${language}.js`;
return importSetGlobalDefault(url, { return importSetGlobalDefault(url, {
global: `svgEditorExtensionLocale_${name}_${lang}` global: `svgEditorExtensionLocale_${name}_${language}`
}); });
} }
try { try {
@@ -360,7 +365,7 @@ function getImportLocale ({defaultLang, defaultName}) {
* Store and retrieve preferences. * Store and retrieve preferences.
* @param {string} key The preference name to be retrieved or set * @param {string} key The preference name to be retrieved or set
* @param {string} [val] The value. If the value supplied is missing or falsey, no change to the preference will be made. * @param {string} [val] The value. If the value supplied is missing or falsey, no change to the preference will be made.
* @returns {string} If val is missing or falsey, the value of the previously stored preference will be returned. * @returns {string|undefined} If val is missing or falsey, the value of the previously stored preference will be returned.
* @todo Can we change setting on the jQuery namespace (onto editor) to avoid conflicts? * @todo Can we change setting on the jQuery namespace (onto editor) to avoid conflicts?
* @todo Review whether any remaining existing direct references to * @todo Review whether any remaining existing direct references to
* getting `curPrefs` can be changed to use `$.pref()` getting to ensure * getting `curPrefs` can be changed to use `$.pref()` getting to ensure
@@ -377,7 +382,7 @@ $.pref = function (key, val) {
* @implements {module:SVGEditor.Prefs} * @implements {module:SVGEditor.Prefs}
*/ */
editor.curPrefs = curPrefs; // Update exported value editor.curPrefs = curPrefs; // Update exported value
return; return undefined;
} }
return (key in curPrefs) ? curPrefs[key] : defaultPrefs[key]; return (key in curPrefs) ? curPrefs[key] : defaultPrefs[key];
}; };
@@ -428,22 +433,20 @@ editor.loadContentAndPrefs = function () {
} }
// LOAD PREFS // LOAD PREFS
for (const key in defaultPrefs) { Object.keys(defaultPrefs).forEach((key) => {
if (defaultPrefs.hasOwnProperty(key)) { // It's our own config, so we don't need to iterate up the prototype chain const storeKey = 'svg-edit-' + key;
const storeKey = 'svg-edit-' + key; if (editor.storage) {
if (editor.storage) { const val = editor.storage.getItem(storeKey);
const val = editor.storage.getItem(storeKey); if (val) {
if (val) { defaultPrefs[key] = String(val); // Convert to string for FF (.value fails in Webkit)
defaultPrefs[key] = String(val); // Convert to string for FF (.value fails in Webkit)
}
} else if (window.widget) {
defaultPrefs[key] = window.widget.preferenceForKey(storeKey);
} else {
const result = document.cookie.match(new RegExp('(?:^|;\\s*)' + Utils.regexEscape(encodeURIComponent(storeKey)) + '=([^;]+)'));
defaultPrefs[key] = result ? decodeURIComponent(result[1]) : '';
} }
} else if (window.widget) {
defaultPrefs[key] = window.widget.preferenceForKey(storeKey);
} else {
const result = document.cookie.match(new RegExp('(?:^|;\\s*)' + Utils.regexEscape(encodeURIComponent(storeKey)) + '=([^;]+)'));
defaultPrefs[key] = result ? decodeURIComponent(result[1]) : '';
} }
} });
}; };
/** /**
@@ -476,12 +479,12 @@ editor.setConfig = function (opts, cfgCfg) {
} }
} }
$.each(opts, function (key, val) { $.each(opts, function (key, val) {
if (opts.hasOwnProperty(key)) { if ({}.hasOwnProperty.call(opts, key)) {
// Only allow prefs defined in defaultPrefs // Only allow prefs defined in defaultPrefs
if (defaultPrefs.hasOwnProperty(key)) { if ({}.hasOwnProperty.call(defaultPrefs, key)) {
if (cfgCfg.overwrite === false && ( if (cfgCfg.overwrite === false && (
curConfig.preventAllURLConfig || curConfig.preventAllURLConfig ||
curPrefs.hasOwnProperty(key) {}.hasOwnProperty.call(curPrefs, key)
)) { )) {
return; return;
} }
@@ -502,30 +505,26 @@ editor.setConfig = function (opts, cfgCfg) {
} }
curConfig[key] = curConfig[key].concat(val); // We will handle any dupes later curConfig[key] = curConfig[key].concat(val); // We will handle any dupes later
// Only allow other curConfig if defined in defaultConfig // Only allow other curConfig if defined in defaultConfig
} else if (defaultConfig.hasOwnProperty(key)) { } else if ({}.hasOwnProperty.call(defaultConfig, key)) {
if (cfgCfg.overwrite === false && ( if (cfgCfg.overwrite === false && (
curConfig.preventAllURLConfig || curConfig.preventAllURLConfig ||
curConfig.hasOwnProperty(key) {}.hasOwnProperty.call(curConfig, key)
)) { )) {
return; return;
} }
// Potentially overwriting of previously set config // Potentially overwriting of previously set config
if (curConfig.hasOwnProperty(key)) { if ({}.hasOwnProperty.call(curConfig, key)) {
if (cfgCfg.overwrite === false) { if (cfgCfg.overwrite === false) {
return; return;
} }
extendOrAdd(curConfig, key, val); extendOrAdd(curConfig, key, val);
} else if (cfgCfg.allowInitialUserOverride === true) {
extendOrAdd(defaultConfig, key, val);
} else if (defaultConfig[key] && typeof defaultConfig[key] === 'object') {
curConfig[key] = {};
$.extend(true, curConfig[key], val); // Merge properties recursively, e.g., on initFill, initStroke objects
} else { } else {
if (cfgCfg.allowInitialUserOverride === true) { curConfig[key] = val;
extendOrAdd(defaultConfig, key, val);
} else {
if (defaultConfig[key] && typeof defaultConfig[key] === 'object') {
curConfig[key] = {};
$.extend(true, curConfig[key], val); // Merge properties recursively, e.g., on initFill, initStroke objects
} else {
curConfig[key] = val;
}
}
} }
} }
} }
@@ -781,7 +780,7 @@ editor.init = function () {
curConfig.extensions.map(async (extname) => { curConfig.extensions.map(async (extname) => {
const extName = extname.match(/^ext-(.+)\.js/); const extName = extname.match(/^ext-(.+)\.js/);
if (!extName) { // Ensure URL cannot specify some other unintended file in the extPath if (!extName) { // Ensure URL cannot specify some other unintended file in the extPath
return; return undefined;
} }
const url = curConfig.extPath + extname; const url = curConfig.extPath + extname;
// Todo: Replace this with `return import(url);` when // Todo: Replace this with `return import(url);` when
@@ -803,12 +802,15 @@ editor.init = function () {
const importLocale = getImportLocale({defaultLang: langParam, defaultName: name}); const importLocale = getImportLocale({defaultLang: langParam, defaultName: name});
return editor.addExtension(name, (init && init.bind(editor)), importLocale); return editor.addExtension(name, (init && init.bind(editor)), importLocale);
} catch (err) { } catch (err) {
console.log(err); // Todo: Add config to alert any errors
console.error('Extension failed to load: ' + extname + '; ' + err); console.log(err); // eslint-disable-line no-console
console.error('Extension failed to load: ' + extname + '; ' + err); // eslint-disable-line no-console
return undefined;
} }
}) })
); );
svgCanvas.bind('extensions_added', svgCanvas.bind(
'extensions_added',
/** /**
* @param {external:Window} win * @param {external:Window} win
* @param {module:svgcanvas.SvgCanvas#event:extensions_added} data * @param {module:svgcanvas.SvgCanvas#event:extensions_added} data
@@ -888,8 +890,8 @@ editor.init = function () {
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
const s = sides[i]; const s = sides[i];
let cur = el.data('orig_margin-' + s); let cur = el.data('orig_margin-' + s);
if (cur == null) { if (Utils.isNullish(cur)) {
cur = parseInt(el.css('margin-' + s), 10); cur = parseInt(el.css('margin-' + s));
// Cache the original margin // Cache the original margin
el.data('orig_margin-' + s, cur); el.data('orig_margin-' + s, cur);
} }
@@ -1336,8 +1338,14 @@ editor.init = function () {
} }
}); });
function getStylesheetPriority (stylesheet) { /**
switch (stylesheet) { * Since stylesheets may be added out of order, we indicate the desired order
* for defaults and others after them (in an indeterminate order).
* @param {string} stylesheetFile
* @returns {Integer|PositiveInfinity}
*/
function getStylesheetPriority (stylesheetFile) {
switch (stylesheetFile) {
case 'jgraduate/css/jPicker.css': case 'jgraduate/css/jPicker.css':
return 1; return 1;
case 'jgraduate/css/jGraduate.css': case 'jgraduate/css/jGraduate.css':
@@ -1366,7 +1374,7 @@ editor.init = function () {
stylesheets.splice(idx, 1, ...$.loadingStylesheets); stylesheets.splice(idx, 1, ...$.loadingStylesheets);
} }
} }
loadStylesheets(stylesheets, {acceptErrors: ({stylesheetURL, reject, resolve}) => { loadStylesheets(stylesheets, {acceptErrors ({stylesheetURL, reject, resolve}) {
if ($.loadingStylesheets.includes(stylesheetURL)) { if ($.loadingStylesheets.includes(stylesheetURL)) {
reject(new Error(`Missing expected stylesheet: ${stylesheetURL}`)); reject(new Error(`Missing expected stylesheet: ${stylesheetURL}`));
return; return;
@@ -1387,7 +1395,8 @@ editor.init = function () {
document.getElementById('svgcanvas'), document.getElementById('svgcanvas'),
curConfig curConfig
); );
const palette = [ // Todo: Make into configuration item? const palette = [
// Todo: Make into configuration item?
'#000000', '#3f3f3f', '#7f7f7f', '#bfbfbf', '#ffffff', '#000000', '#3f3f3f', '#7f7f7f', '#bfbfbf', '#ffffff',
'#ff0000', '#ff7f00', '#ffff00', '#7fff00', '#ff0000', '#ff7f00', '#ffff00', '#7fff00',
'#00ff00', '#00ff7f', '#00ffff', '#007fff', '#00ff00', '#00ff7f', '#00ffff', '#007fff',
@@ -1487,7 +1496,7 @@ editor.init = function () {
if (checkbox.tooltip) { if (checkbox.tooltip) {
label.attr('title', checkbox.tooltip); label.attr('title', checkbox.tooltip);
} }
chkbx.prop('checked', !!checkbox.checked); chkbx.prop('checked', Boolean(checkbox.checked));
div.append($('<div>').append(label)); div.append($('<div>').append(label));
} }
$.each(opts || [], function (opt, val) { $.each(opts || [], function (opt, val) {
@@ -1649,7 +1658,7 @@ editor.init = function () {
editingsource = true; editingsource = true;
origSource = svgCanvas.getSvgString(); origSource = svgCanvas.getSvgString();
$('#save_output_btns').toggle(!!forSaving); $('#save_output_btns').toggle(Boolean(forSaving));
$('#tool_source_back').toggle(!forSaving); $('#tool_source_back').toggle(!forSaving);
$('#svg_source_textarea').val(origSource); $('#svg_source_textarea').val(origSource);
$('#svg_source_editor').fadeIn(); $('#svg_source_editor').fadeIn();
@@ -1931,7 +1940,7 @@ editor.init = function () {
// Create multiple canvases when necessary (due to browser limits) // Create multiple canvases when necessary (due to browser limits)
if (rulerLen >= limit) { if (rulerLen >= limit) {
ctxArrNum = parseInt(rulerLen / limit, 10) + 1; ctxArrNum = parseInt(rulerLen / limit) + 1;
ctxArr = []; ctxArr = [];
ctxArr[0] = ctx; ctxArr[0] = ctx;
let copy; let copy;
@@ -2078,8 +2087,8 @@ editor.init = function () {
const ratio = newCanX / oldCanX; const ratio = newCanX / oldCanX;
const scrollX = w / 2 - wOrig / 2; const scrollX = w / 2 - wOrig / 2; // eslint-disable-line no-shadow
const scrollY = h / 2 - hOrig / 2; const scrollY = h / 2 - hOrig / 2; // eslint-disable-line no-shadow
if (!newCtr) { if (!newCtr) {
const oldDistX = oldCtr.x - oldCanX; const oldDistX = oldCtr.x - oldCanX;
@@ -2127,39 +2136,35 @@ editor.init = function () {
* @returns {undefined} * @returns {undefined}
*/ */
const updateToolButtonState = function () { const updateToolButtonState = function () {
let index, button;
const bNoFill = (svgCanvas.getColor('fill') === 'none'); const bNoFill = (svgCanvas.getColor('fill') === 'none');
const bNoStroke = (svgCanvas.getColor('stroke') === 'none'); const bNoStroke = (svgCanvas.getColor('stroke') === 'none');
const buttonsNeedingStroke = ['#tool_fhpath', '#tool_line']; const buttonsNeedingStroke = ['#tool_fhpath', '#tool_line'];
const buttonsNeedingFillAndStroke = ['#tools_rect .tool_button', '#tools_ellipse .tool_button', '#tool_text', '#tool_path']; const buttonsNeedingFillAndStroke = ['#tools_rect .tool_button', '#tools_ellipse .tool_button', '#tool_text', '#tool_path'];
if (bNoStroke) { if (bNoStroke) {
for (index in buttonsNeedingStroke) { buttonsNeedingStroke.forEach((btn) => {
button = buttonsNeedingStroke[index]; if ($(btn).hasClass('tool_button_current')) {
if ($(button).hasClass('tool_button_current')) {
clickSelect(); clickSelect();
} }
$(button).addClass('disabled'); $(btn).addClass('disabled');
} });
} else { } else {
for (index in buttonsNeedingStroke) { buttonsNeedingStroke.forEach((btn) => {
button = buttonsNeedingStroke[index]; $(btn).removeClass('disabled');
$(button).removeClass('disabled'); });
}
} }
if (bNoStroke && bNoFill) { if (bNoStroke && bNoFill) {
for (index in buttonsNeedingFillAndStroke) { buttonsNeedingFillAndStroke.forEach((btn) => {
button = buttonsNeedingFillAndStroke[index]; if ($(btn).hasClass('tool_button_current')) {
if ($(button).hasClass('tool_button_current')) {
clickSelect(); clickSelect();
} }
$(button).addClass('disabled'); $(btn).addClass('disabled');
} });
} else { } else {
for (index in buttonsNeedingFillAndStroke) { buttonsNeedingFillAndStroke.forEach((btn) => {
button = buttonsNeedingFillAndStroke[index]; $(btn).removeClass('disabled');
$(button).removeClass('disabled'); });
}
} }
svgCanvas.runExtensions('toolButtonStateUpdate', /** @type {module:svgcanvas.SvgCanvas#event:ext-toolButtonStateUpdate} */ { svgCanvas.runExtensions('toolButtonStateUpdate', /** @type {module:svgcanvas.SvgCanvas#event:ext-toolButtonStateUpdate} */ {
@@ -2186,14 +2191,14 @@ editor.init = function () {
// This function also updates the opacity and id elements that are in the context panel // This function also updates the opacity and id elements that are in the context panel
const updateToolbar = function () { const updateToolbar = function () {
let i, len; let i, len;
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
switch (selectedElement.tagName) { switch (selectedElement.tagName) {
case 'use': case 'use':
case 'image': case 'image':
case 'foreignObject': case 'foreignObject':
break; break;
case 'g': case 'g':
case 'a': case 'a': {
// Look for common styles // Look for common styles
const childs = selectedElement.getElementsByTagName('*'); const childs = selectedElement.getElementsByTagName('*');
let gWidth = null; let gWidth = null;
@@ -2213,7 +2218,7 @@ editor.init = function () {
paintBox.stroke.update(true); paintBox.stroke.update(true);
break; break;
default: } default: {
paintBox.fill.update(true); paintBox.fill.update(true);
paintBox.stroke.update(true); paintBox.stroke.update(true);
@@ -2232,10 +2237,11 @@ editor.init = function () {
setStrokeOpt($('#linecap_' + attr)[0]); setStrokeOpt($('#linecap_' + attr)[0]);
} }
} }
}
} }
// All elements including image and group have opacity // All elements including image and group have opacity
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
const opacPerc = (selectedElement.getAttribute('opacity') || 1.0) * 100; const opacPerc = (selectedElement.getAttribute('opacity') || 1.0) * 100;
$('#group_opacity').val(opacPerc); $('#group_opacity').val(opacPerc);
$('#opac_slider').slider('option', 'value', opacPerc); $('#opac_slider').slider('option', 'value', opacPerc);
@@ -2250,7 +2256,7 @@ editor.init = function () {
const updateContextPanel = function () { const updateContextPanel = function () {
let elem = selectedElement; let elem = selectedElement;
// If element has just been deleted, consider it null // If element has just been deleted, consider it null
if (elem != null && !elem.parentNode) { elem = null; } if (!Utils.isNullish(elem) && !elem.parentNode) { elem = null; }
const currentLayerName = svgCanvas.getCurrentDrawing().getCurrentLayerName(); const currentLayerName = svgCanvas.getCurrentDrawing().getCurrentLayerName();
const currentMode = svgCanvas.getMode(); const currentMode = svgCanvas.getMode();
const unit = curConfig.baseUnit !== 'px' ? curConfig.baseUnit : null; const unit = curConfig.baseUnit !== 'px' ? curConfig.baseUnit : null;
@@ -2260,7 +2266,7 @@ editor.init = function () {
$('#selected_panel, #multiselected_panel, #g_panel, #rect_panel, #circle_panel,' + $('#selected_panel, #multiselected_panel, #g_panel, #rect_panel, #circle_panel,' +
'#ellipse_panel, #line_panel, #text_panel, #image_panel, #container_panel,' + '#ellipse_panel, #line_panel, #text_panel, #image_panel, #container_panel,' +
' #use_panel, #a_panel').hide(); ' #use_panel, #a_panel').hide();
if (elem != null) { if (!Utils.isNullish(elem)) {
const elname = elem.nodeName; const elname = elem.nodeName;
// If this is a link with no transform and one child, pretend // If this is a link with no transform and one child, pretend
// its child is selected // its child is selected
@@ -2436,7 +2442,7 @@ editor.init = function () {
} }
menuItems[(tagName === 'g' ? 'en' : 'dis') + 'ableContextMenuItems']('#ungroup'); menuItems[(tagName === 'g' ? 'en' : 'dis') + 'ableContextMenuItems']('#ungroup');
menuItems[((tagName === 'g' || !multiselected) ? 'dis' : 'en') + 'ableContextMenuItems']('#group'); menuItems[((tagName === 'g' || !multiselected) ? 'dis' : 'en') + 'ableContextMenuItems']('#group');
// if (elem != null) // if (!Utils.isNullish(elem))
} else if (multiselected) { } else if (multiselected) {
$('#multiselected_panel').show(); $('#multiselected_panel').show();
menuItems menuItems
@@ -2506,9 +2512,9 @@ editor.init = function () {
} }
const isNode = mode === 'pathedit'; const isNode = mode === 'pathedit';
// if elems[1] is present, then we have more than one element // if elems[1] is present, then we have more than one element
selectedElement = (elems.length === 1 || elems[1] == null ? elems[0] : null); selectedElement = (elems.length === 1 || Utils.isNullish(elems[1]) ? elems[0] : null);
multiselected = (elems.length >= 2 && elems[1] != null); multiselected = (elems.length >= 2 && !Utils.isNullish(elems[1]));
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
// unless we're already in always set the mode of the editor to select because // unless we're already in always set the mode of the editor to select because
// upon creation of a text element the editor is switched into // upon creation of a text element the editor is switched into
// select mode and this event fires - we need our UI to be in sync // select mode and this event fires - we need our UI to be in sync
@@ -2516,7 +2522,7 @@ editor.init = function () {
if (!isNode) { if (!isNode) {
updateToolbar(); updateToolbar();
} }
} // if (elem != null) } // if (!Utils.isNullish(elem))
// Deal with pathedit mode // Deal with pathedit mode
togglePathEditMode(isNode, elems); togglePathEditMode(isNode, elems);
@@ -2545,7 +2551,7 @@ editor.init = function () {
return; return;
} }
multiselected = (elems.length >= 2 && elems[1] != null); multiselected = (elems.length >= 2 && !Utils.isNullish(elems[1]));
// Only updating fields for single elements for now // Only updating fields for single elements for now
if (!multiselected) { if (!multiselected) {
switch (mode) { switch (mode) {
@@ -2601,7 +2607,7 @@ editor.init = function () {
} }
// Update selectedElement if element is no longer part of the image. // Update selectedElement if element is no longer part of the image.
// This occurs for the text elements in Firefox // This occurs for the text elements in Firefox
} else if (elem && selectedElement && selectedElement.parentNode == null) { } else if (elem && selectedElement && Utils.isNullish(selectedElement.parentNode)) {
// || elem && elem.tagName == "path" && !multiselected) { // This was added in r1430, but not sure why // || elem && elem.tagName == "path" && !multiselected) { // This was added in r1430, but not sure why
selectedElement = elem; selectedElement = elem;
} }
@@ -2820,7 +2826,7 @@ editor.init = function () {
options = tool; options = tool;
} else { } else {
// If flyout is selected, allow shift key to iterate through subitems // If flyout is selected, allow shift key to iterate through subitems
i = parseInt(i, 10); i = parseInt(i);
// Use `allHolders` to include both extension `includeWith` and toolbarButtons // Use `allHolders` to include both extension `includeWith` and toolbarButtons
options = allHolders[opts.parent][i + 1] || options = allHolders[opts.parent][i + 1] ||
holders[opts.parent][0]; holders[opts.parent][0];
@@ -3048,7 +3054,7 @@ editor.init = function () {
let html; let html;
// TODO: Allow support for other types, or adding to existing tool // TODO: Allow support for other types, or adding to existing tool
switch (tool.type) { switch (tool.type) {
case 'tool_button': case 'tool_button': {
html = '<div class="tool_button">' + tool.id + '</div>'; html = '<div class="tool_button">' + tool.id + '</div>';
const div = $(html).appendTo(panel); const div = $(html).appendTo(panel);
if (tool.events) { if (tool.events) {
@@ -3057,7 +3063,7 @@ editor.init = function () {
}); });
} }
break; break;
case 'select': } case 'select': {
html = '<label' + contId + '>' + html = '<label' + contId + '>' +
'<select id="' + tool.id + '">'; '<select id="' + tool.id + '">';
$.each(tool.options, function (val, text) { $.each(tool.options, function (val, text) {
@@ -3072,7 +3078,7 @@ editor.init = function () {
$(sel).bind(evt, func); $(sel).bind(evt, func);
}); });
break; break;
case 'button-select': } case 'button-select': {
html = '<div id="' + tool.id + '" class="dropdown toolset" title="' + tool.title + '">' + html = '<div id="' + tool.id + '" class="dropdown toolset" title="' + tool.title + '">' +
'<div id="cur_' + tool.id + '" class="icon_label"></div><button></button></div>'; '<div id="cur_' + tool.id + '" class="icon_label"></div><button></button></div>';
@@ -3094,7 +3100,7 @@ editor.init = function () {
}); });
break; break;
case 'input': } case 'input': {
html = '<label' + contId + '>' + html = '<label' + contId + '>' +
'<span id="' + tool.id + '_label">' + '<span id="' + tool.id + '_label">' +
tool.label + ':</span>' + tool.label + ':</span>' +
@@ -3117,8 +3123,7 @@ editor.init = function () {
}); });
} }
break; break;
} default:
default:
break; break;
} }
}); });
@@ -3503,11 +3508,11 @@ editor.init = function () {
const changeRotationAngle = function (ctl) { const changeRotationAngle = function (ctl) {
svgCanvas.setRotationAngle(ctl.value); svgCanvas.setRotationAngle(ctl.value);
$('#tool_reorient').toggleClass('disabled', parseInt(ctl.value, 10) === 0); $('#tool_reorient').toggleClass('disabled', parseInt(ctl.value) === 0);
}; };
const changeOpacity = function (ctl, val) { const changeOpacity = function (ctl, val) {
if (val == null) { val = ctl.value; } if (Utils.isNullish(val)) { val = ctl.value; }
$('#group_opacity').val(val); $('#group_opacity').val(val);
if (!ctl || !ctl.handle) { if (!ctl || !ctl.handle) {
$('#opac_slider').slider('option', 'value', val); $('#opac_slider').slider('option', 'value', val);
@@ -3516,7 +3521,7 @@ editor.init = function () {
}; };
const changeBlur = function (ctl, val, noUndo) { const changeBlur = function (ctl, val, noUndo) {
if (val == null) { val = ctl.value; } if (Utils.isNullish(val)) { val = ctl.value; }
$('#blur').val(val); $('#blur').val(val);
let complete = false; let complete = false;
if (!ctl || !ctl.handle) { if (!ctl || !ctl.handle) {
@@ -3862,7 +3867,7 @@ editor.init = function () {
editor.addDropDown('#opacity_dropdown', function () { editor.addDropDown('#opacity_dropdown', function () {
if ($(this).find('div').length) { return; } if ($(this).find('div').length) { return; }
const perc = parseInt($(this).text().split('%')[0], 10); const perc = parseInt($(this).text().split('%')[0]);
changeOpacity(false, perc); changeOpacity(false, perc);
}, true); }, true);
@@ -3947,7 +3952,7 @@ editor.init = function () {
}; };
$('#svg_editor').find('button, select, input:not(#text)').focus(function () { $('#svg_editor').find('button, select, input:not(#text)').focus(function () {
inp = this; inp = this; // eslint-disable-line consistent-this
uiContext = 'toolbars'; uiContext = 'toolbars';
workarea.mousedown(unfocus); workarea.mousedown(unfocus);
}).blur(function () { }).blur(function () {
@@ -4053,19 +4058,19 @@ editor.init = function () {
// Delete is a contextual tool that only appears in the ribbon if // Delete is a contextual tool that only appears in the ribbon if
// an element has been selected // an element has been selected
const deleteSelected = function () { const deleteSelected = function () {
if (selectedElement != null || multiselected) { if (!Utils.isNullish(selectedElement) || multiselected) {
svgCanvas.deleteSelectedElements(); svgCanvas.deleteSelectedElements();
} }
}; };
const cutSelected = function () { const cutSelected = function () {
if (selectedElement != null || multiselected) { if (!Utils.isNullish(selectedElement) || multiselected) {
svgCanvas.cutSelectedElements(); svgCanvas.cutSelectedElements();
} }
}; };
const copySelected = function () { const copySelected = function () {
if (selectedElement != null || multiselected) { if (!Utils.isNullish(selectedElement) || multiselected) {
svgCanvas.copySelectedElements(); svgCanvas.copySelectedElements();
} }
}; };
@@ -4078,37 +4083,37 @@ editor.init = function () {
}; };
const moveToTopSelected = function () { const moveToTopSelected = function () {
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
svgCanvas.moveToTopSelectedElement(); svgCanvas.moveToTopSelectedElement();
} }
}; };
const moveToBottomSelected = function () { const moveToBottomSelected = function () {
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
svgCanvas.moveToBottomSelectedElement(); svgCanvas.moveToBottomSelectedElement();
} }
}; };
const moveUpDownSelected = function (dir) { const moveUpDownSelected = function (dir) {
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
svgCanvas.moveUpDownSelected(dir); svgCanvas.moveUpDownSelected(dir);
} }
}; };
const convertToPath = function () { const convertToPath = function () {
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
svgCanvas.convertToPath(); svgCanvas.convertToPath();
} }
}; };
const reorientPath = function () { const reorientPath = function () {
if (selectedElement != null) { if (!Utils.isNullish(selectedElement)) {
path.reorient(); path.reorient();
} }
}; };
const makeHyperlink = function () { const makeHyperlink = function () {
if (selectedElement != null || multiselected) { if (!Utils.isNullish(selectedElement) || multiselected) {
$.prompt(uiStrings.notification.enterNewLinkURL, 'http://', function (url) { $.prompt(uiStrings.notification.enterNewLinkURL, 'http://', function (url) {
if (url) { svgCanvas.makeHyperlink(url); } if (url) { svgCanvas.makeHyperlink(url); }
}); });
@@ -4116,7 +4121,7 @@ editor.init = function () {
}; };
const moveSelected = function (dx, dy) { const moveSelected = function (dx, dy) {
if (selectedElement != null || multiselected) { if (!Utils.isNullish(selectedElement) || multiselected) {
if (curConfig.gridSnapping) { if (curConfig.gridSnapping) {
// Use grid snap value regardless of zoom level // Use grid snap value regardless of zoom level
const multi = svgCanvas.getZoom() * curConfig.snappingStep; const multi = svgCanvas.getZoom() * curConfig.snappingStep;
@@ -4165,7 +4170,7 @@ editor.init = function () {
}; };
const rotateSelected = function (cw, step) { const rotateSelected = function (cw, step) {
if (selectedElement == null || multiselected) { return; } if (Utils.isNullish(selectedElement) || multiselected) { return; }
if (!cw) { step *= -1; } if (!cw) { step *= -1; }
const angle = parseFloat($('#angle').val()) + step; const angle = parseFloat($('#angle').val()) + step;
svgCanvas.setRotationAngle(angle); svgCanvas.setRotationAngle(angle);
@@ -4263,7 +4268,7 @@ editor.init = function () {
if (!customExportImage) { if (!customExportImage) {
openExportWindow(); openExportWindow();
} }
const quality = parseInt($('#image-slider').val(), 10) / 100; const quality = parseInt($('#image-slider').val()) / 100;
/* const results = */ await svgCanvas.rasterExport(imgType, quality, exportWindowName); /* const results = */ await svgCanvas.rasterExport(imgType, quality, exportWindowName);
} }
}, function () { }, function () {
@@ -4289,6 +4294,7 @@ editor.init = function () {
}; };
const clickImport = function () { const clickImport = function () {
/* */
}; };
const clickUndo = function () { const clickUndo = function () {
@@ -4710,13 +4716,14 @@ editor.init = function () {
fillAttr = (paint[ptype] !== 'none') ? '#' + paint[ptype] : paint[ptype]; fillAttr = (paint[ptype] !== 'none') ? '#' + paint[ptype] : paint[ptype];
break; break;
case 'linearGradient': case 'linearGradient':
case 'radialGradient': case 'radialGradient': {
this.grad.remove(); this.grad.remove();
this.grad = this.defs.appendChild(paint[ptype]); this.grad = this.defs.appendChild(paint[ptype]);
const id = this.grad.id = 'gradbox_' + this.type; const id = this.grad.id = 'gradbox_' + this.type;
fillAttr = 'url(#' + id + ')'; fillAttr = 'url(#' + id + ')';
break; break;
} }
}
this.rect.setAttribute('fill', fillAttr); this.rect.setAttribute('fill', fillAttr);
this.rect.setAttribute('opacity', opac); this.rect.setAttribute('opacity', opac);
@@ -4992,8 +4999,8 @@ editor.init = function () {
const rulerX = $('#ruler_x'); const rulerX = $('#ruler_x');
$('#sidepanels').width('+=' + delta); $('#sidepanels').width('+=' + delta);
$('#layerpanel').width('+=' + delta); $('#layerpanel').width('+=' + delta);
rulerX.css('right', parseInt(rulerX.css('right'), 10) + delta); rulerX.css('right', parseInt(rulerX.css('right')) + delta);
workarea.css('right', parseInt(workarea.css('right'), 10) + delta); workarea.css('right', parseInt(workarea.css('right')) + delta);
svgCanvas.runExtensions('workareaResized'); svgCanvas.runExtensions('workareaResized');
}; };
@@ -5337,10 +5344,10 @@ editor.init = function () {
} else { } else {
keyval = opts.key; keyval = opts.key;
} }
keyval += ''; keyval = String(keyval);
const {fn} = opts; const {fn} = opts;
$.each(keyval.split('/'), function (i, key) { $.each(keyval.split('/'), function (j, key) {
$(document).bind('keydown', key, function (e) { $(document).bind('keydown', key, function (e) {
fn(); fn();
if (pd) { if (pd) {
@@ -5369,7 +5376,9 @@ editor.init = function () {
// Misc additional actions // Misc additional actions
// Make 'return' keypress trigger the change event // Make 'return' keypress trigger the change event
$('.attr_changer, #image_url').bind('keydown', 'return', $('.attr_changer, #image_url').bind(
'keydown',
'return',
function (evt) { function (evt) {
$(this).change(); $(this).change();
evt.preventDefault(); evt.preventDefault();
@@ -5459,7 +5468,7 @@ editor.init = function () {
toggleSidePanel(); toggleSidePanel();
} }
$('#rulers').toggle(!!curConfig.showRulers); $('#rulers').toggle(Boolean(curConfig.showRulers));
if (curConfig.showRulers) { if (curConfig.showRulers) {
$('#show_rulers')[0].checked = true; $('#show_rulers')[0].checked = true;
@@ -5631,6 +5640,7 @@ editor.init = function () {
e.returnValue = uiStrings.notification.unsavedChanges; // Firefox needs this when beforeunload set by addEventListener (even though message is not used) e.returnValue = uiStrings.notification.unsavedChanges; // Firefox needs this when beforeunload set by addEventListener (even though message is not used)
return uiStrings.notification.unsavedChanges; return uiStrings.notification.unsavedChanges;
} }
return true;
}); });
/** /**
@@ -5702,8 +5712,8 @@ editor.init = function () {
let reader; let reader;
if (file.type.includes('svg')) { if (file.type.includes('svg')) {
reader = new FileReader(); reader = new FileReader();
reader.onloadend = function (e) { reader.onloadend = function (ev) {
const newElement = svgCanvas.importSvgString(e.target.result, true); const newElement = svgCanvas.importSvgString(ev.target.result, true);
svgCanvas.ungroupSelectedElement(); svgCanvas.ungroupSelectedElement();
svgCanvas.ungroupSelectedElement(); svgCanvas.ungroupSelectedElement();
svgCanvas.groupSelectedElements(); svgCanvas.groupSelectedElements();
@@ -5761,7 +5771,7 @@ editor.init = function () {
workarea[0].addEventListener('drop', importImage); workarea[0].addEventListener('drop', importImage);
const open = $('<input type="file">').click(function () { const open = $('<input type="file">').click(function () {
const f = this; const f = this; // eslint-disable-line consistent-this
editor.openPrep(function (ok) { editor.openPrep(function (ok) {
if (!ok) { return; } if (!ok) { return; }
svgCanvas.clear(); svgCanvas.clear();
@@ -5976,7 +5986,7 @@ editor.loadFromURL = function (url, opts) {
$.ajax({ $.ajax({
url, url,
dataType: 'text', dataType: 'text',
cache: !!cache, cache: Boolean(cache),
beforeSend () { beforeSend () {
$.process_cancel(uiStrings.notification.loadingImage); $.process_cancel(uiStrings.notification.loadingImage);
}, },
@@ -6051,7 +6061,7 @@ const messageQueue = [];
* @fires module:svgcanvas.SvgCanvas#event:message * @fires module:svgcanvas.SvgCanvas#event:message
* @returns {undefined} * @returns {undefined}
*/ */
const messageListener = ({data, origin}) => { const messageListener = ({data, origin}) => { // eslint-disable-line no-shadow
// console.log('data, origin, extensionsAdded', data, origin, extensionsAdded); // console.log('data, origin, extensionsAdded', data, origin, extensionsAdded);
const messageObj = {data, origin}; const messageObj = {data, origin};
if (!extensionsAdded) { if (!extensionsAdded) {

View File

@@ -18,7 +18,7 @@
// Todo: Obtain/adapt latest jsPDF to utilize ES Module for `jsPDF`/avoid global // Todo: Obtain/adapt latest jsPDF to utilize ES Module for `jsPDF`/avoid global
import './svgpathseg.js'; import './svgpathseg.js';
import jqPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
import * as draw from './draw.js'; import * as draw from './draw.js';
import * as pathModule from './path.js'; import * as pathModule from './path.js';
@@ -32,9 +32,10 @@ import {
getBBoxOfElementAsPath, convertToPath, toXml, encode64, decode64, getBBoxOfElementAsPath, convertToPath, toXml, encode64, decode64,
dataURLToObjectURL, createObjectURL, dataURLToObjectURL, createObjectURL,
getVisibleElements, dropXMLInteralSubset, getVisibleElements, dropXMLInteralSubset,
init as utilsInit, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible init as utilsInit, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible,
isNullish
} from './utilities.js'; } from './utilities.js';
import * as history from './history.js'; import * as hstry from './history.js';
import { import {
transformPoint, matrixMultiply, hasMatrixTransform, transformListToTransform, transformPoint, matrixMultiply, hasMatrixTransform, transformListToTransform,
getMatrix, snapToAngle, isIdentity, rectsIntersect, transformBox getMatrix, snapToAngle, isIdentity, rectsIntersect, transformBox
@@ -60,24 +61,25 @@ import {
} from './recalculate.js'; } from './recalculate.js';
import { import {
getSelectorManager, getSelectorManager,
Selector,
init as selectInit init as selectInit
} from './select.js'; } from './select.js';
const $ = jqPluginSVG(jQuery); const $ = jQueryPluginSVG(jQuery);
const { const {
MoveElementCommand, InsertElementCommand, RemoveElementCommand, MoveElementCommand, InsertElementCommand, RemoveElementCommand,
ChangeElementCommand, BatchCommand, UndoManager, HistoryEventTypes ChangeElementCommand, BatchCommand, UndoManager, HistoryEventTypes
} = history; } = hstry;
if (!window.console) { if (!window.console) {
window.console = {}; window.console = {};
window.console.log = function (str) {}; window.console.log = function (str) { /* */ };
window.console.dir = function (str) {}; window.console.dir = function (str) { /* */ };
} }
if (window.opera) { if (window.opera) {
window.console.log = function (str) { window.opera.postError(str); }; window.console.log = function (str) { window.opera.postError(str); };
window.console.dir = function (str) {}; window.console.dir = function (str) { /* */ };
} }
/** /**
@@ -131,7 +133,7 @@ if (config) {
// Array with width/height of canvas // Array with width/height of canvas
const {dimensions} = curConfig; const {dimensions} = curConfig;
const canvas = this; const canvas = this; // eslint-disable-line consistent-this
// "document" element associated with the container (same as window.document using default svg-editor.js) // "document" element associated with the container (same as window.document using default svg-editor.js)
// NOTE: This is not actually a SVG document, but an HTML document. // NOTE: This is not actually a SVG document, but an HTML document.
@@ -396,7 +398,7 @@ const getSelectedElements = this.getSelectedElems = function () {
return selectedElements; return selectedElements;
}; };
const pathActions = pathModule.pathActions; const {pathActions} = pathModule;
/** /**
* This should actually be an intersection as all interfaces should be met. * This should actually be an intersection as all interfaces should be met.
@@ -503,8 +505,8 @@ const undoMgr = canvas.undoMgr = new UndoManager({
} }
if (cmdType === InsertElementCommand.type()) { if (cmdType === InsertElementCommand.type()) {
if (isApply) { restoreRefElems(cmd.elem); } if (isApply) { restoreRefElems(cmd.elem); }
} else { } else if (!isApply) {
if (!isApply) { restoreRefElems(cmd.elem); } restoreRefElems(cmd.elem);
} }
if (cmd.elem.tagName === 'use') { if (cmd.elem.tagName === 'use') {
setUseData(cmd.elem); setUseData(cmd.elem);
@@ -562,7 +564,7 @@ const getCurrentZoom = this.getZoom = function () { return currentZoom; };
* @implements {module:path.EditorContext#round} * @implements {module:path.EditorContext#round}
*/ */
const round = this.round = function (val) { const round = this.round = function (val) {
return parseInt(val * currentZoom, 10) / currentZoom; return parseInt(val * currentZoom) / currentZoom;
}; };
selectInit( selectInit(
@@ -607,12 +609,13 @@ const getId = canvas.getId = function () {
* @implements {module:draw.DrawCanvasInit#call|module:path.EditorContext#call} * @implements {module:draw.DrawCanvasInit#call|module:path.EditorContext#call}
* @param {"selected"|"changed"|"contextset"|"pointsAdded"|"extension_added"|"extensions_added"|"message"|"transition"|"zoomed"|"updateCanvas"|"zoomDone"|"saved"|"exported"|"exportedPDF"|"setnonce"|"unsetnonce"|"cleared"} ev - String with the event name * @param {"selected"|"changed"|"contextset"|"pointsAdded"|"extension_added"|"extensions_added"|"message"|"transition"|"zoomed"|"updateCanvas"|"zoomDone"|"saved"|"exported"|"exportedPDF"|"setnonce"|"unsetnonce"|"cleared"} ev - String with the event name
* @param {module:svgcanvas.SvgCanvas#event:GenericCanvasEvent} arg - Argument to pass through to the callback function. * @param {module:svgcanvas.SvgCanvas#event:GenericCanvasEvent} arg - Argument to pass through to the callback function.
* @returns {undefined} * @returns {module:svgcanvas.EventHandlerReturn|undefined}
*/ */
const call = function (ev, arg) { const call = function (ev, arg) {
if (events[ev]) { if (events[ev]) {
return events[ev](window, arg); return events[ev](window, arg);
} }
return undefined;
}; };
/** /**
@@ -624,7 +627,7 @@ const call = function (ev, arg) {
*/ */
const clearSelection = this.clearSelection = function (noCall) { const clearSelection = this.clearSelection = function (noCall) {
selectedElements.forEach((elem) => { selectedElements.forEach((elem) => {
if (elem == null) { if (isNullish(elem)) {
return; return;
} }
selectorManager.releaseSelector(elem); selectorManager.releaseSelector(elem);
@@ -646,7 +649,7 @@ const addToSelection = this.addToSelection = function (elemsToAdd, showGrips) {
let j = 0; let j = 0;
while (j < selectedElements.length) { while (j < selectedElements.length) {
if (selectedElements[j] == null) { if (isNullish(selectedElements[j])) {
break; break;
} }
++j; ++j;
@@ -692,15 +695,16 @@ const addToSelection = this.addToSelection = function (elemsToAdd, showGrips) {
selectedElements.sort(function (a, b) { selectedElements.sort(function (a, b) {
if (a && b && a.compareDocumentPosition) { if (a && b && a.compareDocumentPosition) {
return 3 - (b.compareDocumentPosition(a) & 6); return 3 - (b.compareDocumentPosition(a) & 6); // eslint-disable-line no-bitwise
} }
if (a == null) { if (isNullish(a)) {
return 1; return 1;
} }
return 0;
}); });
// Make sure first elements are not null // Make sure first elements are not null
while (selectedElements[0] == null) { while (isNullish(selectedElements[0])) {
selectedElements.shift(0); selectedElements.shift(0);
} }
}; };
@@ -717,7 +721,7 @@ const getOpacity = function () {
* @implements {module:path.EditorContext#getMouseTarget} * @implements {module:path.EditorContext#getMouseTarget}
*/ */
const getMouseTarget = this.getMouseTarget = function (evt) { const getMouseTarget = this.getMouseTarget = function (evt) {
if (evt == null) { if (isNullish(evt)) {
return null; return null;
} }
let mouseTarget = evt.target; let mouseTarget = evt.target;
@@ -881,8 +885,7 @@ $(opacAni).attr({
const restoreRefElems = function (elem) { const restoreRefElems = function (elem) {
// Look for missing reference elements, restore any found // Look for missing reference elements, restore any found
const attrs = $(elem).attr(refAttrs); const attrs = $(elem).attr(refAttrs);
for (const o in attrs) { Object.values(attrs).forEach((val) => {
const val = attrs[o];
if (val && val.startsWith('url(')) { if (val && val.startsWith('url(')) {
const id = getUrlFromAttr(val).substr(1); const id = getUrlFromAttr(val).substr(1);
const ref = getElem(id); const ref = getElem(id);
@@ -891,7 +894,7 @@ const restoreRefElems = function (elem) {
delete removedElements[id]; delete removedElements[id];
} }
} }
} });
const childs = elem.getElementsByTagName('*'); const childs = elem.getElementsByTagName('*');
@@ -1158,9 +1161,9 @@ this.addExtension = async function (name, extInitFunc, importLocale) {
extensions[name] = extObj; extensions[name] = extObj;
return call('extension_added', extObj); return call('extension_added', extObj);
} else {
console.log('Cannot add extension "' + name + '", an extension by that name already exists.');
} }
console.log('Cannot add extension "' + name + '", an extension by that name already exists.');
return undefined;
}; };
/** /**
@@ -1176,7 +1179,7 @@ this.addExtension = async function (name, extInitFunc, importLocale) {
* @returns {Element[]|NodeList} Bbox elements * @returns {Element[]|NodeList} Bbox elements
*/ */
const getIntersectionList = this.getIntersectionList = function (rect) { const getIntersectionList = this.getIntersectionList = function (rect) {
if (rubberBox == null) { return null; } if (isNullish(rubberBox)) { return null; }
const parent = currentGroup || getCurrentDrawing().getCurrentLayer(); const parent = currentGroup || getCurrentDrawing().getCurrentLayer();
@@ -1201,14 +1204,14 @@ const getIntersectionList = this.getIntersectionList = function (rect) {
if (!isIE) { if (!isIE) {
if (typeof svgroot.getIntersectionList === 'function') { if (typeof svgroot.getIntersectionList === 'function') {
// Offset the bbox of the rubber box by the offset of the svgcontent element. // Offset the bbox of the rubber box by the offset of the svgcontent element.
rubberBBox.x += parseInt(svgcontent.getAttribute('x'), 10); rubberBBox.x += parseInt(svgcontent.getAttribute('x'));
rubberBBox.y += parseInt(svgcontent.getAttribute('y'), 10); rubberBBox.y += parseInt(svgcontent.getAttribute('y'));
resultList = svgroot.getIntersectionList(rubberBBox, parent); resultList = svgroot.getIntersectionList(rubberBBox, parent);
} }
} }
if (resultList == null || typeof resultList.item !== 'function') { if (isNullish(resultList) || typeof resultList.item !== 'function') {
resultList = []; resultList = [];
if (!curBBoxes.length) { if (!curBBoxes.length) {
@@ -1385,11 +1388,18 @@ canvas.call = call;
* @type {module:svgcanvas.SvgCanvas#event:selected|module:svgcanvas.SvgCanvas#event:changed|module:svgcanvas.SvgCanvas#event:contextset|module:svgcanvas.SvgCanvas#event:pointsAdded|module:svgcanvas.SvgCanvas#event:extension_added|module:svgcanvas.SvgCanvas#event:extensions_added|module:svgcanvas.SvgCanvas#event:message|module:svgcanvas.SvgCanvas#event:transition|module:svgcanvas.SvgCanvas#event:zoomed|module:svgcanvas.SvgCanvas#event:updateCanvas|module:svgcanvas.SvgCanvas#event:saved|module:svgcanvas.SvgCanvas#event:exported|module:svgcanvas.SvgCanvas#event:exportedPDF|module:svgcanvas.SvgCanvas#event:setnonce|module:svgcanvas.SvgCanvas#event:unsetnonce|undefined} * @type {module:svgcanvas.SvgCanvas#event:selected|module:svgcanvas.SvgCanvas#event:changed|module:svgcanvas.SvgCanvas#event:contextset|module:svgcanvas.SvgCanvas#event:pointsAdded|module:svgcanvas.SvgCanvas#event:extension_added|module:svgcanvas.SvgCanvas#event:extensions_added|module:svgcanvas.SvgCanvas#event:message|module:svgcanvas.SvgCanvas#event:transition|module:svgcanvas.SvgCanvas#event:zoomed|module:svgcanvas.SvgCanvas#event:updateCanvas|module:svgcanvas.SvgCanvas#event:saved|module:svgcanvas.SvgCanvas#event:exported|module:svgcanvas.SvgCanvas#event:exportedPDF|module:svgcanvas.SvgCanvas#event:setnonce|module:svgcanvas.SvgCanvas#event:unsetnonce|undefined}
*/ */
/**
* The promise return, if present, resolves to `undefined`
* (`extension_added`, `exported`, `saved`)
* @typedef {Promise|undefined} module:svgcanvas.EventHandlerReturn
*/
/** /**
* @callback module:svgcanvas.EventHandler * @callback module:svgcanvas.EventHandler
* @param {external:Window} win * @param {external:Window} win
* @param {module:svgcanvas.SvgCanvas#event:GenericCanvasEvent} arg * @param {module:svgcanvas.SvgCanvas#event:GenericCanvasEvent} arg
* @listens module:svgcanvas.SvgCanvas#event:GenericCanvasEvent * @listens module:svgcanvas.SvgCanvas#event:GenericCanvasEvent
* @returns {module:svgcanvas.EventHandlerReturn}
*/ */
/** /**
@@ -1503,7 +1513,7 @@ this.setRotationAngle = function (val, preventUndo) {
// } // }
const selector = selectorManager.requestSelector(selectedElements[0]); const selector = selectorManager.requestSelector(selectedElements[0]);
selector.resize(); selector.resize();
selector.updateGripCursors(val); Selector.updateGripCursors(val);
}; };
/** /**
@@ -1574,7 +1584,7 @@ const selectOnly = this.selectOnly = function (elems, showGrips) {
* @returns {undefined} * @returns {undefined}
*/ */
/* const removeFromSelection = */ this.removeFromSelection = function (elemsToRemove) { /* const removeFromSelection = */ this.removeFromSelection = function (elemsToRemove) {
if (selectedElements[0] == null) { return; } if (isNullish(selectedElements[0])) { return; }
if (!elemsToRemove.length) { return; } if (!elemsToRemove.length) { return; }
// find every element and remove it from our array copy // find every element and remove it from our array copy
@@ -1732,7 +1742,7 @@ const mouseDown = function (evt) {
// if it is a selector grip, then it must be a single element selected, // if it is a selector grip, then it must be a single element selected,
// set the mouseTarget to that and update the mode to rotate/resize // set the mouseTarget to that and update the mode to rotate/resize
if (mouseTarget === selectorManager.selectorParentGroup && selectedElements[0] != null) { if (mouseTarget === selectorManager.selectorParentGroup && !isNullish(selectedElements[0])) {
const grip = evt.target; const grip = evt.target;
const griptype = elData(grip, 'type'); const griptype = elData(grip, 'type');
// rotating // rotating
@@ -1774,7 +1784,7 @@ const mouseDown = function (evt) {
// insert a dummy transform so if the element(s) are moved it will have // insert a dummy transform so if the element(s) are moved it will have
// a transform to use for its translate // a transform to use for its translate
for (i = 0; i < selectedElements.length; ++i) { for (i = 0; i < selectedElements.length; ++i) {
if (selectedElements[i] == null) { continue; } if (isNullish(selectedElements[i])) { continue; }
const slist = getTransformList(selectedElements[i]); const slist = getTransformList(selectedElements[i]);
if (slist.numberOfItems) { if (slist.numberOfItems) {
slist.insertItemBefore(svgroot.createSVGTransform(), 0); slist.insertItemBefore(svgroot.createSVGTransform(), 0);
@@ -1786,7 +1796,7 @@ const mouseDown = function (evt) {
} else if (!rightClick) { } else if (!rightClick) {
clearSelection(); clearSelection();
currentMode = 'multiselect'; currentMode = 'multiselect';
if (rubberBox == null) { if (isNullish(rubberBox)) {
rubberBox = selectorManager.getRubberBandBox(); rubberBox = selectorManager.getRubberBandBox();
} }
rStartX *= currentZoom; rStartX *= currentZoom;
@@ -1807,7 +1817,7 @@ const mouseDown = function (evt) {
break; break;
case 'zoom': case 'zoom':
started = true; started = true;
if (rubberBox == null) { if (isNullish(rubberBox)) {
rubberBox = selectorManager.getRubberBandBox(); rubberBox = selectorManager.getRubberBandBox();
} }
assignAttributes(rubberBox, { assignAttributes(rubberBox, {
@@ -1818,7 +1828,7 @@ const mouseDown = function (evt) {
display: 'inline' display: 'inline'
}, 100); }, 100);
break; break;
case 'resize': case 'resize': {
started = true; started = true;
startX = x; startX = x;
startY = y; startY = y;
@@ -1876,7 +1886,7 @@ const mouseDown = function (evt) {
} }
} }
break; break;
case 'fhellipse': } case 'fhellipse':
case 'fhrect': case 'fhrect':
case 'fhpath': case 'fhpath':
start.x = realX; start.x = realX;
@@ -1902,7 +1912,7 @@ const mouseDown = function (evt) {
freehand.miny = realY; freehand.miny = realY;
freehand.maxy = realY; freehand.maxy = realY;
break; break;
case 'image': case 'image': {
started = true; started = true;
const newImage = addSVGElementFromJson({ const newImage = addSVGElementFromJson({
element: 'image', element: 'image',
@@ -1919,7 +1929,7 @@ const mouseDown = function (evt) {
setHref(newImage, lastGoodImgUrl); setHref(newImage, lastGoodImgUrl);
preventClickDefault(newImage); preventClickDefault(newImage);
break; break;
case 'square': } case 'square':
// FIXME: once we create the rect, we lose information that this was a square // FIXME: once we create the rect, we lose information that this was a square
// (for resizing purposes this could be important) // (for resizing purposes this could be important)
// Fallthrough // Fallthrough
@@ -2119,7 +2129,7 @@ const mouseMove = function (evt) {
len = selectedElements.length; len = selectedElements.length;
for (i = 0; i < len; ++i) { for (i = 0; i < len; ++i) {
selected = selectedElements[i]; selected = selectedElements[i];
if (selected == null) { break; } if (isNullish(selected)) { break; }
// if (i === 0) { // if (i === 0) {
// const box = utilsGetBBox(selected); // const box = utilsGetBBox(selected);
// selectedBBoxes[i].x = box.x + dx; // selectedBBoxes[i].x = box.x + dx;
@@ -2411,9 +2421,8 @@ const mouseMove = function (evt) {
start = {x: end.x, y: end.y}; start = {x: end.x, y: end.y};
break; break;
// update path stretch line coordinates // update path stretch line coordinates
} case 'path': { } case 'path': { // eslint-disable-line no-empty
} } // fall through
// fall through
case 'pathedit': { case 'pathedit': {
x *= currentZoom; x *= currentZoom;
y *= currentZoom; y *= currentZoom;
@@ -2545,16 +2554,16 @@ const mouseUp = function (evt) {
// intentionally fall-through to select here // intentionally fall-through to select here
case 'resize': case 'resize':
case 'multiselect': case 'multiselect':
if (rubberBox != null) { if (!isNullish(rubberBox)) {
rubberBox.setAttribute('display', 'none'); rubberBox.setAttribute('display', 'none');
curBBoxes = []; curBBoxes = [];
} }
currentMode = 'select'; currentMode = 'select';
// Fallthrough // Fallthrough
case 'select': case 'select':
if (selectedElements[0] != null) { if (!isNullish(selectedElements[0])) {
// if we only have one selected element // if we only have one selected element
if (selectedElements[1] == null) { if (isNullish(selectedElements[1])) {
// set our current stroke/fill properties to the element's // set our current stroke/fill properties to the element's
const selected = selectedElements[0]; const selected = selectedElements[0];
switch (selected.tagName) { switch (selected.tagName) {
@@ -2589,7 +2598,7 @@ const mouseUp = function (evt) {
if (realX !== rStartX || realY !== rStartY) { if (realX !== rStartX || realY !== rStartY) {
const len = selectedElements.length; const len = selectedElements.length;
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
if (selectedElements[i] == null) { break; } if (isNullish(selectedElements[i])) { break; }
if (!selectedElements[i].firstChild) { if (!selectedElements[i].firstChild) {
// Not needed for groups (incorrectly resizes elems), possibly not needed at all? // Not needed for groups (incorrectly resizes elems), possibly not needed at all?
selectorManager.requestSelector(selectedElements[i]).resize(); selectorManager.requestSelector(selectedElements[i]).resize();
@@ -2598,7 +2607,7 @@ const mouseUp = function (evt) {
// no change in position/size, so maybe we should move to pathedit // no change in position/size, so maybe we should move to pathedit
} else { } else {
t = evt.target; t = evt.target;
if (selectedElements[0].nodeName === 'path' && selectedElements[1] == null) { if (selectedElements[0].nodeName === 'path' && isNullish(selectedElements[1])) {
pathActions.select(selectedElements[0]); pathActions.select(selectedElements[0]);
// if it was a path // if it was a path
// else, if it was selected and this is a shift-click, remove it from selection // else, if it was selected and this is a shift-click, remove it from selection
@@ -2622,7 +2631,7 @@ const mouseUp = function (evt) {
} }
return; return;
case 'zoom': case 'zoom':
if (rubberBox != null) { if (!isNullish(rubberBox)) {
rubberBox.setAttribute('display', 'none'); rubberBox.setAttribute('display', 'none');
} }
const factor = evt.shiftKey ? 0.5 : 2; const factor = evt.shiftKey ? 0.5 : 2;
@@ -2774,7 +2783,7 @@ const mouseUp = function (evt) {
} }
}); });
if (!keep && element != null) { if (!keep && !isNullish(element)) {
getCurrentDrawing().releaseId(getId()); getCurrentDrawing().releaseId(getId());
element.remove(); element.remove();
element = null; element = null;
@@ -2799,7 +2808,7 @@ const mouseUp = function (evt) {
canvas.setMode('select'); canvas.setMode('select');
selectOnly([t], true); selectOnly([t], true);
} }
} else if (element != null) { } else if (!isNullish(element)) {
/** /**
* @name module:svgcanvas.SvgCanvas#addedNew * @name module:svgcanvas.SvgCanvas#addedNew
* @type {boolean} * @type {boolean}
@@ -2936,8 +2945,8 @@ $(container).bind(
// content offset from canvas in screen pixels // content offset from canvas in screen pixels
const wOffset = workarea.offset(); const wOffset = workarea.offset();
const wOffsetLeft = wOffset['left'] + rulerwidth; const wOffsetLeft = wOffset.left + rulerwidth;
const wOffsetTop = wOffset['top'] + rulerwidth; const wOffsetTop = wOffset.top + rulerwidth;
const delta = (evt.wheelDelta) ? evt.wheelDelta : (evt.detail) ? -evt.detail : 0; const delta = (evt.wheelDelta) ? evt.wheelDelta : (evt.detail) ? -evt.detail : 0;
if (!delta) { return; } if (!delta) { return; }
@@ -3508,7 +3517,7 @@ const removeUnusedDefElems = this.removeUnusedDefElems = function () {
*/ */
this.svgCanvasToString = function () { this.svgCanvasToString = function () {
// keep calling it until there are none to remove // keep calling it until there are none to remove
while (removeUnusedDefElems() > 0) {} while (removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty
pathActions.clear(true); pathActions.clear(true);
@@ -3572,7 +3581,9 @@ this.svgToString = function (elem, indent) {
const attrs = Array.from(elem.attributes); const attrs = Array.from(elem.attributes);
let i; let i;
const childs = elem.childNodes; const childs = elem.childNodes;
attrs.sort((a, b) => a.name > b.name ? -1 : 1); attrs.sort((a, b) => {
return a.name > b.name ? -1 : 1;
});
for (i = 0; i < indent; i++) { out.push(' '); } for (i = 0; i < indent; i++) { out.push(' '); }
out.push('<'); out.push(elem.nodeName); out.push('<'); out.push(elem.nodeName);
@@ -3693,14 +3704,14 @@ this.svgToString = function (elem, indent) {
out.push('\n'); out.push('\n');
out.push(this.svgToString(childs.item(i), indent)); out.push(this.svgToString(childs.item(i), indent));
break; break;
case 3: // text node case 3: { // text node
const str = child.nodeValue.replace(/^\s+|\s+$/g, ''); const str = child.nodeValue.replace(/^\s+|\s+$/g, '');
if (str !== '') { if (str !== '') {
bOneLine = true; bOneLine = true;
out.push(String(toXml(str))); out.push(String(toXml(str)));
} }
break; break;
case 4: // cdata node } case 4: // cdata node
out.push('\n'); out.push('\n');
out.push(new Array(indent + 1).join(' ')); out.push(new Array(indent + 1).join(' '));
out.push('<![CDATA['); out.push('<![CDATA[');
@@ -4308,8 +4319,8 @@ const convertToGroup = this.convertToGroup = function (elem) {
if (vb) { if (vb) {
const nums = vb.split(' '); const nums = vb.split(' ');
pos.x -= +nums[0]; pos.x -= Number(nums[0]);
pos.y -= +nums[1]; pos.y -= Number(nums[1]);
} }
// Not ideal, but works // Not ideal, but works
@@ -4635,12 +4646,12 @@ this.importSvgString = function (xmlString) {
// if no explicit viewbox, create one out of the width and height // if no explicit viewbox, create one out of the width and height
vb = innervb ? innervb.split(' ') : [0, 0, innerw, innerh]; vb = innervb ? innervb.split(' ') : [0, 0, innerw, innerh];
for (j = 0; j < 4; ++j) { for (j = 0; j < 4; ++j) {
vb[j] = +(vb[j]); vb[j] = Number(vb[j]);
} }
// TODO: properly handle preserveAspectRatio // TODO: properly handle preserveAspectRatio
const // canvasw = +svgcontent.getAttribute('width'), const // canvasw = +svgcontent.getAttribute('width'),
canvash = +svgcontent.getAttribute('height'); canvash = Number(svgcontent.getAttribute('height'));
// imported content should be 1/3 of the canvas on its largest dimension // imported content should be 1/3 of the canvas on its largest dimension
if (innerh > innerw) { if (innerh > innerw) {
@@ -4864,12 +4875,13 @@ this.setConfig = function (opts) {
/** /**
* @function module:svgcanvas.SvgCanvas#getTitle * @function module:svgcanvas.SvgCanvas#getTitle
* @param {Element} elem * @param {Element} [elem]
* @returns {string|undefined} the current group/SVG's title contents * @returns {string|undefined} the current group/SVG's title contents or
* `undefined` if no element is passed nd there are no selected elements.
*/ */
this.getTitle = function (elem) { this.getTitle = function (elem) {
elem = elem || selectedElements[0]; elem = elem || selectedElements[0];
if (!elem) { return; } if (!elem) { return undefined; }
elem = $(elem).data('gsvg') || $(elem).data('symbol') || elem; elem = $(elem).data('gsvg') || $(elem).data('symbol') || elem;
const childs = elem.childNodes; const childs = elem.childNodes;
for (let i = 0; i < childs.length; i++) { for (let i = 0; i < childs.length; i++) {
@@ -5088,24 +5100,29 @@ this.setBBoxZoom = function (val, editorW, editorH) {
} }
switch (val) { switch (val) {
case 'selection': case 'selection': {
if (!selectedElements[0]) { return; } if (!selectedElements[0]) { return undefined; }
const selectedElems = $.map(selectedElements, function (n) { if (n) { return n; } }); const selectedElems = $.map(selectedElements, function (n) {
if (n) {
return n;
}
return undefined;
});
bb = getStrokedBBoxDefaultVisible(selectedElems); bb = getStrokedBBoxDefaultVisible(selectedElems);
break; break;
case 'canvas': } case 'canvas': {
const res = getResolution(); const res = getResolution();
spacer = 0.95; spacer = 0.95;
bb = {width: res.w, height: res.h, x: 0, y: 0}; bb = {width: res.w, height: res.h, x: 0, y: 0};
break; break;
case 'content': } case 'content':
bb = getStrokedBBoxDefaultVisible(); bb = getStrokedBBoxDefaultVisible();
break; break;
case 'layer': case 'layer':
bb = getStrokedBBoxDefaultVisible(getVisibleElements(getCurrentDrawing().getCurrentLayer())); bb = getStrokedBBoxDefaultVisible(getVisibleElements(getCurrentDrawing().getCurrentLayer()));
break; break;
default: default:
return; return undefined;
} }
return calcZoom(bb); return calcZoom(bb);
}; };
@@ -5262,14 +5279,12 @@ this.setColor = function (type, val, preventUndo) {
if (elem) { if (elem) {
if (elem.tagName === 'g') { if (elem.tagName === 'g') {
walkTree(elem, addNonG); walkTree(elem, addNonG);
} else { } else if (type === 'fill') {
if (type === 'fill') { if (elem.tagName !== 'polyline' && elem.tagName !== 'line') {
if (elem.tagName !== 'polyline' && elem.tagName !== 'line') {
elems.push(elem);
}
} else {
elems.push(elem); elems.push(elem);
} }
} else {
elems.push(elem);
} }
} }
} }
@@ -5651,14 +5666,12 @@ canvas.setBlurOffsets = function (filter, stdDev) {
width: '200%', width: '200%',
height: '200%' height: '200%'
}, 100); }, 100);
} else { // Removing these attributes hides text in Chrome (see Issue 579)
// Removing these attributes hides text in Chrome (see Issue 579) } else if (!isWebkit()) {
if (!isWebkit()) {
filter.removeAttribute('x'); filter.removeAttribute('x');
filter.removeAttribute('y'); filter.removeAttribute('y');
filter.removeAttribute('width'); filter.removeAttribute('width');
filter.removeAttribute('height'); filter.removeAttribute('height');
}
} }
}; };
@@ -5739,8 +5752,8 @@ canvas.setBlur = function (val, complete) {
this.getBold = function () { this.getBold = function () {
// should only have one element selected // should only have one element selected
const selected = selectedElements[0]; const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' && if (!isNullish(selected) && selected.tagName === 'text' &&
selectedElements[1] == null) { isNullish(selectedElements[1])) {
return (selected.getAttribute('font-weight') === 'bold'); return (selected.getAttribute('font-weight') === 'bold');
} }
return false; return false;
@@ -5754,8 +5767,8 @@ this.getBold = function () {
*/ */
this.setBold = function (b) { this.setBold = function (b) {
const selected = selectedElements[0]; const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' && if (!isNullish(selected) && selected.tagName === 'text' &&
selectedElements[1] == null) { isNullish(selectedElements[1])) {
changeSelectedAttribute('font-weight', b ? 'bold' : 'normal'); changeSelectedAttribute('font-weight', b ? 'bold' : 'normal');
} }
if (!selectedElements[0].textContent) { if (!selectedElements[0].textContent) {
@@ -5770,8 +5783,8 @@ this.setBold = function (b) {
*/ */
this.getItalic = function () { this.getItalic = function () {
const selected = selectedElements[0]; const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' && if (!isNullish(selected) && selected.tagName === 'text' &&
selectedElements[1] == null) { isNullish(selectedElements[1])) {
return (selected.getAttribute('font-style') === 'italic'); return (selected.getAttribute('font-style') === 'italic');
} }
return false; return false;
@@ -5785,8 +5798,8 @@ this.getItalic = function () {
*/ */
this.setItalic = function (i) { this.setItalic = function (i) {
const selected = selectedElements[0]; const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' && if (!isNullish(selected) && selected.tagName === 'text' &&
selectedElements[1] == null) { isNullish(selectedElements[1])) {
changeSelectedAttribute('font-style', i ? 'italic' : 'normal'); changeSelectedAttribute('font-style', i ? 'italic' : 'normal');
} }
if (!selectedElements[0].textContent) { if (!selectedElements[0].textContent) {
@@ -5863,7 +5876,7 @@ this.setFontSize = function (val) {
*/ */
this.getText = function () { this.getText = function () {
const selected = selectedElements[0]; const selected = selectedElements[0];
if (selected == null) { return ''; } if (isNullish(selected)) { return ''; }
return selected.textContent; return selected.textContent;
}; };
@@ -5967,7 +5980,7 @@ this.setLinkURL = function (val) {
*/ */
this.setRectRadius = function (val) { this.setRectRadius = function (val) {
const selected = selectedElements[0]; const selected = selectedElements[0];
if (selected != null && selected.tagName === 'rect') { if (!isNullish(selected) && selected.tagName === 'rect') {
const r = selected.getAttribute('rx'); const r = selected.getAttribute('rx');
if (r !== String(val)) { if (r !== String(val)) {
selected.setAttribute('rx', val); selected.setAttribute('rx', val);
@@ -6023,12 +6036,12 @@ this.setSegType = function (newType) {
* Otherwise the resulting path element is returned. * Otherwise the resulting path element is returned.
*/ */
this.convertToPath = function (elem, getBBox) { this.convertToPath = function (elem, getBBox) {
if (elem == null) { if (isNullish(elem)) {
const elems = selectedElements; const elems = selectedElements;
$.each(elems, function (i, elem) { $.each(elems, function (i, el) {
if (elem) { canvas.convertToPath(elem); } if (el) { canvas.convertToPath(el); }
}); });
return; return undefined;
} }
if (getBBox) { if (getBBox) {
return getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions); return getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
@@ -6047,7 +6060,7 @@ this.convertToPath = function (elem, getBBox) {
opacity: curShape.opacity, opacity: curShape.opacity,
visibility: 'hidden' visibility: 'hidden'
}; };
return convertToPath(elem, attrs, addSVGElementFromJson, pathActions, clearSelection, addToSelection, history, addCommandToHistory); return convertToPath(elem, attrs, addSVGElementFromJson, pathActions, clearSelection, addToSelection, hstry, addCommandToHistory);
}; };
/** /**
@@ -6066,11 +6079,11 @@ const changeSelectedAttributeNoUndo = function (attr, newValue, elems) {
elems = elems || selectedElements; elems = elems || selectedElements;
let i = elems.length; let i = elems.length;
const noXYElems = ['g', 'polyline', 'path']; const noXYElems = ['g', 'polyline', 'path'];
const goodGAttrs = ['transform', 'opacity', 'filter']; // const goodGAttrs = ['transform', 'opacity', 'filter'];
while (i--) { while (i--) {
let elem = elems[i]; let elem = elems[i];
if (elem == null) { continue; } if (isNullish(elem)) { continue; }
// Set x,y vals on elements that don't have them // Set x,y vals on elements that don't have them
if ((attr === 'x' || attr === 'y') && noXYElems.includes(elem.tagName)) { if ((attr === 'x' || attr === 'y') && noXYElems.includes(elem.tagName)) {
@@ -6082,10 +6095,10 @@ const changeSelectedAttributeNoUndo = function (attr, newValue, elems) {
} }
// only allow the transform/opacity/filter attribute to change on <g> elements, slightly hacky // only allow the transform/opacity/filter attribute to change on <g> elements, slightly hacky
// TODO: FIXME: This doesn't seem right. Where's the body of this if statement? // TODO: FIXME: Missing statement body
if (elem.tagName === 'g' && goodGAttrs.includes(attr)) {} // if (elem.tagName === 'g' && goodGAttrs.includes(attr)) {}
let oldval = attr === '#text' ? elem.textContent : elem.getAttribute(attr); let oldval = attr === '#text' ? elem.textContent : elem.getAttribute(attr);
if (oldval == null) { oldval = ''; } if (isNullish(oldval)) { oldval = ''; }
if (oldval !== String(newValue)) { if (oldval !== String(newValue)) {
if (attr === '#text') { if (attr === '#text') {
// const oldW = utilsGetBBox(elem).width; // const oldW = utilsGetBBox(elem).width;
@@ -6209,7 +6222,7 @@ this.deleteSelectedElements = function () {
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
const selected = selectedElements[i]; const selected = selectedElements[i];
if (selected == null) { break; } if (isNullish(selected)) { break; }
let parent = selected.parentNode; let parent = selected.parentNode;
let t = selected; let t = selected;
@@ -6406,7 +6419,7 @@ this.groupSelectedElements = function (type, urlArg) {
let i = selectedElements.length; let i = selectedElements.length;
while (i--) { while (i--) {
let elem = selectedElements[i]; let elem = selectedElements[i];
if (elem == null) { continue; } if (isNullish(elem)) { continue; }
if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) { if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) {
elem = elem.parentNode; elem = elem.parentNode;
@@ -6690,7 +6703,7 @@ this.ungroupSelectedElement = function () {
*/ */
this.moveToTopSelectedElement = function () { this.moveToTopSelectedElement = function () {
const [selected] = selectedElements; const [selected] = selectedElements;
if (selected != null) { if (!isNullish(selected)) {
let t = selected; let t = selected;
const oldParent = t.parentNode; const oldParent = t.parentNode;
const oldNextSibling = t.nextSibling; const oldNextSibling = t.nextSibling;
@@ -6713,7 +6726,7 @@ this.moveToTopSelectedElement = function () {
*/ */
this.moveToBottomSelectedElement = function () { this.moveToBottomSelectedElement = function () {
const [selected] = selectedElements; const [selected] = selectedElements;
if (selected != null) { if (!isNullish(selected)) {
let t = selected; let t = selected;
const oldParent = t.parentNode; const oldParent = t.parentNode;
const oldNextSibling = t.nextSibling; const oldNextSibling = t.nextSibling;
@@ -6759,9 +6772,9 @@ this.moveUpDownSelected = function (dir) {
if (this === selected) { if (this === selected) {
foundCur = true; foundCur = true;
} }
return; return true;
} }
closest = this; closest = this; // eslint-disable-line consistent-this
return false; return false;
}); });
if (!closest) { return; } if (!closest) { return; }
@@ -6799,7 +6812,7 @@ this.moveSelectedElements = function (dx, dy, undoable) {
let i = selectedElements.length; let i = selectedElements.length;
while (i--) { while (i--) {
const selected = selectedElements[i]; const selected = selectedElements[i];
if (selected != null) { if (!isNullish(selected)) {
// if (i === 0) { // if (i === 0) {
// selectedBBoxes[0] = utilsGetBBox(selected); // selectedBBoxes[0] = utilsGetBBox(selected);
// } // }
@@ -6867,7 +6880,7 @@ this.cloneSelectedElements = function (x, y) {
selectedElements.sort(sortfunction); selectedElements.sort(sortfunction);
for (i = 0; i < len; ++i) { for (i = 0; i < len; ++i) {
elem = selectedElements[i]; elem = selectedElements[i];
if (elem == null) { break; } if (isNullish(elem)) { break; }
} }
// use slice to quickly get the subset of elements we need // use slice to quickly get the subset of elements we need
const copiedElements = selectedElements.slice(0, i); const copiedElements = selectedElements.slice(0, i);
@@ -6905,7 +6918,7 @@ this.alignSelectedElements = function (type, relativeTo) {
miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE; miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE;
let curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE; let curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE;
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
if (selectedElements[i] == null) { break; } if (isNullish(selectedElements[i])) { break; }
const elem = selectedElements[i]; const elem = selectedElements[i];
bboxes[i] = getStrokedBBoxDefaultVisible([elem]); bboxes[i] = getStrokedBBoxDefaultVisible([elem]);
@@ -6958,7 +6971,7 @@ this.alignSelectedElements = function (type, relativeTo) {
const dx = new Array(len); const dx = new Array(len);
const dy = new Array(len); const dy = new Array(len);
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
if (selectedElements[i] == null) { break; } if (isNullish(selectedElements[i])) { break; }
// const elem = selectedElements[i]; // const elem = selectedElements[i];
const bbox = bboxes[i]; const bbox = bboxes[i];
dx[i] = 0; dx[i] = 0;
@@ -7118,7 +7131,7 @@ this.cycleElement = function (next) {
let elem = false; let elem = false;
const allElems = getVisibleElements(currentGroup || getCurrentDrawing().getCurrentLayer()); const allElems = getVisibleElements(currentGroup || getCurrentDrawing().getCurrentLayer());
if (!allElems.length) { return; } if (!allElems.length) { return; }
if (curElem == null) { if (isNullish(curElem)) {
num = next ? allElems.length - 1 : 0; num = next ? allElems.length - 1 : 0;
elem = allElems[num]; elem = allElems[num];
} else { } else {

View File

@@ -1,3 +1,5 @@
// Todo: Move to own module (and have it import a modular base64 encoder)
import {encode64} from '../utilities.js';
/** /**
* SVG Icon Loader 2.0 * SVG Icon Loader 2.0
* *
@@ -96,18 +98,22 @@ $(function() {
* @param {external:jQuery} $ Its keys include all icon IDs and the values, the icon as a jQuery object * @param {external:jQuery} $ Its keys include all icon IDs and the values, the icon as a jQuery object
* @returns {external:jQuery} The enhanced jQuery object * @returns {external:jQuery} The enhanced jQuery object
*/ */
export default function ($) { export default function jQueryPluginSVGIcons ($) {
const svgIcons = {}; const svgIcons = {};
let fixIDs; let fixIDs;
/**
* List of raster images with each
* key being the SVG icon ID to replace, and the value the image file name
* @typedef {PlainObject.<string, string>} external:jQuery.svgIcons.Fallback
*/
/** /**
* @function external:jQuery.svgIcons * @function external:jQuery.svgIcons
* @param {string} file The location of a local SVG or SVGz file * @param {string} file The location of a local SVG or SVGz file
* @param {PlainObject} [opts] * @param {PlainObject} [opts]
* @param {Float} [opts.w] The icon widths * @param {Float} [opts.w] The icon widths
* @param {Float} [opts.h] The icon heights * @param {Float} [opts.h] The icon heights
* @param {PlainObject.<string, string>} [opts.fallback] List of raster images with each * @param {external:jQuery.svgIcons.Fallback} [opts.fallback]
key being the SVG icon ID to replace, and the value the image file name
* @param {string} [opts.fallback_path] The path to use for all images * @param {string} [opts.fallback_path] The path to use for all images
listed under "fallback" listed under "fallback"
* @param {boolean} [opts.replace] If set to `true`, HTML elements will be replaced by, * @param {boolean} [opts.replace] If set to `true`, HTML elements will be replaced by,
@@ -132,8 +138,10 @@ export default function ($) {
iconW = opts.w || 24, iconW = opts.w || 24,
iconH = opts.h || 24; iconH = opts.h || 24;
let elems, svgdoc, testImg, let elems, svgdoc, testImg,
iconsMade = false, dataLoaded = false, loadAttempts = 0; iconsMade = false,
const isOpera = !!window.opera, dataLoaded = false,
loadAttempts = 0;
const isOpera = Boolean(window.opera),
// ua = navigator.userAgent, // ua = navigator.userAgent,
// isSafari = (ua.includes('Safari/') && !ua.includes('Chrome/')), // isSafari = (ua.includes('Safari/') && !ua.includes('Chrome/')),
dataPre = 'data:image/svg+xml;charset=utf-8;base64,'; dataPre = 'data:image/svg+xml;charset=utf-8;base64,';
@@ -169,24 +177,28 @@ export default function ($) {
$(function () { $(function () {
useFallback(); useFallback();
}); });
} else { } else if (err.responseText) {
if (err.responseText) { svgdoc = parser.parseFromString(err.responseText, 'text/xml');
svgdoc = parser.parseFromString(err.responseText, 'text/xml');
if (!svgdoc.childNodes.length) { if (!svgdoc.childNodes.length) {
$(useFallback);
}
$(function () {
getIcons('ajax');
});
} else {
$(useFallback); $(useFallback);
} }
$(function () {
getIcons('ajax');
});
} else {
$(useFallback);
} }
} }
}); });
} }
/**
*
* @param {"ajax"|0|undefined} evt
* @param {boolean} [noWait]
* @returns {undefined}
*/
function getIcons (evt, noWait) { function getIcons (evt, noWait) {
if (evt !== 'ajax') { if (evt !== 'ajax') {
if (dataLoaded) return; if (dataLoaded) return;
@@ -231,6 +243,14 @@ export default function ($) {
} }
} }
/**
*
* @param {external:jQuery} target
* @param {external:jQuery} icon A wrapped `defs` or Image
* @param {string} id SVG icon ID
* @param {string} setID
* @returns {undefined}
*/
function setIcon (target, icon, id, setID) { function setIcon (target, icon, id, setID) {
if (isOpera) icon.css('visibility', 'hidden'); if (isOpera) icon.css('visibility', 'hidden');
if (opts.replace) { if (opts.replace) {
@@ -249,6 +269,11 @@ export default function ($) {
} }
let holder; let holder;
/**
* @param {external:jQuery} icon A wrapped `defs` or Image
* @param {string} id SVG icon ID
* @returns {undefined}
*/
function addIcon (icon, id) { function addIcon (icon, id) {
if (opts.id_match === undefined || opts.id_match !== false) { if (opts.id_match === undefined || opts.id_match !== false) {
setIcon(holder, icon, id, true); setIcon(holder, icon, id, true);
@@ -256,7 +281,13 @@ export default function ($) {
svgIcons[id] = icon; svgIcons[id] = icon;
} }
function makeIcons (toImage, fallback) { /**
*
* @param {boolean} [toImage]
* @param {external:jQuery.svgIcons.Fallback} [fallback]
* @returns {undefined}
*/
function makeIcons (toImage = false, fallback) {
if (iconsMade) return; if (iconsMade) return;
if (opts.no_img) toImage = false; if (opts.no_img) toImage = false;
@@ -359,13 +390,14 @@ export default function ($) {
let idElems; let idElems;
if (isOpera) { if (isOpera) {
idElems = defs.find('*').filter(function () { idElems = defs.find('*').filter(function () {
return !!this.id; return Boolean(this.id);
}); });
} else { } else {
idElems = defs.find('[id]'); idElems = defs.find('[id]');
} }
const allElems = svgEl[0].getElementsByTagName('*'), len = allElems.length; const allElems = svgEl[0].getElementsByTagName('*'),
len = allElems.length;
idElems.each(function (i) { idElems.each(function (i) {
const {id} = this; const {id} = this;
@@ -409,49 +441,20 @@ export default function ($) {
return svgEl; return svgEl;
}; };
/**
* @returns {undefined}
*/
function useFallback () { function useFallback () {
if (file.includes('.svgz')) { if (file.includes('.svgz')) {
const regFile = file.replace('.svgz', '.svg'); const regFile = file.replace('.svgz', '.svg');
if (window.console) { if (window.console) {
console.log('.svgz failed, trying with .svg'); console.log('.svgz failed, trying with .svg'); // eslint-disable-line no-console
} }
$.svgIcons(regFile, opts); $.svgIcons(regFile, opts);
} else if (opts.fallback) { } else if (opts.fallback) {
makeIcons(false, opts.fallback); makeIcons(false, opts.fallback);
} }
} }
function encode64 (input) {
// base64 strings are 4/3 larger than the original string
if (window.btoa) return window.btoa(input);
const _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const output = new Array(Math.floor((input.length + 2) / 3) * 4);
let i = 0, p = 0;
do {
const chr1 = input.charCodeAt(i++);
const chr2 = input.charCodeAt(i++);
const chr3 = input.charCodeAt(i++);
const enc1 = chr1 >> 2;
const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
let enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output[p++] = _keyStr.charAt(enc1);
output[p++] = _keyStr.charAt(enc2);
output[p++] = _keyStr.charAt(enc3);
output[p++] = _keyStr.charAt(enc4);
} while (i < input.length);
return output.join('');
}
}; };
/** /**

View File

@@ -16,6 +16,10 @@
* including the latest spec changes which were implemented in Firefox 43 and * including the latest spec changes which were implemented in Firefox 43 and
* Chrome 46. * Chrome 46.
*/ */
/* eslint-disable no-shadow, class-methods-use-this */
// Linting: We avoid `no-shadow` as ESLint thinks these are still available globals
// Linting: We avoid `class-methods-use-this` as this is a polyfill that must
// follow the conventions
(() => { (() => {
if (!('SVGPathSeg' in window)) { if (!('SVGPathSeg' in window)) {
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg // Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
@@ -591,7 +595,7 @@ if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.pro
return []; return [];
} }
const owningPathSegList = this; const owningPathSegList = this; // eslint-disable-line consistent-this
class Builder { class Builder {
constructor () { constructor () {
@@ -883,10 +887,10 @@ if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.pro
} case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL: { } case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; const points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1); return new SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1);
} case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS: } case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()}; const points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1); return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1);
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL: } case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber()); return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS: case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber()); return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber());

View File

@@ -12,7 +12,11 @@ import {supportsNativeTransformLists} from './browser.js';
const svgroot = document.createElementNS(NS.SVG, 'svg'); const svgroot = document.createElementNS(NS.SVG, 'svg');
// Helper function. /**
* Helper function to convert `SVGTransform` to a string.
* @param {SVGTransform} xform
* @returns {string}
*/
function transformToString (xform) { function transformToString (xform) {
const m = xform.matrix; const m = xform.matrix;
let text = ''; let text = '';
@@ -114,8 +118,9 @@ let listMap_ = {};
* These methods do not currently raise any exceptions. * These methods do not currently raise any exceptions.
* These methods also do not check that transforms are being inserted. This is basically * These methods also do not check that transforms are being inserted. This is basically
* implementing as much of SVGTransformList that we need to get the job done. * implementing as much of SVGTransformList that we need to get the job done.
* @implements {module:SVGTransformList.SVGEditTransformList}
*/ */
export class SVGTransformList { export class SVGTransformList {// eslint-disable-line no-shadow
/** /**
* @param {Element} elem * @param {Element} elem
*/ */
@@ -170,7 +175,7 @@ export class SVGTransformList {
} else if (name === 'rotate' && values.length === 1) { } else if (name === 'rotate' && values.length === 1) {
values.push(0, 0); values.push(0, 0);
} }
xform[fname].apply(xform, values); xform[fname](...values);
this._list.appendItem(xform); this._list.appendItem(xform);
} }
} }
@@ -179,20 +184,15 @@ export class SVGTransformList {
if (item) { if (item) {
// Check if this transform is already in a transformlist, and // Check if this transform is already in a transformlist, and
// remove it if so. // remove it if so.
let found = false; Object.values(listMap_).some((tl) => {
for (const id in listMap_) {
const tl = listMap_[id];
for (let i = 0, len = tl._xforms.length; i < len; ++i) { for (let i = 0, len = tl._xforms.length; i < len; ++i) {
if (tl._xforms[i] === item) { if (tl._xforms[i] === item) {
found = true;
tl.removeItem(i); tl.removeItem(i);
break; return true;
} }
} }
if (found) { return false;
break; });
}
}
} }
}; };
@@ -330,7 +330,7 @@ export const resetListMap = function () {
* @param {Element} elem - a DOM Element * @param {Element} elem - a DOM Element
* @returns {undefined} * @returns {undefined}
*/ */
export let removeElementFromListMap = function (elem) { export let removeElementFromListMap = function (elem) { // eslint-disable-line import/no-mutable-exports
if (elem.id && listMap_[elem.id]) { if (elem.id && listMap_[elem.id]) {
delete listMap_[elem.id]; delete listMap_[elem.id];
} }
@@ -377,6 +377,7 @@ export const getTransformList = function (elem) {
* @param {module:SVGTransformList.removeElementFromListMap} cb Passed a single argument `elem` * @param {module:SVGTransformList.removeElementFromListMap} cb Passed a single argument `elem`
* @returns {undefined} * @returns {undefined}
*/ */
export const changeRemoveElementFromListMap = function (cb) {
export const changeRemoveElementFromListMap = function (cb) { // eslint-disable-line promise/prefer-await-to-callbacks
removeElementFromListMap = cb; removeElementFromListMap = cb;
}; };

View File

@@ -1,4 +1,9 @@
// http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/ // http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
/**
*
* @param {Event} ev
* @returns {undefined}
*/
function touchHandler (ev) { function touchHandler (ev) {
const {changedTouches} = ev, const {changedTouches} = ev,
first = changedTouches[0]; first = changedTouches[0];
@@ -11,7 +16,7 @@ function touchHandler (ev) {
default: return; default: return;
} }
const {screenX, screenY, clientX, clientY} = first; const {screenX, screenY, clientX, clientY} = first; // eslint-disable-line no-shadow
const simulatedEvent = new MouseEvent(type, { const simulatedEvent = new MouseEvent(type, {
// Event interface // Event interface
bubbles: true, bubbles: true,

View File

@@ -50,3 +50,12 @@
/** /**
* @external Window * @external Window
*/ */
/**
* @external JamilihArray
* @type {GenericArray}
* @property {string} 0 Element name
* @property {PlainObject<string, string>|JamilihArray} [1] Generally a map from an attribute name to attribute value, but also adds event handlers, etc.
* @property {JamilihArray} [2] Children
* @see {@link https://github.com/brettz9/jamilih/}
*/

View File

@@ -7,6 +7,7 @@
*/ */
import {NS} from './namespaces.js'; import {NS} from './namespaces.js';
import {isNullish} from './utilities.js';
const wAttrs = ['x', 'x1', 'cx', 'rx', 'width']; const wAttrs = ['x', 'x1', 'cx', 'rx', 'width'];
const hAttrs = ['y', 'y1', 'cy', 'ry', 'height']; const hAttrs = ['y', 'y1', 'cy', 'ry', 'height'];
@@ -40,7 +41,7 @@ let typeMap_ = {};
*/ */
/** /**
* @function module:units.ElementContainer#getElement * @function module:units.ElementContainer#getElement
* @returns {Element} An element in the container given an id * @returns {?Element} An element in the container given an id
*/ */
/** /**
* @function module:units.ElementContainer#getHeight * @function module:units.ElementContainer#getHeight
@@ -134,8 +135,7 @@ export const getTypeMap = function () {
export const shortFloat = function (val) { export const shortFloat = function (val) {
const digits = elementContainer_.getRoundDigits(); const digits = elementContainer_.getRoundDigits();
if (!isNaN(val)) { if (!isNaN(val)) {
// Note that + converts to Number return Number(Number(val).toFixed(digits));
return +((+val).toFixed(digits));
} }
if (Array.isArray(val)) { if (Array.isArray(val)) {
return shortFloat(val[0]) + ',' + shortFloat(val[1]); return shortFloat(val[0]) + ',' + shortFloat(val[1]);
@@ -304,7 +304,7 @@ export const isValidUnit = function (attr, val, selectedElement) {
// not already present // not already present
try { try {
const elem = elementContainer_.getElement(val); const elem = elementContainer_.getElement(val);
result = (elem == null || elem === selectedElement); result = (isNullish(elem) || elem === selectedElement);
} catch (e) {} } catch (e) {}
return result; return result;
} }

View File

@@ -8,7 +8,7 @@
*/ */
import './svgpathseg.js'; import './svgpathseg.js';
import jqPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr` import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
import {NS} from './namespaces.js'; import {NS} from './namespaces.js';
import {getTransformList} from './svgtransformlist.js'; import {getTransformList} from './svgtransformlist.js';
import {setUnitAttr, getTypeMap} from './units.js'; import {setUnitAttr, getTypeMap} from './units.js';
@@ -22,7 +22,7 @@ import {
} from './browser.js'; } from './browser.js';
// Constants // Constants
const $ = jqPluginSVG(jQuery); const $ = jQueryPluginSVG(jQuery);
// String used to encode base64. // String used to encode base64.
const KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; const KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
@@ -123,7 +123,12 @@ export const dropXMLInteralSubset = (str) => {
export const toXml = function (str) { export const toXml = function (str) {
// &apos; is ok in XML, but not HTML // &apos; is ok in XML, but not HTML
// &gt; does not normally need escaping, though it can if within a CDATA expression (and preceded by "]]") // &gt; does not normally need escaping, though it can if within a CDATA expression (and preceded by "]]")
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;'); // Note: `&apos;` is XML only return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;'); // Note: `&apos;` is XML only
}; };
/** /**
@@ -133,9 +138,9 @@ export const toXml = function (str) {
* @param {string} str - The string to be converted * @param {string} str - The string to be converted
* @returns {string} The converted string * @returns {string} The converted string
*/ */
export const fromXml = function (str) { export function fromXml (str) {
return $('<p/>').html(str).text(); return $('<p/>').html(str).text();
}; }
// This code was written by Tyler Akins and has been placed in the // This code was written by Tyler Akins and has been placed in the
// public domain. It would be nice if you left this header intact. // public domain. It would be nice if you left this header intact.
@@ -150,30 +155,33 @@ export const fromXml = function (str) {
* @param {string} input * @param {string} input
* @returns {string} Base64 output * @returns {string} Base64 output
*/ */
export const encode64 = function (input) { export function encode64 (input) {
// base64 strings are 4/3 larger than the original string // base64 strings are 4/3 larger than the original string
input = encodeUTF8(input); // convert non-ASCII characters input = encodeUTF8(input); // convert non-ASCII characters
// input = convertToXMLReferences(input); // input = convertToXMLReferences(input);
if (window.btoa) { if (window.btoa) {
return window.btoa(input); // Use native if available return window.btoa(input); // Use native if available
} }
const output = []; const output = new Array(Math.floor((input.length + 2) / 3) * 4);
output.length = Math.floor((input.length + 2) / 3) * 4;
let i = 0, p = 0; let i = 0,
p = 0;
do { do {
const chr1 = input.charCodeAt(i++); const chr1 = input.charCodeAt(i++);
const chr2 = input.charCodeAt(i++); const chr2 = input.charCodeAt(i++);
const chr3 = input.charCodeAt(i++); const chr3 = input.charCodeAt(i++);
/* eslint-disable no-bitwise */
const enc1 = chr1 >> 2; const enc1 = chr1 >> 2;
const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
let enc4 = chr3 & 63; let enc4 = chr3 & 63;
/* eslint-enable no-bitwise */
if (isNaN(chr2)) { if (isNaN(chr2)) {
enc3 = enc4 = 64; enc3 = 64;
enc4 = 64;
} else if (isNaN(chr3)) { } else if (isNaN(chr3)) {
enc4 = 64; enc4 = 64;
} }
@@ -185,7 +193,7 @@ export const encode64 = function (input) {
} while (i < input.length); } while (i < input.length);
return output.join(''); return output.join('');
}; }
/** /**
* Converts a string from base64. * Converts a string from base64.
@@ -193,7 +201,7 @@ export const encode64 = function (input) {
* @param {string} input Base64-encoded input * @param {string} input Base64-encoded input
* @returns {string} Decoded output * @returns {string} Decoded output
*/ */
export const decode64 = function (input) { export function decode64 (input) {
if (window.atob) { if (window.atob) {
return decodeUTF8(window.atob(input)); return decodeUTF8(window.atob(input));
} }
@@ -210,30 +218,32 @@ export const decode64 = function (input) {
const enc3 = KEYSTR.indexOf(input.charAt(i++)); const enc3 = KEYSTR.indexOf(input.charAt(i++));
const enc4 = KEYSTR.indexOf(input.charAt(i++)); const enc4 = KEYSTR.indexOf(input.charAt(i++));
/* eslint-disable no-bitwise */
const chr1 = (enc1 << 2) | (enc2 >> 4); const chr1 = (enc1 << 2) | (enc2 >> 4);
const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
const chr3 = ((enc3 & 3) << 6) | enc4; const chr3 = ((enc3 & 3) << 6) | enc4;
/* eslint-enable no-bitwise */
output += String.fromCharCode(chr1); output += String.fromCharCode(chr1);
if (enc3 !== 64) { if (enc3 !== 64) {
output = output + String.fromCharCode(chr2); output += String.fromCharCode(chr2);
} }
if (enc4 !== 64) { if (enc4 !== 64) {
output = output + String.fromCharCode(chr3); output += String.fromCharCode(chr3);
} }
} while (i < input.length); } while (i < input.length);
return decodeUTF8(output); return decodeUTF8(output);
}; }
/** /**
* @function module:utilities.decodeUTF8 * @function module:utilities.decodeUTF8
* @param {string} argString * @param {string} argString
* @returns {string} * @returns {string}
*/ */
export const decodeUTF8 = function (argString) { export function decodeUTF8 (argString) {
return decodeURIComponent(escape(argString)); return decodeURIComponent(escape(argString));
}; }
// codedread:does not seem to work with webkit-based browsers on OSX // Brettz9: please test again as function upgraded // codedread:does not seem to work with webkit-based browsers on OSX // Brettz9: please test again as function upgraded
/** /**
@@ -255,7 +265,8 @@ export const dataURLToObjectURL = function (dataurl) {
if (typeof Uint8Array === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL) { if (typeof Uint8Array === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL) {
return ''; return '';
} }
const arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], const arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]); bstr = atob(arr[1]);
let n = bstr.length; let n = bstr.length;
const u8arr = new Uint8Array(n); const u8arr = new Uint8Array(n);
@@ -430,7 +441,7 @@ export const getUrlFromAttr = function (attrVal) {
* @param {Element} elem * @param {Element} elem
* @returns {string} The given element's `xlink:href` value * @returns {string} The given element's `xlink:href` value
*/ */
export let getHref = function (elem) { export let getHref = function (elem) { // eslint-disable-line import/no-mutable-exports
return elem.getAttributeNS(NS.XLINK, 'href'); return elem.getAttributeNS(NS.XLINK, 'href');
}; };
@@ -441,7 +452,7 @@ export let getHref = function (elem) {
* @param {string} val * @param {string} val
* @returns {undefined} * @returns {undefined}
*/ */
export let setHref = function (elem, val) { export let setHref = function (elem, val) { // eslint-disable-line import/no-mutable-exports
elem.setAttributeNS(NS.XLINK, 'xlink:href', val); elem.setAttributeNS(NS.XLINK, 'xlink:href', val);
}; };
@@ -484,6 +495,15 @@ export const getPathBBox = function (path) {
const start = seglist.getItem(0); const start = seglist.getItem(0);
let P0 = [start.x, start.y]; let P0 = [start.x, start.y];
const getCalc = function (j, P1, P2, P3) {
return function (t) {
return Math.pow(1 - t, 3) * P0[j] +
3 * Math.pow(1 - t, 2) * t * P1[j] +
3 * (1 - t) * Math.pow(t, 2) * P2[j] +
Math.pow(t, 3) * P3[j];
};
};
for (let i = 0; i < tot; i++) { for (let i = 0; i < tot; i++) {
const seg = seglist.getItem(i); const seg = seglist.getItem(i);
@@ -499,21 +519,14 @@ export const getPathBBox = function (path) {
P3 = [seg.x, seg.y]; P3 = [seg.x, seg.y];
for (let j = 0; j < 2; j++) { for (let j = 0; j < 2; j++) {
const calc = function (t) { const calc = getCalc(j, P1, P2, P3);
return Math.pow(1 - t, 3) * P0[j] +
3 * Math.pow(1 - t, 2) * t * P1[j] +
3 * (1 - t) * Math.pow(t, 2) * P2[j] +
Math.pow(t, 3) * P3[j];
};
const b = 6 * P0[j] - 12 * P1[j] + 6 * P2[j]; const b = 6 * P0[j] - 12 * P1[j] + 6 * P2[j];
const a = -3 * P0[j] + 9 * P1[j] - 9 * P2[j] + 3 * P3[j]; const a = -3 * P0[j] + 9 * P1[j] - 9 * P2[j] + 3 * P3[j];
const c = 3 * P1[j] - 3 * P0[j]; const c = 3 * P1[j] - 3 * P0[j];
if (a === 0) { if (a === 0) {
if (b === 0) { if (b === 0) { continue; }
continue;
}
const t = -c / b; const t = -c / b;
if (t > 0 && t < 1) { if (t > 0 && t < 1) {
bounds[j].push(calc(t)); bounds[j].push(calc(t));
@@ -612,15 +625,15 @@ export const getBBox = function (elem) {
selected.textContent = 'a'; // Some character needed for the selector to use. selected.textContent = 'a'; // Some character needed for the selector to use.
ret = selected.getBBox(); ret = selected.getBBox();
selected.textContent = ''; selected.textContent = '';
} else { } else if (selected.getBBox) {
if (selected.getBBox) { ret = selected.getBBox(); } ret = selected.getBBox();
} }
break; break;
case 'path': case 'path':
if (!supportsPathBBox()) { if (!supportsPathBBox()) {
ret = getPathBBox(selected); ret = getPathBBox(selected);
} else { } else if (selected.getBBox) {
if (selected.getBBox) { ret = selected.getBBox(); } ret = selected.getBBox();
} }
break; break;
case 'g': case 'g':
@@ -719,12 +732,13 @@ export const getPathDFromElement = function (elem) {
let d, a, rx, ry; let d, a, rx, ry;
switch (elem.tagName) { switch (elem.tagName) {
case 'ellipse': case 'ellipse':
case 'circle': case 'circle': {
a = $(elem).attr(['rx', 'ry', 'cx', 'cy']); a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
const {cx, cy} = a; const {cx, cy} = a;
({rx, ry} = a); ({rx, ry} = a);
if (elem.tagName === 'circle') { if (elem.tagName === 'circle') {
rx = ry = $(elem).attr('r'); ry = $(elem).attr('r');
rx = ry;
} }
d = getPathDFromSegments([ d = getPathDFromSegments([
@@ -736,7 +750,7 @@ export const getPathDFromElement = function (elem) {
['Z', []] ['Z', []]
]); ]);
break; break;
case 'path': } case 'path':
d = elem.getAttribute('d'); d = elem.getAttribute('d');
break; break;
case 'line': case 'line':
@@ -749,11 +763,13 @@ export const getPathDFromElement = function (elem) {
case 'polygon': case 'polygon':
d = 'M' + elem.getAttribute('points') + ' Z'; d = 'M' + elem.getAttribute('points') + ' Z';
break; break;
case 'rect': case 'rect': {
const r = $(elem).attr(['rx', 'ry']); const r = $(elem).attr(['rx', 'ry']);
({rx, ry} = r); ({rx, ry} = r);
const b = elem.getBBox(); const b = elem.getBBox();
const {x, y} = b, w = b.width, h = b.height; const {x, y} = b,
w = b.width,
h = b.height;
num = 4 - num; // Why? Because! num = 4 - num; // Why? Because!
if (!rx && !ry) { if (!rx && !ry) {
@@ -781,7 +797,7 @@ export const getPathDFromElement = function (elem) {
]); ]);
} }
break; break;
default: } default:
break; break;
} }
@@ -826,11 +842,11 @@ export const getBBoxOfElementAsPath = function (elem, addSVGElementFromJson, pat
path.setAttribute('transform', eltrans); path.setAttribute('transform', eltrans);
} }
const parent = elem.parentNode; const {parentNode} = elem;
if (elem.nextSibling) { if (elem.nextSibling) {
elem.before(path); elem.before(path);
} else { } else {
parent.append(path); parentNode.append(path);
} }
const d = getPathDFromElement(elem); const d = getPathDFromElement(elem);
@@ -861,12 +877,12 @@ export const getBBoxOfElementAsPath = function (elem, addSVGElementFromJson, pat
* @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions. * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
* @param {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} clearSelection - see [canvas.clearSelection]{@link module:svgcanvas.SvgCanvas#clearSelection} * @param {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} clearSelection - see [canvas.clearSelection]{@link module:svgcanvas.SvgCanvas#clearSelection}
* @param {module:path.EditorContext#addToSelection} addToSelection - see [canvas.addToSelection]{@link module:svgcanvas.SvgCanvas#addToSelection} * @param {module:path.EditorContext#addToSelection} addToSelection - see [canvas.addToSelection]{@link module:svgcanvas.SvgCanvas#addToSelection}
* @param {module:history} history - see history module * @param {module:history} hstry - see history module
* @param {module:path.EditorContext#addCommandToHistory|module:draw.DrawCanvasInit#addCommandToHistory} addCommandToHistory - see [canvas.addCommandToHistory]{@link module:svgcanvas~addCommandToHistory} * @param {module:path.EditorContext#addCommandToHistory|module:draw.DrawCanvasInit#addCommandToHistory} addCommandToHistory - see [canvas.addCommandToHistory]{@link module:svgcanvas~addCommandToHistory}
* @returns {SVGPathElement|null} The converted path element or null if the DOM element was not recognized. * @returns {SVGPathElement|null} The converted path element or null if the DOM element was not recognized.
*/ */
export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathActions, clearSelection, addToSelection, history, addCommandToHistory) { export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathActions, clearSelection, addToSelection, hstry, addCommandToHistory) {
const batchCmd = new history.BatchCommand('Convert element to Path'); const batchCmd = new hstry.BatchCommand('Convert element to Path');
// Any attribute on the element not covered by the passed-in attributes // Any attribute on the element not covered by the passed-in attributes
attrs = $.extend({}, attrs, getExtraAttributesForConvertToPath(elem)); attrs = $.extend({}, attrs, getExtraAttributesForConvertToPath(elem));
@@ -882,11 +898,11 @@ export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathA
} }
const {id} = elem; const {id} = elem;
const parent = elem.parentNode; const {parentNode} = elem;
if (elem.nextSibling) { if (elem.nextSibling) {
elem.before(path); elem.before(path);
} else { } else {
parent.append(path); parentNode.append(path);
} }
const d = getPathDFromElement(elem); const d = getPathDFromElement(elem);
@@ -939,14 +955,14 @@ export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathA
* getBBox then apply the angle and any transforms. * getBBox then apply the angle and any transforms.
* *
* @param {Float} angle - The rotation angle in degrees * @param {Float} angle - The rotation angle in degrees
* @param {boolean} hasMatrixTransform - True if there is a matrix transform * @param {boolean} hasAMatrixTransform - True if there is a matrix transform
* @returns {boolean} True if the bbox can be optimized. * @returns {boolean} True if the bbox can be optimized.
*/ */
function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasMatrixTransform) { function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasAMatrixTransform) {
const angleModulo90 = angle % 90; const angleModulo90 = angle % 90;
const closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99; const closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99;
const closeTo0 = angleModulo90 > -0.001 && angleModulo90 < 0.001; const closeTo0 = angleModulo90 > -0.001 && angleModulo90 < 0.001;
return hasMatrixTransform || !(closeTo0 || closeTo90); return hasAMatrixTransform || !(closeTo0 || closeTo90);
} }
/** /**
@@ -979,13 +995,15 @@ export const getBBoxWithTransform = function (elem, addSVGElementFromJson, pathA
// TODO: why ellipse and not circle // TODO: why ellipse and not circle
const elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon']; const elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon'];
if (elemNames.includes(elem.tagName)) { if (elemNames.includes(elem.tagName)) {
bb = goodBb = getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions); goodBb = getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
bb = goodBb;
} else if (elem.tagName === 'rect') { } else if (elem.tagName === 'rect') {
// Look for radius // Look for radius
const rx = elem.getAttribute('rx'); const rx = elem.getAttribute('rx');
const ry = elem.getAttribute('ry'); const ry = elem.getAttribute('ry');
if (rx || ry) { if (rx || ry) {
bb = goodBb = getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions); goodBb = getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
bb = goodBb;
} }
} }
} }
@@ -1012,7 +1030,12 @@ export const getBBoxWithTransform = function (elem, addSVGElementFromJson, pathA
return bb; return bb;
}; };
// TODO: This is problematic with large stroke-width and, for example, a single horizontal line. The calculated BBox extends way beyond left and right sides. /**
* @param {Element} elem
* @returns {Float}
* @todo This is problematic with large stroke-width and, for example, a single
* horizontal line. The calculated BBox extends way beyond left and right sides.
*/
function getStrokeOffsetForBBox (elem) { function getStrokeOffsetForBBox (elem) {
const sw = elem.getAttribute('stroke-width'); const sw = elem.getAttribute('stroke-width');
return (!isNaN(sw) && elem.getAttribute('stroke') !== 'none') ? sw / 2 : 0; return (!isNaN(sw) && elem.getAttribute('stroke') !== 'none') ? sw / 2 : 0;
@@ -1090,16 +1113,16 @@ export const getStrokedBBox = function (elems, addSVGElementFromJson, pathAction
* Note that 0-opacity, off-screen etc elements are still considered "visible" * Note that 0-opacity, off-screen etc elements are still considered "visible"
* for this function. * for this function.
* @function module:utilities.getVisibleElements * @function module:utilities.getVisibleElements
* @param {Element} parent - The parent DOM element to search within * @param {Element} parentElement - The parent DOM element to search within
* @returns {Element[]} All "visible" elements. * @returns {Element[]} All "visible" elements.
*/ */
export const getVisibleElements = function (parent) { export const getVisibleElements = function (parentElement) {
if (!parent) { if (!parentElement) {
parent = $(editorContext_.getSVGContent()).children(); // Prevent layers from being included parentElement = $(editorContext_.getSVGContent()).children(); // Prevent layers from being included
} }
const contentElems = []; const contentElems = [];
$(parent).children().each(function (i, elem) { $(parentElement).children().each(function (i, elem) {
if (elem.getBBox) { if (elem.getBBox) {
contentElems.push(elem); contentElems.push(elem);
} }
@@ -1148,7 +1171,7 @@ export const getRotationAngleFromTransformList = function (tlist, toRad) {
* @param {boolean} [toRad=false] - When true returns the value in radians rather than degrees * @param {boolean} [toRad=false] - When true returns the value in radians rather than degrees
* @returns {Float} The angle in degrees or radians * @returns {Float} The angle in degrees or radians
*/ */
export let getRotationAngle = function (elem, toRad) { export let getRotationAngle = function (elem, toRad) { // eslint-disable-line import/no-mutable-exports
const selected = elem || editorContext_.getSelectedElements()[0]; const selected = elem || editorContext_.getSelectedElements()[0];
// find the rotation transform (if any) and set it // find the rotation transform (if any) and set it
const tlist = getTransformList(selected); const tlist = getTransformList(selected);
@@ -1169,13 +1192,14 @@ export const getRefElem = function (attrVal) {
* Get a DOM element by ID within the SVG root element. * Get a DOM element by ID within the SVG root element.
* @function module:utilities.getElem * @function module:utilities.getElem
* @param {string} id - String with the element's new ID * @param {string} id - String with the element's new ID
* @returns {Element} * @returns {?Element}
*/ */
export const getElem = (supportsSelectors()) export const getElem = (supportsSelectors())
? function (id) { ? function (id) {
// querySelector lookup // querySelector lookup
return svgroot_.querySelector('#' + id); return svgroot_.querySelector('#' + id);
} : supportsXpath() }
: supportsXpath()
? function (id) { ? function (id) {
// xpath lookup // xpath lookup
return domdoc_.evaluate( return domdoc_.evaluate(
@@ -1183,7 +1207,8 @@ export const getElem = (supportsSelectors())
domcontainer_, domcontainer_,
function () { return NS.SVG; }, function () { return NS.SVG; },
9, 9,
null).singleNodeValue; null
).singleNodeValue;
} }
: function (id) { : function (id) {
// jQuery lookup: twice as slow as xpath in FF // jQuery lookup: twice as slow as xpath in FF
@@ -1242,12 +1267,11 @@ export const cleanupElement = function (element) {
delete defaults.ry; delete defaults.ry;
} }
for (const attr in defaults) { Object.entries(defaults).forEach(([attr, val]) => {
const val = defaults[attr];
if (element.getAttribute(attr) === String(val)) { if (element.getAttribute(attr) === String(val)) {
element.removeAttribute(attr); element.removeAttribute(attr);
} }
} });
}; };
/** /**
@@ -1343,6 +1367,15 @@ export const copyElem = function (el, getNextId) {
return newEl; return newEl;
}; };
/**
* Whether a value is `null` or `undefined`.
* @param {Any} val
* @returns {boolean}
*/
export const isNullish = (val) => {
return val === null || val === undefined;
};
/** /**
* Overwrite methods for unit testing. * Overwrite methods for unit testing.
* @function module:utilities.mock * @function module:utilities.mock

View File

@@ -1,4 +1,5 @@
import svgEditor from './svg-editor.js'; import svgEditor from './svg-editor.js';
svgEditor.setConfig({ svgEditor.setConfig({
canvasName: 'xdomain', // Namespace this canvasName: 'xdomain', // Namespace this
allowedOrigins: ['*'] allowedOrigins: ['*']

View File

@@ -1,3 +1,7 @@
/**
* Opens the dialog with the SVG Editor.
* @returns {undefined}
*/
function startSvgEdit () { // eslint-disable-line no-unused-vars function startSvgEdit () { // eslint-disable-line no-unused-vars
const url = 'chrome://svg-edit/content/editor/svg-editor.html'; const url = 'chrome://svg-edit/content/editor/svg-editor.html';
window.openDialog(url, 'SVG Editor', 'width=1024,height=700,menubar=no,toolbar=no'); window.openDialog(url, 'SVG Editor', 'width=1024,height=700,menubar=no,toolbar=no');

View File

@@ -3,6 +3,11 @@
jQuery(function () { jQuery(function () {
if (!window.Components) return; if (!window.Components) return;
/**
* Offer choice of file.
* @param {boolean} readflag
* @returns {nsILocalFile}
*/
function mozFilePicker (readflag) { function mozFilePicker (readflag) {
const fp = window.Components.classes['@mozilla.org/filepicker;1'] const fp = window.Components.classes['@mozilla.org/filepicker;1']
.createInstance(Components.interfaces.nsIFilePicker); .createInstance(Components.interfaces.nsIFilePicker);
@@ -19,16 +24,16 @@ jQuery(function () {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
const file = mozFilePicker(true); const file = mozFilePicker(true);
if (!file) { if (!file) {
return null; return;
} }
const inputStream = Components.classes['@mozilla.org/network/file-input-stream;1'].createInstance(Components.interfaces.nsIFileInputStream); const inputStream = Components.classes['@mozilla.org/network/file-input-stream;1'].createInstance(Components.interfaces.nsIFileInputStream);
inputStream.init(file, 0x01, parseInt('00004', 8), null); inputStream.init(file, 0x01, 0o00004, null);
const sInputStream = Components.classes['@mozilla.org/scriptableinputstream;1'].createInstance(Components.interfaces.nsIScriptableInputStream); const sInputStream = Components.classes['@mozilla.org/scriptableinputstream;1'].createInstance(Components.interfaces.nsIScriptableInputStream);
sInputStream.init(inputStream); sInputStream.init(inputStream);
svgCanvas.setSvgString(sInputStream.read(sInputStream.available())); svgCanvas.setSvgString(sInputStream.read(sInputStream.available()));
} catch (e) { } catch (e) {
console.log('Exception while attempting to load' + e); console.log('Exception while attempting to load' + e); // eslint-disable-line no-console
} }
}, },
save (svg, str) { save (svg, str) {
@@ -39,16 +44,16 @@ jQuery(function () {
} }
if (!file.exists()) { if (!file.exists()) {
file.create(0, parseInt('0664', 8)); file.create(0, 0o0664);
} }
const out = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream); const out = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream);
out.init(file, 0x20 | 0x02, parseInt('00004', 8), null); out.init(file, 0x20 | 0x02, 0o00004, null); // eslint-disable-line no-bitwise
out.write(str, str.length); out.write(str, str.length);
out.flush(); out.flush();
out.close(); out.close();
} catch (e) { } catch (e) {
alert(e); alert(e); // eslint-disable-line no-alert
} }
} }
}); });

View File

@@ -3,6 +3,7 @@
* @todo Fork find-in-files to get ignore pattern support * @todo Fork find-in-files to get ignore pattern support
*/ */
const fif = require('find-in-files'); const fif = require('find-in-files');
(async () => { (async () => {
/** /**
* @typedef {PlainObject} FileResult * @typedef {PlainObject} FileResult
@@ -15,7 +16,7 @@ const fileMatchPatterns = ['editor'];
* Keys are file name strings * Keys are file name strings
* @type {Object.<string, FileResult>} * @type {Object.<string, FileResult>}
*/ */
let results = await Promise.all(fileMatchPatterns.map(async (fileMatchPattern) => { let results = await Promise.all(fileMatchPatterns.map((fileMatchPattern) => {
return fif.find( return fif.find(
{ {
// We grab to the end of the line as the `line` result for `find-in-files` // We grab to the end of the line as the `line` result for `find-in-files`
@@ -46,22 +47,28 @@ Object.entries(results).forEach(([file, res]) => {
}); });
*/ */
}); });
console.log(`${output}\nTotal failures found: ${total}.\n`); console.log(`${output}\nTotal failures found: ${total}.\n`); // eslint-disable-line no-console
/**
* @external FindInFilesResult
* @type {PlainObject}
* @property {string[]} matches The matched strings
* @property {Integer} count The number of matches
* @property {string[]} line The lines that were matched. The docs mistakenly indicate the property is named `lines`; see {@link https://github.com/kaesetoast/find-in-files/pull/19}.
*/
/**
* Eliminates known false matches against overly generic types.
* @param {string} file
* @param {external:FindInFilesResult} res
* @returns {undefined}
*/
function reduceFalseMatches (file, res) { function reduceFalseMatches (file, res) {
switch (file) { switch (file) {
case 'editor/external/jamilih/jml-es.js': case 'editor/external/jamilih/jml-es.js':
case 'editor/xdomain-svgedit-config-iife.js': // Ignore case 'editor/xdomain-svgedit-config-iife.js': // Ignore
res.line = []; res.line = [];
break; break;
case 'editor/external/dynamic-import-polyfill/importModule.js':
res.line = res.line.filter((line) => {
return ![
'* @returns {*} The return depends on the export of the targeted module.',
'* @returns {ArbitraryModule|*} The return depends on the export of the targeted module.'
].includes(line);
});
break;
case 'editor/embedapi.js': case 'editor/embedapi.js':
res.line = res.line.filter((line) => { res.line = res.line.filter((line) => {
return ![ return ![

Some files were not shown because too many files have changed in this diff Show More