- 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
master
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": {
"sourceType": "module"
},
"plugins": ["compat", "qunit", "testcafe", "jsdoc", "markdown"],
"plugins": ["compat", "qunit", "testcafe", "jsdoc", "markdown", "import", "node", "promise"],
"env": {
"node": false,
"browser": true
@ -26,24 +26,296 @@
"allowAugmentsExtendsWithoutParam": true
}
},
"overrides": [{
"files": ["**/*.md"],
"rules": {
"no-undef": ["off"],
"no-unused-vars": ["warn"],
"padded-blocks": ["off"]
"overrides": [
{
"files": ["editor/locale/lang.*.js"],
"rules": {
"import/no-anonymous-default-export": ["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": {
"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}],
"object-property-newline": 0,
"one-var": 0,
"no-var": 2,
"prefer-const": 2,
"no-extra-semi": 2,
"quote-props": [2, "as-needed"],
"object-property-newline": ["off"],
"one-var": ["off"],
"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-tag-names": 1,
"jsdoc/check-types": 1,
@ -61,7 +333,7 @@
"jsdoc/no-undefined-types": ["off"],
"jsdoc/valid-types": ["error"],
"valid-jsdoc": ["off", {
"valid-jsdoc": ["error", {
"prefer": {
"arg": "param",
"argument": "param",
@ -81,6 +353,16 @@
"matchDescription": "^([A-Z][\\s\\S]*[.`?!])?$",
"requireParamDescription": 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 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');
// 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 isGecko_ = userAgent.includes('Gecko/');
const isIE_ = userAgent.includes('MSIE');
@ -39,11 +39,11 @@ const isMac_ = userAgent.includes('Macintosh');
const isTouch_ = 'ontouchstart' in window;
const supportsSelectors_ = (function () {
return !!svg.querySelector;
return Boolean(svg.querySelector);
}());
const supportsXpath_ = (function () {
return !!document.evaluate;
return Boolean(document.evaluate);
}());
// segList functions (for FF1.5 and 2.0)

View File

@ -11,6 +11,15 @@
import RGBColor from './rgbcolor.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;
/**
@ -63,7 +72,7 @@ export const setStackBlurCanvasRGBA = (cb) => {
*/
export const canvg = function (target, s, opts) {
// no parameters
if (target == null && s == null && opts == null) {
if (isNullish(target) && isNullish(s) && isNullish(opts)) {
const svgTags = document.querySelectorAll('svg');
return Promise.all([...svgTags].map((svgTag) => {
const c = document.createElement('canvas');
@ -82,7 +91,7 @@ export const canvg = function (target, s, opts) {
}
// store class on canvas
if (target.svg != null) target.svg.stop();
if (!isNullish(target.svg)) target.svg.stop();
const svg = build(opts || {});
// 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')) {
@ -113,9 +122,9 @@ function build (opts) {
svg.FRAMERATE = 30;
svg.MAX_VIRTUAL_PIXELS = 30000;
svg.log = function (msg) {};
svg.log = function (msg) { /* */ };
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
@ -139,7 +148,7 @@ function build (opts) {
width () { return this.Current().width; },
height () { return this.Current().height; },
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 === 'y') return this.height();
return Math.sqrt(
@ -190,13 +199,12 @@ function build (opts) {
if (window.DOMParser) {
const parser = new DOMParser();
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
@ -226,7 +234,7 @@ function build (opts) {
}
hasValue () {
return (this.value != null && this.value !== '');
return (!isNullish(this.value) && this.value !== '');
}
// return the numerical value of the property
@ -234,8 +242,8 @@ function build (opts) {
if (!this.hasValue()) return 0;
let n = parseFloat(this.value);
if ((this.value + '').match(/%$/)) {
n = n / 100.0;
if (String(this.value).match(/%$/)) {
n /= 100.0;
}
return n;
}
@ -254,7 +262,7 @@ function build (opts) {
// augment the current color value with the opacity
addOpacity (opacityProp) {
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);
if (color.ok) {
newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacityProp.numValue() + ')';
@ -280,12 +288,12 @@ function build (opts) {
let def = this.getDefinition();
// gradient
if (def != null && def.createGradient) {
if (!isNullish(def) && def.createGradient) {
return def.createGradient(svg.ctx, e, opacityProp);
}
// pattern
if (def != null && def.createPattern) {
if (!isNullish(def) && def.createPattern) {
if (def.getHrefAttribute().hasValue()) {
const pt = def.attribute('patternTransform');
def = def.getHrefAttribute().getDefinition();
@ -312,14 +320,13 @@ function build (opts) {
}
getUnits () {
const s = this.value + '';
return s.replace(/[0-9.-]/g, '');
return String(this.value).replace(/[0-9.-]/g, '');
}
// get the length as pixels
toPixels (viewPort, processPercent) {
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(/ex$/)) return this.numValue() * this.getEM(viewPort) / 2.0;
if (s.match(/px$/)) return this.numValue();
@ -338,7 +345,7 @@ function build (opts) {
// get the time as milliseconds
toMilliseconds () {
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(/ms$/)) return this.numValue();
return this.numValue();
@ -348,7 +355,7 @@ function build (opts) {
// get the angle as radians
toRadians () {
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(/grad$/)) return this.numValue() * (Math.PI / 200.0);
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',
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 {
fontFamily: fontFamily || f.fontFamily,
fontSize: fontSize || f.fontSize,
@ -474,7 +481,7 @@ function build (opts) {
height () { return this.y2 - this.y1; }
addPoint (x, y) {
if (x != null) {
if (!isNullish(x)) {
if (isNaN(this.x1) || isNaN(this.x2)) {
this.x1 = x;
this.x2 = x;
@ -483,7 +490,7 @@ function build (opts) {
if (x > this.x2) this.x2 = x;
}
if (y != null) {
if (!isNullish(y)) {
if (isNaN(this.y1) || isNaN(this.y2)) {
this.y1 = y;
this.y2 = y;
@ -710,7 +717,7 @@ function build (opts) {
else if (meetOrSlice === 'slice') ctx.scale(scaleMax, scaleMax);
// translate
ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
ctx.translate(isNullish(minX) ? 0 : -minX, isNullish(minY) ? 0 : -minY);
};
// elements
@ -724,7 +731,7 @@ function build (opts) {
this.attributes = {};
this.styles = {};
this.children = [];
if (node != null && node.nodeType === 1) { // ELEMENT_NODE
if (!isNullish(node) && node.nodeType === 1) { // ELEMENT_NODE
// add children
[...node.childNodes].forEach((childNode) => {
if (childNode.nodeType === 1) {
@ -749,7 +756,7 @@ function build (opts) {
});
// add tag styles
let styles = svg.Styles[node.nodeName];
if (styles != null) {
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
@ -760,13 +767,13 @@ function build (opts) {
const classes = svg.compressSpaces(this.attribute('class').value).split(' ');
classes.forEach((clss) => {
styles = svg.Styles['.' + clss];
if (styles != null) {
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
}
styles = svg.Styles[node.nodeName + '.' + clss];
if (styles != null) {
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
@ -777,7 +784,7 @@ function build (opts) {
// add id styles
if (this.attribute('id').hasValue()) {
const styles = svg.Styles['#' + this.attribute('id').value];
if (styles != null) {
if (!isNullish(styles)) {
for (const name in styles) {
this.styles[name] = styles[name];
}
@ -799,7 +806,7 @@ function build (opts) {
// add id
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;
}
}
@ -809,7 +816,7 @@ function build (opts) {
// get or create attribute
attribute (name, createIfNotExists) {
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; }
return a || svg.EmptyProperty;
@ -827,19 +834,19 @@ function build (opts) {
// get or create style, crawls up node tree
style (name, createIfNotExists, skipAncestors) {
let s = this.styles[name];
if (s != null) return s;
if (!isNullish(s)) return s;
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
return a;
}
if (skipAncestors !== true) {
const p = this.parent;
if (p != null) {
if (!isNullish(p)) {
const ps = p.style(name);
if (ps != null && ps.hasValue()) {
if (!isNullish(ps) && ps.hasValue()) {
return ps;
}
}
@ -860,10 +867,10 @@ function build (opts) {
ctx.save();
if (this.attribute('mask').hasValue()) { // mask
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
const filter = this.style('filter').getDefinition();
if (filter != null) filter.apply(ctx, this);
if (!isNullish(filter)) filter.apply(ctx, this);
} else {
this.setContext(ctx);
this.renderChildren(ctx);
@ -903,7 +910,7 @@ function build (opts) {
// fill
if (this.style('fill').isUrlDefinition()) {
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()) {
const fillStyle = this.style('fill');
if (fillStyle.value === 'currentColor') fillStyle.value = this.style('color').value;
@ -918,7 +925,7 @@ function build (opts) {
// stroke
if (this.style('stroke').isUrlDefinition()) {
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()) {
const strokeStyle = this.style('stroke');
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-weight').value,
this.style('font-size').hasValue() ? this.style('font-size').toPixels() + 'px' : '',
this.style('font-family').value).toString();
this.style('font-family').value
).toString();
}
// transform
@ -975,7 +983,7 @@ function build (opts) {
// clip
if (this.style('clip-path', false, true).hasValue()) {
const clip = this.style('clip-path', false, true).getDefinition();
if (clip != null) clip.apply(ctx);
if (!isNullish(clip)) clip.apply(ctx);
}
// opacity
@ -987,7 +995,7 @@ function build (opts) {
svg.Element.PathElementBase = class extends svg.Element.RenderedElementBase {
path (ctx) {
if (ctx != null) ctx.beginPath();
if (!isNullish(ctx)) ctx.beginPath();
return new svg.BoundingBox();
}
@ -1004,7 +1012,7 @@ function build (opts) {
if (ctx.strokeStyle !== '') ctx.stroke();
const markers = this.getMarkers();
if (markers != null) {
if (!isNullish(markers)) {
if (this.style('marker-start').isUrlDefinition()) {
const marker = this.style('marker-start').getDefinition();
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;
rx = Math.min(rx, width / 2.0);
ry = Math.min(ry, height / 2.0);
if (ctx != null) {
if (!isNullish(ctx)) {
ctx.beginPath();
ctx.moveTo(x + rx, y);
ctx.lineTo(x + width - rx, y);
@ -1148,7 +1156,7 @@ function build (opts) {
const cy = this.attribute('cy').toPixels('y');
const r = this.attribute('r').toPixels();
if (ctx != null) {
if (!isNullish(ctx)) {
ctx.beginPath();
ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
ctx.closePath();
@ -1167,7 +1175,7 @@ function build (opts) {
const cx = this.attribute('cx').toPixels('x');
const cy = this.attribute('cy').toPixels('y');
if (ctx != null) {
if (!isNullish(ctx)) {
ctx.beginPath();
ctx.moveTo(cx, cy - ry);
ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
@ -1186,13 +1194,14 @@ function build (opts) {
getPoints () {
return [
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) {
const points = this.getPoints();
if (ctx != null) {
if (!isNullish(ctx)) {
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
@ -1218,14 +1227,14 @@ function build (opts) {
path (ctx) {
const {x, y} = this.points[0];
const bb = new svg.BoundingBox(x, y);
if (ctx != null) {
if (!isNullish(ctx)) {
ctx.beginPath();
ctx.moveTo(x, y);
}
for (let i = 1; i < this.points.length; i++) {
const {x, y} = this.points[i];
bb.addPoint(x, y);
if (ctx != null) ctx.lineTo(x, y);
if (!isNullish(ctx)) ctx.lineTo(x, y);
}
return bb;
}
@ -1244,7 +1253,7 @@ function build (opts) {
svg.Element.polygon = class extends svg.Element.polyline {
path (ctx) {
const bb = super.path(ctx);
if (ctx != null) {
if (!isNullish(ctx)) {
ctx.lineTo(this.points[0].x, this.points[0].y);
ctx.closePath();
}
@ -1289,7 +1298,7 @@ function build (opts) {
isCommandOrEnd () {
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 () {
@ -1363,10 +1372,10 @@ function build (opts) {
addMarker (p, from, priorTo) {
// 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.addMarkerAngle(p, from == null ? null : from.angleTo(p));
this.addMarkerAngle(p, isNullish(from) ? null : from.angleTo(p));
},
addMarkerAngle (p, a) {
@ -1377,9 +1386,9 @@ function build (opts) {
getMarkerPoints () { return this.points; },
getMarkerAngles () {
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++) {
if (this.angles[j] != null) {
if (!isNullish(this.angles[j])) {
this.angles[i] = this.angles[j];
break;
}
@ -1396,7 +1405,7 @@ function build (opts) {
pp.reset();
const bb = new svg.BoundingBox();
if (ctx != null) ctx.beginPath();
if (!isNullish(ctx)) ctx.beginPath();
while (!pp.isEnd()) {
pp.nextCommand();
switch (pp.command) {
@ -1405,13 +1414,13 @@ function build (opts) {
const p = pp.getAsCurrentPoint();
pp.addMarker(p);
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;
while (!pp.isCommandOrEnd()) {
const p = pp.getAsCurrentPoint();
pp.addMarker(p, pp.start);
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;
case 'L':
@ -1421,7 +1430,7 @@ function build (opts) {
const p = pp.getAsCurrentPoint();
pp.addMarker(p, c);
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;
case 'H':
@ -1431,7 +1440,7 @@ function build (opts) {
pp.addMarker(newP, pp.current);
pp.current = newP;
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;
case 'V':
@ -1441,7 +1450,7 @@ function build (opts) {
pp.addMarker(newP, pp.current);
pp.current = newP;
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;
case 'C':
@ -1453,7 +1462,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, p1);
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;
case 'S':
@ -1465,7 +1474,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, p1);
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;
case 'Q':
@ -1476,7 +1485,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, cntrl);
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;
case 'T':
@ -1488,7 +1497,7 @@ function build (opts) {
const cp = pp.getAsCurrentPoint();
pp.addMarker(cp, cntrl, cntrl);
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;
case 'A':
@ -1560,7 +1569,7 @@ function build (opts) {
pp.addMarkerAngle(cp, ah - dir * Math.PI);
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 sx = rx > ry ? 1 : rx / ry;
const sy = rx > ry ? ry / rx : 1;
@ -1577,7 +1586,7 @@ function build (opts) {
break;
case 'Z':
case 'z':
if (ctx != null) ctx.closePath();
if (!isNullish(ctx)) ctx.closePath();
pp.current = pp.start;
}
}
@ -1604,10 +1613,10 @@ function build (opts) {
// render me using a temporary svg element
const tempSvg = new svg.Element.svg();
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
tempSvg.attributes['width'] = new svg.Property('width', width + 'px');
tempSvg.attributes['height'] = new svg.Property('height', height + 'px');
tempSvg.attributes['transform'] = new svg.Property('transform', this.attribute('patternTransform').value);
tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').value);
tempSvg.attributes.width = new svg.Property('width', width + 'px');
tempSvg.attributes.height = new svg.Property('height', height + 'px');
tempSvg.attributes.transform = new svg.Property('transform', this.attribute('patternTransform').value);
tempSvg.children = this.children;
const c = document.createElement('canvas');
@ -1641,13 +1650,13 @@ function build (opts) {
// render me using a temporary svg element
const tempSvg = new svg.Element.svg();
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').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['width'] = new svg.Property('width', this.attribute('markerWidth').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['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
tempSvg.attributes.viewBox = new svg.Property('viewBox', this.attribute('viewBox').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.width = new svg.Property('width', this.attribute('markerWidth').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.stroke = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
tempSvg.children = this.children;
tempSvg.render(ctx);
@ -1698,7 +1707,7 @@ function build (opts) {
};
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}) => {
g.addColorStop(offset, addParentOpacity(color));
});
@ -1708,20 +1717,20 @@ function build (opts) {
const rootView = svg.ViewPort.viewPorts[0];
const rect = new svg.Element.rect();
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['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
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.width = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
rect.attributes.height = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
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];
const tempSvg = new svg.Element.svg();
tempSvg.attributes['x'] = new svg.Property('x', 0);
tempSvg.attributes['y'] = new svg.Property('y', 0);
tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
tempSvg.attributes.x = new svg.Property('x', 0);
tempSvg.attributes.y = new svg.Property('y', 0);
tempSvg.attributes.width = new svg.Property('width', rootView.width);
tempSvg.attributes.height = new svg.Property('height', rootView.height);
tempSvg.children = [group];
const c = document.createElement('canvas');
@ -1867,7 +1876,7 @@ function build (opts) {
update (delta) {
// set initial value
if (this.initialValue == null) {
if (isNullish(this.initialValue)) {
this.initialValue = this.getProperty().value;
this.initialUnits = this.getProperty().getUnits();
}
@ -1949,7 +1958,7 @@ function build (opts) {
const r = from.r + (to.r - from.r) * p.progress;
const g = from.g + (to.g - from.g) * 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;
}
@ -2048,8 +2057,8 @@ function build (opts) {
super.setContext(ctx);
let textBaseline = this.style('dominant-baseline').toTextBaseline();
if (textBaseline == null) textBaseline = this.style('alignment-baseline').toTextBaseline();
if (textBaseline != null) ctx.textBaseline = textBaseline;
if (isNullish(textBaseline)) textBaseline = this.style('alignment-baseline').toTextBaseline();
if (!isNullish(textBaseline)) ctx.textBaseline = textBaseline;
}
getBoundingBox () {
@ -2124,18 +2133,18 @@ function build (opts) {
if (i > 0 && text[i - 1] !== ' ' && (i === text.length - 1 || text[i + 1] === ' ')) arabicForm = 'initial';
if (typeof font.glyphs[c] !== 'undefined') {
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 {
glyph = font.glyphs[c];
}
if (glyph == null) glyph = font.missingGlyph;
if (isNullish(glyph)) glyph = font.missingGlyph;
return glyph;
}
renderChildren (ctx) {
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 fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
let text = this.getText();
@ -2182,7 +2191,7 @@ function build (opts) {
measureText (ctx) {
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);
let measure = 0;
let text = this.getText();
@ -2225,7 +2234,8 @@ function build (opts) {
svg.Element.tref = class extends svg.Element.TextElementBase {
getText () {
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) {
this.img.crossOrigin = 'Anonymous';
}
const self = this;
this.img.onload = function () {
self.loaded = true;
this.img.onload = () => {
this.loaded = true;
};
this.img.onerror = function () {
this.img.onerror = () => {
svg.log('ERROR: image "' + href + '" not found');
self.loaded = true;
this.loaded = true;
};
this.img.src = href;
} else {
@ -2387,14 +2396,14 @@ function build (opts) {
const prop = cssProp.indexOf(':');
const name = cssProp.substr(0, 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));
}
});
svg.Styles[cssClass] = props;
if (cssClass === '@font-face') {
const fontFamily = props['font-family'].value.replace(/"/g, '');
const srcs = props['src'].value.split(',');
const srcs = props.src.value.split(',');
srcs.forEach((src) => {
if (src.includes('format("svg")')) {
const urlStart = src.indexOf('url');
@ -2433,31 +2442,31 @@ function build (opts) {
path (ctx) {
const {_el: element} = this;
if (element != null) element.path(ctx);
if (!isNullish(element)) element.path(ctx);
}
getBoundingBox () {
const {_el: element} = this;
if (element != null) return element.getBoundingBox();
if (!isNullish(element)) return element.getBoundingBox();
}
renderChildren (ctx) {
const {_el: element} = this;
if (element != null) {
if (!isNullish(element)) {
let tempSvg = element;
if (element.type === 'symbol') {
// 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.type = 'svg';
tempSvg.attributes['viewBox'] = new svg.Property('viewBox', element.attribute('viewBox').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.viewBox = new svg.Property('viewBox', element.attribute('viewBox').value);
tempSvg.attributes.preserveAspectRatio = new svg.Property('preserveAspectRatio', element.attribute('preserveAspectRatio').value);
tempSvg.attributes.overflow = new svg.Property('overflow', element.attribute('overflow').value);
tempSvg.children = element.children;
}
if (tempSvg.type === 'svg') {
// 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('height').hasValue()) tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').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);
}
const oldParent = tempSvg.parent;
tempSvg.parent = null;
@ -2762,14 +2771,14 @@ function build (opts) {
// bind mouse
if (svg.opts.ignoreMouse !== true) {
ctx.canvas.onclick = function (e) {
const args = e != null
const args = !isNullish(e)
? [e.clientX, e.clientY]
: [event.clientX, event.clientY];
const {x, y} = mapXY(new svg.Point(...args));
svg.Mouse.onclick(x, y);
};
ctx.canvas.onmousemove = function (e) {
const args = e != null
const args = !isNullish(e)
? [e.clientX, e.clientY]
: [event.clientX, event.clientY];
const {x, y} = mapXY(new svg.Point(...args));
@ -2812,17 +2821,17 @@ function build (opts) {
}
svg.ViewPort.SetCurrent(cWidth, cHeight);
if (svg.opts.offsetX != null) {
if (!isNullish(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;
}
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);
let xRatio = null, yRatio = null;
if (svg.opts.scaleWidth != null) {
if (!isNullish(svg.opts.scaleWidth)) {
if (e.attribute('width').hasValue()) {
xRatio = e.attribute('width').toPixels('x') / svg.opts.scaleWidth;
} 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()) {
yRatio = e.attribute('height').toPixels('y') / svg.opts.scaleHeight;
} else if (!isNaN(viewBox[3])) {
@ -2838,8 +2847,8 @@ function build (opts) {
}
}
if (xRatio == null) { xRatio = yRatio; }
if (yRatio == null) { yRatio = xRatio; }
if (isNullish(xRatio)) { xRatio = yRatio; }
if (isNullish(yRatio)) { yRatio = xRatio; }
e.attribute('width', true).value = svg.opts.scaleWidth;
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)'],
process (bits) {
return [
parseInt(bits[1], 10),
parseInt(bits[2], 10),
parseInt(bits[3], 10)
parseInt(bits[1]),
parseInt(bits[2]),
parseInt(bits[3])
];
}
},
@ -279,8 +279,7 @@ export default class RGBColor {
margin: 3px;
border: 1px solid black;
background: ${listColor.toHex()};
color: ${listColor.toHex()};`
;
color: ${listColor.toHex()};`;
exampleDiv.append('test');
const listItemValue = ` ${examples[i]} -> ${listColor.toRGB()} -> ${listColor.toHex()}`;
listItem.append(exampleDiv, listItemValue);

View File

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

View File

@ -74,8 +74,7 @@ function jQueryContextMenu ($) {
// Add contextMenu class
menu.addClass('contextMenu');
// Simulate a true right click
$(this).bind('mousedown', function (e) {
const evt = e;
$(this).bind('mousedown', function (evt) {
$(this).mouseup(function (e) {
const srcElement = $(this);
srcElement.unbind('mouseup');
@ -109,8 +108,8 @@ function jQueryContextMenu ($) {
});
// Keyboard
doc.keypress(function (e) {
switch (e.keyCode) {
doc.keypress(function (ev) {
switch (ev.keyCode) {
case 38: // up
if (!menu.find('LI.hover').length) {
menu.find('LI:last').addClass('hover');
@ -143,7 +142,9 @@ function jQueryContextMenu ($) {
$('.contextMenu').hide();
// 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;
});

View File

@ -17,8 +17,10 @@ import {getTransformList} from './svgtransformlist.js';
const $ = jQuery;
// 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',
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
const pathMap = [
0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
];
/**
* @interface module:coords.EditorContext
@ -59,9 +61,9 @@ export const remapElement = function (selected, changes, m) {
doSnapping = editorContext_.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg',
finishUp = function () {
if (doSnapping) {
for (const o in changes) {
changes[o] = snapToGrid(changes[o]);
}
Object.entries(changes).forEach(([o, value]) => {
changes[o] = snapToGrid(value);
});
}
assignAttributes(selected, changes, 1000, true);
},
@ -297,8 +299,8 @@ export const remapElement = function (selected, changes, m) {
break;
case 11: // relative elliptical arc (a)
case 10: // absolute elliptical arc (A)
dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + (+seg.largeArcFlag) +
' ' + (+seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) +
' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
break;
case 17: // relative 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');
// If already set in the DOM, use the nonce throughout the document
// 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;
} else if (randIds === RandomizeModes.ALWAYS_RANDOMIZE) {
this.setNonce(Math.floor(Math.random() * 100001));
@ -253,7 +253,7 @@ export class Drawing {
return false;
}
// 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
// then return false.
@ -669,8 +669,8 @@ export class Drawing {
* @returns {Element}
*/
copyElem (el) {
const self = this;
const getNextIdClosure = function () { return self.getNextId(); };
const that = this;
const getNextIdClosure = function () { return that.getNextId(); };
return utilCopyElem(el, getNextIdClosure);
}
}

View File

@ -22,11 +22,11 @@ let cbid = 0;
*/
function getCallbackSetter (funcName) {
return function (...args) {
const t = this, // New callback
cbid = t.send(funcName, args, function () {}); // The callback (currently it's nothing, but will be set later)
const that = this, // New callback
callbackID = this.send(funcName, args, function () { /* */ }); // The callback (currently it's nothing, but will be set later)
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
* @returns {undefined}
*/
function addCallback (t, {result, error, id: cbid}) {
if (typeof cbid === 'number' && t.callbacks[cbid]) {
function addCallback (t, {result, error, id: callbackID}) {
if (typeof callbackID === 'number' && t.callbacks[callbackID]) {
// These should be safe both because we check `cbid` is numeric and
// because the calls are from trusted origins
if (result) {
t.callbacks[cbid](result); // lgtm [js/remote-property-injection]
t.callbacks[callbackID](result); // lgtm [js/remote-property-injection]
} 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 ||
(!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;
}
addCallback(this, data);
@ -136,7 +136,7 @@ class EmbeddedSVGEdit {
* If supplied, it should probably be the same as svgEditor's allowedOrigins
*/
constructor (frame, allowedOrigins) {
const t = this;
const that = this;
this.allowedOrigins = allowedOrigins || [];
// Initialize communication
this.frame = frame;
@ -326,7 +326,7 @@ class EmbeddedSVGEdit {
const keyboardEvent = new KeyboardEvent(e.type, {
key, keyCode, charCode, which
});
t.frame.contentDocument.dispatchEvent(keyboardEvent);
that.frame.contentDocument.dispatchEvent(keyboardEvent);
}
});
}
@ -338,11 +338,11 @@ class EmbeddedSVGEdit {
* @returns {Integer}
*/
send (name, args, callback) {
const t = this;
const that = this;
cbid++;
this.callbacks[cbid] = callback;
setTimeout((function (cbid) {
setTimeout((function (callbackID) {
return function () { // Delay for the callback to be set in case its synchronous
/*
* 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
let sameOriginWithGlobal = false;
try {
sameOriginWithGlobal = window.location.origin === t.frame.contentWindow.location.origin &&
t.frame.contentWindow.svgEditor.canvas;
sameOriginWithGlobal = window.location.origin === that.frame.contentWindow.location.origin &&
that.frame.contentWindow.svgEditor.canvas;
} catch (err) {}
if (sameOriginWithGlobal) {
@ -365,17 +365,17 @@ class EmbeddedSVGEdit {
// of the current JSON-based communication API (e.g., not passing
// callbacks). We might be able to address these shortcomings; see
// the todo elsewhere in this file.
const message = {id: cbid},
{svgEditor: {canvas: svgCanvas}} = t.frame.contentWindow;
const message = {id: callbackID},
{svgEditor: {canvas: svgCanvas}} = that.frame.contentWindow;
try {
message.result = svgCanvas[name].apply(svgCanvas, args);
message.result = svgCanvas[name](...args);
} catch (err) {
message.error = err.message;
}
addCallback(t, message);
addCallback(that, message);
} else { // Requires the ext-xdomain-messaging.js extension
t.frame.contentWindow.postMessage(JSON.stringify({
namespace: 'svgCanvas', id: cbid, name, args
that.frame.contentWindow.postMessage(JSON.stringify({
namespace: 'svgCanvas', id: callbackID, name, args
}), '*');
}
};

View File

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

View File

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

View File

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

View File

@ -5,12 +5,23 @@
* @module importModule
*/
/**
* Converts a possible relative URL into an absolute one.
* @param {string} url
* @returns {string}
*/
function toAbsoluteURL (url) {
const a = document.createElement('a');
a.setAttribute('href', url); // <a href="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) {
['id', 'class', 'type'].forEach((prop) => {
if (prop in atts) {
@ -27,18 +38,19 @@ function addScriptAtts (script, atts) {
*/
/**
* @function module:importModule.importSetGlobalDefault
* @param {string} url
* @param {string|string[]} url
* @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});
}
/**
* @function module:importModule.importSetGlobal
* @param {string} url
* @param {string|string[]} url
* @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}) {
// Todo: Replace calls to this function with `import()` when supported
@ -51,14 +63,21 @@ export async function importSetGlobal (url, {global, returnDefault}) {
await importScript(url);
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 = {}) {
if (Array.isArray(url)) {
return Promise.all(url.map((u) => {
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 destructor = () => {
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} = {}) {
if (Array.isArray(url)) {
return Promise.all(url.map((u) => {
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 script = document.createElement('script');
const destructor = () => {

View File

@ -5,7 +5,7 @@
* @copyright 2010 Jeff Schiller
*/
import {getHref, setHref, getRotationAngle} from './utilities.js';
import {getHref, setHref, getRotationAngle, isNullish} from './utilities.js';
import {removeElementFromListMap} from './svgtransformlist.js';
/**
@ -285,9 +285,9 @@ export class RemoveElementCommand extends Command {
}
removeElementFromListMap(this.elem);
if (this.nextSibling == null) {
if (isNullish(this.nextSibling)) {
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`
@ -670,7 +670,7 @@ export class UndoManager {
const oldValues = new Array(i), elements = new Array(i);
while (i--) {
const elem = elems[i];
if (elem == null) { continue; }
if (isNullish(elem)) { continue; }
elements[i] = elem;
oldValues[i] = elem.getAttribute(attrName);
}
@ -695,7 +695,7 @@ export class UndoManager {
let i = changeset.elements.length;
while (i--) {
const elem = changeset.elements[i];
if (elem == null) { continue; }
if (isNullish(elem)) { continue; }
const changes = {};
changes[attrName] = changeset.oldValues[i];
if (changes[attrName] !== elem.getAttribute(attrName)) {

View File

@ -77,8 +77,8 @@ class HistoryRecordingService {
if (this.currentBatchCommand_) {
const batchCommand = this.currentBatchCommand_;
this.batchCommandStack_.pop();
const {length} = this.batchCommandStack_;
this.currentBatchCommand_ = length ? this.batchCommandStack_[length - 1] : null;
const {length: len} = this.batchCommandStack_;
this.currentBatchCommand_ = len ? this.batchCommandStack_[len - 1] : null;
this.addCommand_(batchCommand);
}
return this;
@ -141,7 +141,7 @@ class HistoryRecordingService {
* Private function to add a command to the history or current batch command.
* @private
* @param {Command} cmd
* @returns {module:history.HistoryRecordingService}
* @returns {module:history.HistoryRecordingService|undefined}
*/
addCommand_ (cmd) {
if (!this.undoManager_) { return this; }
@ -150,6 +150,7 @@ class HistoryRecordingService {
} else {
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
* @returns {external:jQuery}
*/
export default function ($) {
export default function jQueryPluginSVG ($) {
const proxied = $.fn.attr,
svgns = 'http://www.w3.org/2000/svg';
/**
@ -32,7 +32,7 @@ export default function ($) {
*/
$.fn.attr = function (key, value) {
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) {
const elem = this[i];
// set/get SVG attribute
@ -70,7 +70,7 @@ export default function ($) {
return attr;
}
} else {
return proxied.apply(this, arguments);
return proxied.call(this, key, value);
}
}
return this;

View File

@ -407,7 +407,8 @@ export default function ($) {
'<div class="jGraduate_OkCancel">' +
'<input type="button" id="' + id + '_jGraduate_Ok" class="jGraduate_Ok" value="OK"/>' +
'<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)
@ -1234,7 +1235,7 @@ export default function ($) {
const sm = spreadMethodOpt.val();
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);
if (showFocus) {
$('#' + 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);
}
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');
if (attributeIsNotEmpty(strokeColor)) {
@ -134,38 +134,38 @@ const svgElementToPdf = function (element, pdf, options) {
break;
case 'line':
pdf.line(
k * parseInt(node.getAttribute('x1'), 10),
k * parseInt(node.getAttribute('y1'), 10),
k * parseInt(node.getAttribute('x2'), 10),
k * parseInt(node.getAttribute('y2'), 10)
k * parseInt(node.getAttribute('x1')),
k * parseInt(node.getAttribute('y1')),
k * parseInt(node.getAttribute('x2')),
k * parseInt(node.getAttribute('y2'))
);
removeAttributes(node, pdfSvgAttr.line);
break;
case 'rect':
pdf.rect(
k * parseInt(node.getAttribute('x'), 10),
k * parseInt(node.getAttribute('y'), 10),
k * parseInt(node.getAttribute('width'), 10),
k * parseInt(node.getAttribute('height'), 10),
k * parseInt(node.getAttribute('x')),
k * parseInt(node.getAttribute('y')),
k * parseInt(node.getAttribute('width')),
k * parseInt(node.getAttribute('height')),
colorMode
);
removeAttributes(node, pdfSvgAttr.rect);
break;
case 'ellipse':
pdf.ellipse(
k * parseInt(node.getAttribute('cx'), 10),
k * parseInt(node.getAttribute('cy'), 10),
k * parseInt(node.getAttribute('rx'), 10),
k * parseInt(node.getAttribute('ry'), 10),
k * parseInt(node.getAttribute('cx')),
k * parseInt(node.getAttribute('cy')),
k * parseInt(node.getAttribute('rx')),
k * parseInt(node.getAttribute('ry')),
colorMode
);
removeAttributes(node, pdfSvgAttr.ellipse);
break;
case 'circle':
pdf.circle(
k * parseInt(node.getAttribute('cx'), 10),
k * parseInt(node.getAttribute('cy'), 10),
k * parseInt(node.getAttribute('r'), 10),
k * parseInt(node.getAttribute('cx')),
k * parseInt(node.getAttribute('cy')),
k * parseInt(node.getAttribute('r')),
colorMode
);
removeAttributes(node, pdfSvgAttr.circle);
@ -216,7 +216,7 @@ const svgElementToPdf = function (element, pdf, options) {
}
pdf.setFontType(fontType);
const pdfFontSize = node.hasAttribute('font-size')
? parseInt(node.getAttribute('font-size'), 10)
? parseInt(node.getAttribute('font-size'))
: 16;
const getWidth = (node) => {
@ -247,8 +247,8 @@ const svgElementToPdf = function (element, pdf, options) {
case 'start': break;
case 'default': node.setAttribute('text-anchor', 'start'); break;
}
x = parseInt(node.getAttribute('x'), 10) - xOffset;
y = parseInt(node.getAttribute('y'), 10);
x = parseInt(node.getAttribute('x')) - xOffset;
y = parseInt(node.getAttribute('y'));
}
// console.log('fontSize:', pdfFontSize, 'text:', node.textContent);
pdf.setFontSize(pdfFontSize).text(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL',
URLLoadFail: 'Unable to load from URL',
retrieving: "Retrieving '%s' ...",
popupWindowBlocked: 'Popup window may be blocked by browser',
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.',
enterNewLinkURL: "Entrez la nouvelle URL de l'hyperlien",
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 »…',
popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -207,7 +207,7 @@ export default {
unsavedChanges: 'Wykryto niezapisane zmiany.',
enterNewLinkURL: 'Wpisz nowy adres URL hiperłącza',
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\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

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

View File

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

View File

@ -205,7 +205,7 @@ export default {
unsavedChanges: 'Sunt schimbări nesalvate.',
enterNewLinkURL: 'IntroduAliniere în raport cu ...sceţi noul URL',
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\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

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

View File

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

View File

@ -205,7 +205,7 @@ export default {
unsavedChanges: 'Obstajajo neshranjene spremembe.',
enterNewLinkURL: 'Vnesite novo URL povezavo',
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\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser',
exportNoBlur: 'Blurred elements will appear as un-blurred',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -206,7 +206,7 @@ export default {
unsavedChanges: 'There are unsaved changes.',
enterNewLinkURL: 'Enter the new hyperlink URL',
errorLoadingSVG: 'Error: Unable to load SVG data',
URLloadFail: 'Unable to load from URL',
URLLoadFail: 'Unable to load from URL',
retrieving: 'Retrieving \'%s\' ...',
popupWindowBlocked: 'Popup window may be blocked by browser',
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,
layer_delete: layers.del,
layer_down: layers.move_down,
layer_new: layers['new'],
layer_new: layers.new,
layer_rename: layers.rename,
layer_moreopts: common.more_opts,
layer_up: layers.move_up,

View File

@ -21,6 +21,7 @@
import {NS} from './namespaces.js';
import {getTransformList} from './svgtransformlist.js';
import {isNullish} from './utilities.js';
// Constants
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
*/
export const transformListToTransform = function (tlist, min, max) {
if (tlist == null) {
if (isNullish(tlist)) {
// Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
}
min = min || 0;
max = max || (tlist.numberOfItems - 1);
min = parseInt(min, 10);
max = parseInt(max, 10);
min = parseInt(min);
max = parseInt(max);
if (min > max) { const temp = max; max = min; min = temp; }
let m = svg.createSVGMatrix();
for (let i = min; i <= max; ++i) {

View File

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

View File

@ -5,12 +5,12 @@
* @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 {convertToNum} from './units.js';
import {isWebkit} from './browser.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 {remapElement} from './coords.js';
import {
@ -18,7 +18,7 @@ import {
hasMatrixTransform
} from './math.js';
const $ = jqPluginSVG(jQuery);
const $ = jQueryPluginSVG(jQuery);
let context_;
@ -75,7 +75,7 @@ export const updateClipPath = function (attr, tx, ty) {
* @returns {Command} Undo command object with the resulting change
*/
export const recalculateDimensions = function (selected) {
if (selected == null) { return null; }
if (isNullish(selected)) { return null; }
// Firefox Issue - 1081
if (selected.nodeName === 'svg' && navigator.userAgent.includes('Firefox/20')) {
@ -154,7 +154,8 @@ export const recalculateDimensions = function (selected) {
const m = matrixMultiply(
tlist.getItem(k - 2).matrix,
tlist.getItem(k - 1).matrix);
tlist.getItem(k - 1).matrix
);
mt.setMatrix(m);
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
// make a copy of initial values and include the transform
if (initial == null) {
if (isNullish(initial)) {
initial = $.extend(true, {}, changes);
$.each(initial, function (attr, val) {
initial[attr] = convertToNum(attr, val);
@ -795,15 +796,15 @@ export const recalculateDimensions = function (selected) {
// translation required to re-center it
// Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
} else if (operation === 3 && angle) {
const m = transformListToTransform(tlist).matrix;
const {matrix} = transformListToTransform(tlist);
const roldt = svgroot.createSVGTransform();
roldt.setRotate(angle, oldcenter.x, oldcenter.y);
const rold = roldt.matrix;
const rnew = svgroot.createSVGTransform();
rnew.setRotate(angle, newcenter.x, newcenter.y);
const rnewInv = rnew.matrix.inverse();
const mInv = m.inverse();
const extrat = matrixMultiply(mInv, rnewInv, rold, m);
const mInv = matrix.inverse();
const extrat = matrixMultiply(mInv, rnewInv, rold, matrix);
remapElement(selected, changes, extrat);
if (angle) {

View File

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

View File

@ -143,7 +143,7 @@ export const sanitizeSvg = function (node) {
const attrNsURI = attr.namespaceURI;
// 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
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])) {
// TODO(codedread): Programmatically add the se: attributes to the NS-aware whitelist.
// Bypassing the whitelist to allow se: prefixes.
@ -159,11 +159,12 @@ export const sanitizeSvg = function (node) {
switch (attrName) {
case 'transform':
case 'gradientTransform':
case 'patternTransform':
case 'patternTransform': {
const val = attr.value.replace(/(\d)-/g, '$1 -');
node.setAttribute(attrName, val);
break;
}
}
}
// 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 {getRotationAngle, getBBox, getStrokedBBox} from './utilities.js';
import {getRotationAngle, getBBox, getStrokedBBox, isNullish} from './utilities.js';
import {transformListToTransform, transformBox, transformPoint} from './math.js';
import {getTransformList} from './svgtransformlist.js';
@ -88,30 +88,6 @@ export class Selector {
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.
* @param {boolean} show - Indicates whether grips should be shown or not
@ -124,7 +100,7 @@ export class Selector {
this.hasGrips = show;
if (elem && show) {
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],
s: [nbax + (nbaw) / 2, nbay + nbah]
};
for (const dir in this.gripCoords) {
const coords = this.gripCoords[dir];
Object.entries(this.gripCoords).forEach(([dir, coords]) => {
selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]);
}
});
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
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).
@ -326,7 +318,7 @@ export class SelectorManager {
this.rubberBandBox = null;
// add the corner grips
for (const dir in this.selectorGrips) {
Object.keys(this.selectorGrips).forEach((dir) => {
const grip = svgFactory_.createSVGElement({
element: 'circle',
attr: {
@ -346,7 +338,7 @@ export class SelectorManager {
$.data(grip, 'dir', dir);
$.data(grip, 'type', 'resize');
this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip);
}
});
// add rotator elems
this.rotateGripConnector = this.selectorGripsGroup.appendChild(
@ -420,7 +412,7 @@ export class SelectorManager {
* @returns {Selector} The selector based on the given element
*/
requestSelector (elem, bbox) {
if (elem == null) { return null; }
if (isNullish(elem)) { return null; }
const N = this.selectors.length;
// If we've already acquired one for this element, return it.
@ -450,12 +442,12 @@ export class SelectorManager {
* @returns {undefined}
*/
releaseSelector (elem) {
if (elem == null) { return; }
if (isNullish(elem)) { return; }
const N = this.selectors.length,
sel = this.selectorMap[elem.id];
if (!sel.locked) {
// 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) {
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
* @returns {external:jQuery}
*/
export default function ($) {
export default function jQueryPluginSpinButton ($) {
if (!$.loadingStylesheets) {
$.loadingStylesheets = [];
}

View File

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

View File

@ -18,7 +18,7 @@
// Todo: Obtain/adapt latest jsPDF to utilize ES Module for `jsPDF`/avoid global
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 pathModule from './path.js';
@ -32,9 +32,10 @@ import {
getBBoxOfElementAsPath, convertToPath, toXml, encode64, decode64,
dataURLToObjectURL, createObjectURL,
getVisibleElements, dropXMLInteralSubset,
init as utilsInit, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible
init as utilsInit, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible,
isNullish
} from './utilities.js';
import * as history from './history.js';
import * as hstry from './history.js';
import {
transformPoint, matrixMultiply, hasMatrixTransform, transformListToTransform,
getMatrix, snapToAngle, isIdentity, rectsIntersect, transformBox
@ -60,24 +61,25 @@ import {
} from './recalculate.js';
import {
getSelectorManager,
Selector,
init as selectInit
} from './select.js';
const $ = jqPluginSVG(jQuery);
const $ = jQueryPluginSVG(jQuery);
const {
MoveElementCommand, InsertElementCommand, RemoveElementCommand,
ChangeElementCommand, BatchCommand, UndoManager, HistoryEventTypes
} = history;
} = hstry;
if (!window.console) {
window.console = {};
window.console.log = function (str) {};
window.console.dir = function (str) {};
window.console.log = function (str) { /* */ };
window.console.dir = function (str) { /* */ };
}
if (window.opera) {
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
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)
// NOTE: This is not actually a SVG document, but an HTML document.
@ -396,7 +398,7 @@ const getSelectedElements = this.getSelectedElems = function () {
return selectedElements;
};
const pathActions = pathModule.pathActions;
const {pathActions} = pathModule;
/**
* 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 (isApply) { restoreRefElems(cmd.elem); }
} else {
if (!isApply) { restoreRefElems(cmd.elem); }
} else if (!isApply) {
restoreRefElems(cmd.elem);
}
if (cmd.elem.tagName === 'use') {
setUseData(cmd.elem);
@ -562,7 +564,7 @@ const getCurrentZoom = this.getZoom = function () { return currentZoom; };
* @implements {module:path.EditorContext#round}
*/
const round = this.round = function (val) {
return parseInt(val * currentZoom, 10) / currentZoom;
return parseInt(val * currentZoom) / currentZoom;
};
selectInit(
@ -607,12 +609,13 @@ const getId = canvas.getId = function () {
* @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 {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) {
if (events[ev]) {
return events[ev](window, arg);
}
return undefined;
};
/**
@ -624,7 +627,7 @@ const call = function (ev, arg) {
*/
const clearSelection = this.clearSelection = function (noCall) {
selectedElements.forEach((elem) => {
if (elem == null) {
if (isNullish(elem)) {
return;
}
selectorManager.releaseSelector(elem);
@ -646,7 +649,7 @@ const addToSelection = this.addToSelection = function (elemsToAdd, showGrips) {
let j = 0;
while (j < selectedElements.length) {
if (selectedElements[j] == null) {
if (isNullish(selectedElements[j])) {
break;
}
++j;
@ -692,15 +695,16 @@ const addToSelection = this.addToSelection = function (elemsToAdd, showGrips) {
selectedElements.sort(function (a, b) {
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 0;
});
// Make sure first elements are not null
while (selectedElements[0] == null) {
while (isNullish(selectedElements[0])) {
selectedElements.shift(0);
}
};
@ -717,7 +721,7 @@ const getOpacity = function () {
* @implements {module:path.EditorContext#getMouseTarget}
*/
const getMouseTarget = this.getMouseTarget = function (evt) {
if (evt == null) {
if (isNullish(evt)) {
return null;
}
let mouseTarget = evt.target;
@ -881,8 +885,7 @@ $(opacAni).attr({
const restoreRefElems = function (elem) {
// Look for missing reference elements, restore any found
const attrs = $(elem).attr(refAttrs);
for (const o in attrs) {
const val = attrs[o];
Object.values(attrs).forEach((val) => {
if (val && val.startsWith('url(')) {
const id = getUrlFromAttr(val).substr(1);
const ref = getElem(id);
@ -891,7 +894,7 @@ const restoreRefElems = function (elem) {
delete removedElements[id];
}
}
}
});
const childs = elem.getElementsByTagName('*');
@ -1158,9 +1161,9 @@ this.addExtension = async function (name, extInitFunc, importLocale) {
extensions[name] = 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
*/
const getIntersectionList = this.getIntersectionList = function (rect) {
if (rubberBox == null) { return null; }
if (isNullish(rubberBox)) { return null; }
const parent = currentGroup || getCurrentDrawing().getCurrentLayer();
@ -1201,14 +1204,14 @@ const getIntersectionList = this.getIntersectionList = function (rect) {
if (!isIE) {
if (typeof svgroot.getIntersectionList === 'function') {
// Offset the bbox of the rubber box by the offset of the svgcontent element.
rubberBBox.x += parseInt(svgcontent.getAttribute('x'), 10);
rubberBBox.y += parseInt(svgcontent.getAttribute('y'), 10);
rubberBBox.x += parseInt(svgcontent.getAttribute('x'));
rubberBBox.y += parseInt(svgcontent.getAttribute('y'));
resultList = svgroot.getIntersectionList(rubberBBox, parent);
}
}
if (resultList == null || typeof resultList.item !== 'function') {
if (isNullish(resultList) || typeof resultList.item !== 'function') {
resultList = [];
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}
*/
/**
* The promise return, if present, resolves to `undefined`
* (`extension_added`, `exported`, `saved`)
* @typedef {Promise|undefined} module:svgcanvas.EventHandlerReturn
*/
/**
* @callback module:svgcanvas.EventHandler
* @param {external:Window} win
* @param {module:svgcanvas.SvgCanvas#event:GenericCanvasEvent} arg
* @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]);
selector.resize();
selector.updateGripCursors(val);
Selector.updateGripCursors(val);
};
/**
@ -1574,7 +1584,7 @@ const selectOnly = this.selectOnly = function (elems, showGrips) {
* @returns {undefined}
*/
/* const removeFromSelection = */ this.removeFromSelection = function (elemsToRemove) {
if (selectedElements[0] == null) { return; }
if (isNullish(selectedElements[0])) { return; }
if (!elemsToRemove.length) { return; }
// 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,
// 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 griptype = elData(grip, 'type');
// rotating
@ -1774,7 +1784,7 @@ const mouseDown = function (evt) {
// insert a dummy transform so if the element(s) are moved it will have
// a transform to use for its translate
for (i = 0; i < selectedElements.length; ++i) {
if (selectedElements[i] == null) { continue; }
if (isNullish(selectedElements[i])) { continue; }
const slist = getTransformList(selectedElements[i]);
if (slist.numberOfItems) {
slist.insertItemBefore(svgroot.createSVGTransform(), 0);
@ -1786,7 +1796,7 @@ const mouseDown = function (evt) {
} else if (!rightClick) {
clearSelection();
currentMode = 'multiselect';
if (rubberBox == null) {
if (isNullish(rubberBox)) {
rubberBox = selectorManager.getRubberBandBox();
}
rStartX *= currentZoom;
@ -1807,7 +1817,7 @@ const mouseDown = function (evt) {
break;
case 'zoom':
started = true;
if (rubberBox == null) {
if (isNullish(rubberBox)) {
rubberBox = selectorManager.getRubberBandBox();
}
assignAttributes(rubberBox, {
@ -1818,7 +1828,7 @@ const mouseDown = function (evt) {
display: 'inline'
}, 100);
break;
case 'resize':
case 'resize': {
started = true;
startX = x;
startY = y;
@ -1876,7 +1886,7 @@ const mouseDown = function (evt) {
}
}
break;
case 'fhellipse':
} case 'fhellipse':
case 'fhrect':
case 'fhpath':
start.x = realX;
@ -1902,7 +1912,7 @@ const mouseDown = function (evt) {
freehand.miny = realY;
freehand.maxy = realY;
break;
case 'image':
case 'image': {
started = true;
const newImage = addSVGElementFromJson({
element: 'image',
@ -1919,7 +1929,7 @@ const mouseDown = function (evt) {
setHref(newImage, lastGoodImgUrl);
preventClickDefault(newImage);
break;
case 'square':
} case 'square':
// FIXME: once we create the rect, we lose information that this was a square
// (for resizing purposes this could be important)
// Fallthrough
@ -2119,7 +2129,7 @@ const mouseMove = function (evt) {
len = selectedElements.length;
for (i = 0; i < len; ++i) {
selected = selectedElements[i];
if (selected == null) { break; }
if (isNullish(selected)) { break; }
// if (i === 0) {
// const box = utilsGetBBox(selected);
// selectedBBoxes[i].x = box.x + dx;
@ -2411,9 +2421,8 @@ const mouseMove = function (evt) {
start = {x: end.x, y: end.y};
break;
// update path stretch line coordinates
} case 'path': {
}
// fall through
} case 'path': { // eslint-disable-line no-empty
} // fall through
case 'pathedit': {
x *= currentZoom;
y *= currentZoom;
@ -2545,16 +2554,16 @@ const mouseUp = function (evt) {
// intentionally fall-through to select here
case 'resize':
case 'multiselect':
if (rubberBox != null) {
if (!isNullish(rubberBox)) {
rubberBox.setAttribute('display', 'none');
curBBoxes = [];
}
currentMode = 'select';
// Fallthrough
case 'select':
if (selectedElements[0] != null) {
if (!isNullish(selectedElements[0])) {
// 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
const selected = selectedElements[0];
switch (selected.tagName) {
@ -2589,7 +2598,7 @@ const mouseUp = function (evt) {
if (realX !== rStartX || realY !== rStartY) {
const len = selectedElements.length;
for (let i = 0; i < len; ++i) {
if (selectedElements[i] == null) { break; }
if (isNullish(selectedElements[i])) { break; }
if (!selectedElements[i].firstChild) {
// Not needed for groups (incorrectly resizes elems), possibly not needed at all?
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
} else {
t = evt.target;
if (selectedElements[0].nodeName === 'path' && selectedElements[1] == null) {
if (selectedElements[0].nodeName === 'path' && isNullish(selectedElements[1])) {
pathActions.select(selectedElements[0]);
// if it was a path
// else, if it was selected and this is a shift-click, remove it from selection
@ -2622,7 +2631,7 @@ const mouseUp = function (evt) {
}
return;
case 'zoom':
if (rubberBox != null) {
if (!isNullish(rubberBox)) {
rubberBox.setAttribute('display', 'none');
}
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());
element.remove();
element = null;
@ -2799,7 +2808,7 @@ const mouseUp = function (evt) {
canvas.setMode('select');
selectOnly([t], true);
}
} else if (element != null) {
} else if (!isNullish(element)) {
/**
* @name module:svgcanvas.SvgCanvas#addedNew
* @type {boolean}
@ -2936,8 +2945,8 @@ $(container).bind(
// content offset from canvas in screen pixels
const wOffset = workarea.offset();
const wOffsetLeft = wOffset['left'] + rulerwidth;
const wOffsetTop = wOffset['top'] + rulerwidth;
const wOffsetLeft = wOffset.left + rulerwidth;
const wOffsetTop = wOffset.top + rulerwidth;
const delta = (evt.wheelDelta) ? evt.wheelDelta : (evt.detail) ? -evt.detail : 0;
if (!delta) { return; }
@ -3508,7 +3517,7 @@ const removeUnusedDefElems = this.removeUnusedDefElems = function () {
*/
this.svgCanvasToString = function () {
// keep calling it until there are none to remove
while (removeUnusedDefElems() > 0) {}
while (removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty
pathActions.clear(true);
@ -3572,7 +3581,9 @@ this.svgToString = function (elem, indent) {
const attrs = Array.from(elem.attributes);
let i;
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(' '); }
out.push('<'); out.push(elem.nodeName);
@ -3693,14 +3704,14 @@ this.svgToString = function (elem, indent) {
out.push('\n');
out.push(this.svgToString(childs.item(i), indent));
break;
case 3: // text node
case 3: { // text node
const str = child.nodeValue.replace(/^\s+|\s+$/g, '');
if (str !== '') {
bOneLine = true;
out.push(String(toXml(str)));
}
break;
case 4: // cdata node
} case 4: // cdata node
out.push('\n');
out.push(new Array(indent + 1).join(' '));
out.push('<![CDATA[');
@ -4308,8 +4319,8 @@ const convertToGroup = this.convertToGroup = function (elem) {
if (vb) {
const nums = vb.split(' ');
pos.x -= +nums[0];
pos.y -= +nums[1];
pos.x -= Number(nums[0]);
pos.y -= Number(nums[1]);
}
// Not ideal, but works
@ -4635,12 +4646,12 @@ this.importSvgString = function (xmlString) {
// if no explicit viewbox, create one out of the width and height
vb = innervb ? innervb.split(' ') : [0, 0, innerw, innerh];
for (j = 0; j < 4; ++j) {
vb[j] = +(vb[j]);
vb[j] = Number(vb[j]);
}
// TODO: properly handle preserveAspectRatio
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
if (innerh > innerw) {
@ -4864,12 +4875,13 @@ this.setConfig = function (opts) {
/**
* @function module:svgcanvas.SvgCanvas#getTitle
* @param {Element} elem
* @returns {string|undefined} the current group/SVG's title contents
* @param {Element} [elem]
* @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) {
elem = elem || selectedElements[0];
if (!elem) { return; }
if (!elem) { return undefined; }
elem = $(elem).data('gsvg') || $(elem).data('symbol') || elem;
const childs = elem.childNodes;
for (let i = 0; i < childs.length; i++) {
@ -5088,24 +5100,29 @@ this.setBBoxZoom = function (val, editorW, editorH) {
}
switch (val) {
case 'selection':
if (!selectedElements[0]) { return; }
const selectedElems = $.map(selectedElements, function (n) { if (n) { return n; } });
case 'selection': {
if (!selectedElements[0]) { return undefined; }
const selectedElems = $.map(selectedElements, function (n) {
if (n) {
return n;
}
return undefined;
});
bb = getStrokedBBoxDefaultVisible(selectedElems);
break;
case 'canvas':
} case 'canvas': {
const res = getResolution();
spacer = 0.95;
bb = {width: res.w, height: res.h, x: 0, y: 0};
break;
case 'content':
} case 'content':
bb = getStrokedBBoxDefaultVisible();
break;
case 'layer':
bb = getStrokedBBoxDefaultVisible(getVisibleElements(getCurrentDrawing().getCurrentLayer()));
break;
default:
return;
return undefined;
}
return calcZoom(bb);
};
@ -5262,14 +5279,12 @@ this.setColor = function (type, val, preventUndo) {
if (elem) {
if (elem.tagName === 'g') {
walkTree(elem, addNonG);
} else {
if (type === 'fill') {
if (elem.tagName !== 'polyline' && elem.tagName !== 'line') {
elems.push(elem);
}
} else {
} else if (type === 'fill') {
if (elem.tagName !== 'polyline' && elem.tagName !== 'line') {
elems.push(elem);
}
} else {
elems.push(elem);
}
}
}
@ -5651,14 +5666,12 @@ canvas.setBlurOffsets = function (filter, stdDev) {
width: '200%',
height: '200%'
}, 100);
} else {
// Removing these attributes hides text in Chrome (see Issue 579)
if (!isWebkit()) {
// Removing these attributes hides text in Chrome (see Issue 579)
} else if (!isWebkit()) {
filter.removeAttribute('x');
filter.removeAttribute('y');
filter.removeAttribute('width');
filter.removeAttribute('height');
}
}
};
@ -5739,8 +5752,8 @@ canvas.setBlur = function (val, complete) {
this.getBold = function () {
// should only have one element selected
const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' &&
selectedElements[1] == null) {
if (!isNullish(selected) && selected.tagName === 'text' &&
isNullish(selectedElements[1])) {
return (selected.getAttribute('font-weight') === 'bold');
}
return false;
@ -5754,8 +5767,8 @@ this.getBold = function () {
*/
this.setBold = function (b) {
const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' &&
selectedElements[1] == null) {
if (!isNullish(selected) && selected.tagName === 'text' &&
isNullish(selectedElements[1])) {
changeSelectedAttribute('font-weight', b ? 'bold' : 'normal');
}
if (!selectedElements[0].textContent) {
@ -5770,8 +5783,8 @@ this.setBold = function (b) {
*/
this.getItalic = function () {
const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' &&
selectedElements[1] == null) {
if (!isNullish(selected) && selected.tagName === 'text' &&
isNullish(selectedElements[1])) {
return (selected.getAttribute('font-style') === 'italic');
}
return false;
@ -5785,8 +5798,8 @@ this.getItalic = function () {
*/
this.setItalic = function (i) {
const selected = selectedElements[0];
if (selected != null && selected.tagName === 'text' &&
selectedElements[1] == null) {
if (!isNullish(selected) && selected.tagName === 'text' &&
isNullish(selectedElements[1])) {
changeSelectedAttribute('font-style', i ? 'italic' : 'normal');
}
if (!selectedElements[0].textContent) {
@ -5863,7 +5876,7 @@ this.setFontSize = function (val) {
*/
this.getText = function () {
const selected = selectedElements[0];
if (selected == null) { return ''; }
if (isNullish(selected)) { return ''; }
return selected.textContent;
};
@ -5967,7 +5980,7 @@ this.setLinkURL = function (val) {
*/
this.setRectRadius = function (val) {
const selected = selectedElements[0];
if (selected != null && selected.tagName === 'rect') {
if (!isNullish(selected) && selected.tagName === 'rect') {
const r = selected.getAttribute('rx');
if (r !== String(val)) {
selected.setAttribute('rx', val);
@ -6023,12 +6036,12 @@ this.setSegType = function (newType) {
* Otherwise the resulting path element is returned.
*/
this.convertToPath = function (elem, getBBox) {
if (elem == null) {
if (isNullish(elem)) {
const elems = selectedElements;
$.each(elems, function (i, elem) {
if (elem) { canvas.convertToPath(elem); }
$.each(elems, function (i, el) {
if (el) { canvas.convertToPath(el); }
});
return;
return undefined;
}
if (getBBox) {
return getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
@ -6047,7 +6060,7 @@ this.convertToPath = function (elem, getBBox) {
opacity: curShape.opacity,
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;
let i = elems.length;
const noXYElems = ['g', 'polyline', 'path'];
const goodGAttrs = ['transform', 'opacity', 'filter'];
// const goodGAttrs = ['transform', 'opacity', 'filter'];
while (i--) {
let elem = elems[i];
if (elem == null) { continue; }
if (isNullish(elem)) { continue; }
// Set x,y vals on elements that don't have them
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
// TODO: FIXME: This doesn't seem right. Where's the body of this if statement?
if (elem.tagName === 'g' && goodGAttrs.includes(attr)) {}
// TODO: FIXME: Missing statement body
// if (elem.tagName === 'g' && goodGAttrs.includes(attr)) {}
let oldval = attr === '#text' ? elem.textContent : elem.getAttribute(attr);
if (oldval == null) { oldval = ''; }
if (isNullish(oldval)) { oldval = ''; }
if (oldval !== String(newValue)) {
if (attr === '#text') {
// const oldW = utilsGetBBox(elem).width;
@ -6209,7 +6222,7 @@ this.deleteSelectedElements = function () {
for (let i = 0; i < len; ++i) {
const selected = selectedElements[i];
if (selected == null) { break; }
if (isNullish(selected)) { break; }
let parent = selected.parentNode;
let t = selected;
@ -6406,7 +6419,7 @@ this.groupSelectedElements = function (type, urlArg) {
let i = selectedElements.length;
while (i--) {
let elem = selectedElements[i];
if (elem == null) { continue; }
if (isNullish(elem)) { continue; }
if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) {
elem = elem.parentNode;
@ -6690,7 +6703,7 @@ this.ungroupSelectedElement = function () {
*/
this.moveToTopSelectedElement = function () {
const [selected] = selectedElements;
if (selected != null) {
if (!isNullish(selected)) {
let t = selected;
const oldParent = t.parentNode;
const oldNextSibling = t.nextSibling;
@ -6713,7 +6726,7 @@ this.moveToTopSelectedElement = function () {
*/
this.moveToBottomSelectedElement = function () {
const [selected] = selectedElements;
if (selected != null) {
if (!isNullish(selected)) {
let t = selected;
const oldParent = t.parentNode;
const oldNextSibling = t.nextSibling;
@ -6759,9 +6772,9 @@ this.moveUpDownSelected = function (dir) {
if (this === selected) {
foundCur = true;
}
return;
return true;
}
closest = this;
closest = this; // eslint-disable-line consistent-this
return false;
});
if (!closest) { return; }
@ -6799,7 +6812,7 @@ this.moveSelectedElements = function (dx, dy, undoable) {
let i = selectedElements.length;
while (i--) {
const selected = selectedElements[i];
if (selected != null) {
if (!isNullish(selected)) {
// if (i === 0) {
// selectedBBoxes[0] = utilsGetBBox(selected);
// }
@ -6867,7 +6880,7 @@ this.cloneSelectedElements = function (x, y) {
selectedElements.sort(sortfunction);
for (i = 0; i < len; ++i) {
elem = selectedElements[i];
if (elem == null) { break; }
if (isNullish(elem)) { break; }
}
// use slice to quickly get the subset of elements we need
const copiedElements = selectedElements.slice(0, i);
@ -6905,7 +6918,7 @@ this.alignSelectedElements = function (type, relativeTo) {
miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE;
let curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE;
for (let i = 0; i < len; ++i) {
if (selectedElements[i] == null) { break; }
if (isNullish(selectedElements[i])) { break; }
const elem = selectedElements[i];
bboxes[i] = getStrokedBBoxDefaultVisible([elem]);
@ -6958,7 +6971,7 @@ this.alignSelectedElements = function (type, relativeTo) {
const dx = new Array(len);
const dy = new Array(len);
for (let i = 0; i < len; ++i) {
if (selectedElements[i] == null) { break; }
if (isNullish(selectedElements[i])) { break; }
// const elem = selectedElements[i];
const bbox = bboxes[i];
dx[i] = 0;
@ -7118,7 +7131,7 @@ this.cycleElement = function (next) {
let elem = false;
const allElems = getVisibleElements(currentGroup || getCurrentDrawing().getCurrentLayer());
if (!allElems.length) { return; }
if (curElem == null) {
if (isNullish(curElem)) {
num = next ? allElems.length - 1 : 0;
elem = allElems[num];
} 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
*
@ -96,18 +98,22 @@ $(function() {
* @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
*/
export default function ($) {
export default function jQueryPluginSVGIcons ($) {
const svgIcons = {};
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
* @param {string} file The location of a local SVG or SVGz file
* @param {PlainObject} [opts]
* @param {Float} [opts.w] The icon widths
* @param {Float} [opts.h] The icon heights
* @param {PlainObject.<string, string>} [opts.fallback] List of raster images with each
key being the SVG icon ID to replace, and the value the image file name
* @param {external:jQuery.svgIcons.Fallback} [opts.fallback]
* @param {string} [opts.fallback_path] The path to use for all images
listed under "fallback"
* @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,
iconH = opts.h || 24;
let elems, svgdoc, testImg,
iconsMade = false, dataLoaded = false, loadAttempts = 0;
const isOpera = !!window.opera,
iconsMade = false,
dataLoaded = false,
loadAttempts = 0;
const isOpera = Boolean(window.opera),
// ua = navigator.userAgent,
// isSafari = (ua.includes('Safari/') && !ua.includes('Chrome/')),
dataPre = 'data:image/svg+xml;charset=utf-8;base64,';
@ -169,24 +177,28 @@ export default function ($) {
$(function () {
useFallback();
});
} else {
if (err.responseText) {
svgdoc = parser.parseFromString(err.responseText, 'text/xml');
} else if (err.responseText) {
svgdoc = parser.parseFromString(err.responseText, 'text/xml');
if (!svgdoc.childNodes.length) {
$(useFallback);
}
$(function () {
getIcons('ajax');
});
} else {
if (!svgdoc.childNodes.length) {
$(useFallback);
}
$(function () {
getIcons('ajax');
});
} else {
$(useFallback);
}
}
});
}
/**
*
* @param {"ajax"|0|undefined} evt
* @param {boolean} [noWait]
* @returns {undefined}
*/
function getIcons (evt, noWait) {
if (evt !== 'ajax') {
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) {
if (isOpera) icon.css('visibility', 'hidden');
if (opts.replace) {
@ -249,6 +269,11 @@ export default function ($) {
}
let holder;
/**
* @param {external:jQuery} icon A wrapped `defs` or Image
* @param {string} id SVG icon ID
* @returns {undefined}
*/
function addIcon (icon, id) {
if (opts.id_match === undefined || opts.id_match !== false) {
setIcon(holder, icon, id, true);
@ -256,7 +281,13 @@ export default function ($) {
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 (opts.no_img) toImage = false;
@ -359,13 +390,14 @@ export default function ($) {
let idElems;
if (isOpera) {
idElems = defs.find('*').filter(function () {
return !!this.id;
return Boolean(this.id);
});
} else {
idElems = defs.find('[id]');
}
const allElems = svgEl[0].getElementsByTagName('*'), len = allElems.length;
const allElems = svgEl[0].getElementsByTagName('*'),
len = allElems.length;
idElems.each(function (i) {
const {id} = this;
@ -409,49 +441,20 @@ export default function ($) {
return svgEl;
};
/**
* @returns {undefined}
*/
function useFallback () {
if (file.includes('.svgz')) {
const regFile = file.replace('.svgz', '.svg');
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);
} else if (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
* 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)) {
// 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 [];
}
const owningPathSegList = this;
const owningPathSegList = this; // eslint-disable-line consistent-this
class Builder {
constructor () {
@ -883,10 +887,10 @@ if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.pro
} case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL: {
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);
} 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()};
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());
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
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');
// Helper function.
/**
* Helper function to convert `SVGTransform` to a string.
* @param {SVGTransform} xform
* @returns {string}
*/
function transformToString (xform) {
const m = xform.matrix;
let text = '';
@ -114,8 +118,9 @@ let listMap_ = {};
* These methods do not currently raise any exceptions.
* 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.
* @implements {module:SVGTransformList.SVGEditTransformList}
*/
export class SVGTransformList {
export class SVGTransformList {// eslint-disable-line no-shadow
/**
* @param {Element} elem
*/
@ -170,7 +175,7 @@ export class SVGTransformList {
} else if (name === 'rotate' && values.length === 1) {
values.push(0, 0);
}
xform[fname].apply(xform, values);
xform[fname](...values);
this._list.appendItem(xform);
}
}
@ -179,20 +184,15 @@ export class SVGTransformList {
if (item) {
// Check if this transform is already in a transformlist, and
// remove it if so.
let found = false;
for (const id in listMap_) {
const tl = listMap_[id];
Object.values(listMap_).some((tl) => {
for (let i = 0, len = tl._xforms.length; i < len; ++i) {
if (tl._xforms[i] === item) {
found = true;
tl.removeItem(i);
break;
return true;
}
}
if (found) {
break;
}
}
return false;
});
}
};
@ -330,7 +330,7 @@ export const resetListMap = function () {
* @param {Element} elem - a DOM Element
* @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]) {
delete listMap_[elem.id];
}
@ -377,6 +377,7 @@ export const getTransformList = function (elem) {
* @param {module:SVGTransformList.removeElementFromListMap} cb Passed a single argument `elem`
* @returns {undefined}
*/
export const changeRemoveElementFromListMap = function (cb) {
export const changeRemoveElementFromListMap = function (cb) { // eslint-disable-line promise/prefer-await-to-callbacks
removeElementFromListMap = cb;
};

View File

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

View File

@ -50,3 +50,12 @@
/**
* @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 {isNullish} from './utilities.js';
const wAttrs = ['x', 'x1', 'cx', 'rx', 'width'];
const hAttrs = ['y', 'y1', 'cy', 'ry', 'height'];
@ -40,7 +41,7 @@ let typeMap_ = {};
*/
/**
* @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
@ -134,8 +135,7 @@ export const getTypeMap = function () {
export const shortFloat = function (val) {
const digits = elementContainer_.getRoundDigits();
if (!isNaN(val)) {
// Note that + converts to Number
return +((+val).toFixed(digits));
return Number(Number(val).toFixed(digits));
}
if (Array.isArray(val)) {
return shortFloat(val[0]) + ',' + shortFloat(val[1]);
@ -304,7 +304,7 @@ export const isValidUnit = function (attr, val, selectedElement) {
// not already present
try {
const elem = elementContainer_.getElement(val);
result = (elem == null || elem === selectedElement);
result = (isNullish(elem) || elem === selectedElement);
} catch (e) {}
return result;
}

View File

@ -8,7 +8,7 @@
*/
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 {getTransformList} from './svgtransformlist.js';
import {setUnitAttr, getTypeMap} from './units.js';
@ -22,7 +22,7 @@ import {
} from './browser.js';
// Constants
const $ = jqPluginSVG(jQuery);
const $ = jQueryPluginSVG(jQuery);
// String used to encode base64.
const KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
@ -123,7 +123,12 @@ export const dropXMLInteralSubset = (str) => {
export const toXml = function (str) {
// &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 "]]")
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
* @returns {string} The converted string
*/
export const fromXml = function (str) {
export function fromXml (str) {
return $('<p/>').html(str).text();
};
}
// 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.
@ -150,30 +155,33 @@ export const fromXml = function (str) {
* @param {string} input
* @returns {string} Base64 output
*/
export const encode64 = function (input) {
export function encode64 (input) {
// base64 strings are 4/3 larger than the original string
input = encodeUTF8(input); // convert non-ASCII characters
// input = convertToXMLReferences(input);
if (window.btoa) {
return window.btoa(input); // Use native if available
}
const output = [];
output.length = Math.floor((input.length + 2) / 3) * 4;
const output = new Array(Math.floor((input.length + 2) / 3) * 4);
let i = 0, p = 0;
let i = 0,
p = 0;
do {
const chr1 = input.charCodeAt(i++);
const chr2 = input.charCodeAt(i++);
const chr3 = input.charCodeAt(i++);
/* eslint-disable no-bitwise */
const enc1 = chr1 >> 2;
const enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
let enc4 = chr3 & 63;
/* eslint-enable no-bitwise */
if (isNaN(chr2)) {
enc3 = enc4 = 64;
enc3 = 64;
enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
@ -185,7 +193,7 @@ export const encode64 = function (input) {
} while (i < input.length);
return output.join('');
};
}
/**
* Converts a string from base64.
@ -193,7 +201,7 @@ export const encode64 = function (input) {
* @param {string} input Base64-encoded input
* @returns {string} Decoded output
*/
export const decode64 = function (input) {
export function decode64 (input) {
if (window.atob) {
return decodeUTF8(window.atob(input));
}
@ -210,30 +218,32 @@ export const decode64 = function (input) {
const enc3 = KEYSTR.indexOf(input.charAt(i++));
const enc4 = KEYSTR.indexOf(input.charAt(i++));
/* eslint-disable no-bitwise */
const chr1 = (enc1 << 2) | (enc2 >> 4);
const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
const chr3 = ((enc3 & 3) << 6) | enc4;
/* eslint-enable no-bitwise */
output += String.fromCharCode(chr1);
if (enc3 !== 64) {
output = output + String.fromCharCode(chr2);
output += String.fromCharCode(chr2);
}
if (enc4 !== 64) {
output = output + String.fromCharCode(chr3);
output += String.fromCharCode(chr3);
}
} while (i < input.length);
return decodeUTF8(output);
};
}
/**
* @function module:utilities.decodeUTF8
* @param {string} argString
* @returns {string}
*/
export const decodeUTF8 = function (argString) {
export function decodeUTF8 (argString) {
return decodeURIComponent(escape(argString));
};
}
// 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) {
return '';
}
const arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
const arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
@ -430,7 +441,7 @@ export const getUrlFromAttr = function (attrVal) {
* @param {Element} elem
* @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');
};
@ -441,7 +452,7 @@ export let getHref = function (elem) {
* @param {string} val
* @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);
};
@ -484,6 +495,15 @@ export const getPathBBox = function (path) {
const start = seglist.getItem(0);
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++) {
const seg = seglist.getItem(i);
@ -499,21 +519,14 @@ export const getPathBBox = function (path) {
P3 = [seg.x, seg.y];
for (let j = 0; j < 2; j++) {
const calc = 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];
};
const calc = getCalc(j, P1, P2, P3);
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 c = 3 * P1[j] - 3 * P0[j];
if (a === 0) {
if (b === 0) {
continue;
}
if (b === 0) { continue; }
const t = -c / b;
if (t > 0 && t < 1) {
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.
ret = selected.getBBox();
selected.textContent = '';
} else {
if (selected.getBBox) { ret = selected.getBBox(); }
} else if (selected.getBBox) {
ret = selected.getBBox();
}
break;
case 'path':
if (!supportsPathBBox()) {
ret = getPathBBox(selected);
} else {
if (selected.getBBox) { ret = selected.getBBox(); }
} else if (selected.getBBox) {
ret = selected.getBBox();
}
break;
case 'g':
@ -719,12 +732,13 @@ export const getPathDFromElement = function (elem) {
let d, a, rx, ry;
switch (elem.tagName) {
case 'ellipse':
case 'circle':
case 'circle': {
a = $(elem).attr(['rx', 'ry', 'cx', 'cy']);
const {cx, cy} = a;
({rx, ry} = a);
if (elem.tagName === 'circle') {
rx = ry = $(elem).attr('r');
ry = $(elem).attr('r');
rx = ry;
}
d = getPathDFromSegments([
@ -736,7 +750,7 @@ export const getPathDFromElement = function (elem) {
['Z', []]
]);
break;
case 'path':
} case 'path':
d = elem.getAttribute('d');
break;
case 'line':
@ -749,11 +763,13 @@ export const getPathDFromElement = function (elem) {
case 'polygon':
d = 'M' + elem.getAttribute('points') + ' Z';
break;
case 'rect':
case 'rect': {
const r = $(elem).attr(['rx', 'ry']);
({rx, ry} = r);
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!
if (!rx && !ry) {
@ -781,7 +797,7 @@ export const getPathDFromElement = function (elem) {
]);
}
break;
default:
} default:
break;
}
@ -826,11 +842,11 @@ export const getBBoxOfElementAsPath = function (elem, addSVGElementFromJson, pat
path.setAttribute('transform', eltrans);
}
const parent = elem.parentNode;
const {parentNode} = elem;
if (elem.nextSibling) {
elem.before(path);
} else {
parent.append(path);
parentNode.append(path);
}
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: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: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}
* @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) {
const batchCmd = new history.BatchCommand('Convert element to Path');
export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathActions, clearSelection, addToSelection, hstry, addCommandToHistory) {
const batchCmd = new hstry.BatchCommand('Convert element to Path');
// Any attribute on the element not covered by the passed-in attributes
attrs = $.extend({}, attrs, getExtraAttributesForConvertToPath(elem));
@ -882,11 +898,11 @@ export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathA
}
const {id} = elem;
const parent = elem.parentNode;
const {parentNode} = elem;
if (elem.nextSibling) {
elem.before(path);
} else {
parent.append(path);
parentNode.append(path);
}
const d = getPathDFromElement(elem);
@ -939,14 +955,14 @@ export const convertToPath = function (elem, attrs, addSVGElementFromJson, pathA
* getBBox then apply the angle and any transforms.
*
* @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.
*/
function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasMatrixTransform) {
function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasAMatrixTransform) {
const angleModulo90 = angle % 90;
const closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99;
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
const elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon'];
if (elemNames.includes(elem.tagName)) {
bb = goodBb = getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
goodBb = getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
bb = goodBb;
} else if (elem.tagName === 'rect') {
// Look for radius
const rx = elem.getAttribute('rx');
const ry = elem.getAttribute('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;
};
// 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) {
const sw = elem.getAttribute('stroke-width');
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"
* for this function.
* @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.
*/
export const getVisibleElements = function (parent) {
if (!parent) {
parent = $(editorContext_.getSVGContent()).children(); // Prevent layers from being included
export const getVisibleElements = function (parentElement) {
if (!parentElement) {
parentElement = $(editorContext_.getSVGContent()).children(); // Prevent layers from being included
}
const contentElems = [];
$(parent).children().each(function (i, elem) {
$(parentElement).children().each(function (i, elem) {
if (elem.getBBox) {
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
* @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];
// find the rotation transform (if any) and set it
const tlist = getTransformList(selected);
@ -1169,13 +1192,14 @@ export const getRefElem = function (attrVal) {
* Get a DOM element by ID within the SVG root element.
* @function module:utilities.getElem
* @param {string} id - String with the element's new ID
* @returns {Element}
* @returns {?Element}
*/
export const getElem = (supportsSelectors())
? function (id) {
// querySelector lookup
return svgroot_.querySelector('#' + id);
} : supportsXpath()
}
: supportsXpath()
? function (id) {
// xpath lookup
return domdoc_.evaluate(
@ -1183,7 +1207,8 @@ export const getElem = (supportsSelectors())
domcontainer_,
function () { return NS.SVG; },
9,
null).singleNodeValue;
null
).singleNodeValue;
}
: function (id) {
// jQuery lookup: twice as slow as xpath in FF
@ -1242,12 +1267,11 @@ export const cleanupElement = function (element) {
delete defaults.ry;
}
for (const attr in defaults) {
const val = defaults[attr];
Object.entries(defaults).forEach(([attr, val]) => {
if (element.getAttribute(attr) === String(val)) {
element.removeAttribute(attr);
}
}
});
};
/**
@ -1343,6 +1367,15 @@ export const copyElem = function (el, getNextId) {
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.
* @function module:utilities.mock

View File

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

View File

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

View File

@ -3,6 +3,11 @@
jQuery(function () {
if (!window.Components) return;
/**
* Offer choice of file.
* @param {boolean} readflag
* @returns {nsILocalFile}
*/
function mozFilePicker (readflag) {
const fp = window.Components.classes['@mozilla.org/filepicker;1']
.createInstance(Components.interfaces.nsIFilePicker);
@ -19,16 +24,16 @@ jQuery(function () {
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
const file = mozFilePicker(true);
if (!file) {
return null;
return;
}
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);
sInputStream.init(inputStream);
svgCanvas.setSvgString(sInputStream.read(sInputStream.available()));
} 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) {
@ -39,16 +44,16 @@ jQuery(function () {
}
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);
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.flush();
out.close();
} 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
*/
const fif = require('find-in-files');
(async () => {
/**
* @typedef {PlainObject} FileResult
@ -15,7 +16,7 @@ const fileMatchPatterns = ['editor'];
* Keys are file name strings
* @type {Object.<string, FileResult>}
*/
let results = await Promise.all(fileMatchPatterns.map(async (fileMatchPattern) => {
let results = await Promise.all(fileMatchPatterns.map((fileMatchPattern) => {
return fif.find(
{
// 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) {
switch (file) {
case 'editor/external/jamilih/jml-es.js':
case 'editor/xdomain-svgedit-config-iife.js': // Ignore
res.line = [];
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':
res.line = res.line.filter((line) => {
return ![

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