Nov25 fixes (#1072)

* update deps
* fixes #963 and #1061
* fix the issue #974
* update test
This commit is contained in:
JFH
2025-11-27 14:10:49 -03:00
committed by GitHub
parent 16a0e0d945
commit babd3490c9
6 changed files with 60 additions and 9 deletions

View File

@@ -1,9 +1,9 @@
{"total": {"lines":{"total":6744,"covered":3959,"skipped":0,"pct":58.7},"statements":{"total":7050,"covered":4071,"skipped":0,"pct":57.74},"functions":{"total":1014,"covered":533,"skipped":0,"pct":52.56},"branches":{"total":3442,"covered":1419,"skipped":0,"pct":41.22},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
{"total": {"lines":{"total":6848,"covered":4089,"skipped":0,"pct":59.71},"statements":{"total":7242,"covered":4262,"skipped":0,"pct":58.85},"functions":{"total":1048,"covered":577,"skipped":0,"pct":55.05},"branches":{"total":3533,"covered":1462,"skipped":0,"pct":41.38},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/Users/jfh/Documents/GitHub/svgedit/packages/svgcanvas/common/browser.js": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":6,"covered":2,"skipped":0,"pct":33.33},"statements":{"total":30,"covered":25,"skipped":0,"pct":83.33},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/Users/jfh/Documents/GitHub/svgedit/packages/svgcanvas/common/util.js": {"lines":{"total":90,"covered":8,"skipped":0,"pct":8.88},"functions":{"total":7,"covered":3,"skipped":0,"pct":42.85},"statements":{"total":92,"covered":10,"skipped":0,"pct":10.86},"branches":{"total":98,"covered":10,"skipped":0,"pct":10.2}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/ConfigObj.js": {"lines":{"total":101,"covered":39,"skipped":0,"pct":38.61},"functions":{"total":14,"covered":9,"skipped":0,"pct":64.28},"statements":{"total":102,"covered":39,"skipped":0,"pct":38.23},"branches":{"total":95,"covered":25,"skipped":0,"pct":26.31}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/Editor.js": {"lines":{"total":414,"covered":192,"skipped":0,"pct":46.37},"functions":{"total":103,"covered":31,"skipped":0,"pct":30.09},"statements":{"total":420,"covered":193,"skipped":0,"pct":45.95},"branches":{"total":213,"covered":75,"skipped":0,"pct":35.21}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/EditorStartup.js": {"lines":{"total":383,"covered":237,"skipped":0,"pct":61.87},"functions":{"total":57,"covered":31,"skipped":0,"pct":54.38},"statements":{"total":395,"covered":246,"skipped":0,"pct":62.27},"branches":{"total":147,"covered":51,"skipped":0,"pct":34.69}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/EditorStartup.js": {"lines":{"total":487,"covered":366,"skipped":0,"pct":75.15},"functions":{"total":91,"covered":75,"skipped":0,"pct":82.41},"statements":{"total":586,"covered":435,"skipped":0,"pct":74.23},"branches":{"total":238,"covered":93,"skipped":0,"pct":39.07}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/MainMenu.js": {"lines":{"total":101,"covered":44,"skipped":0,"pct":43.56},"functions":{"total":14,"covered":7,"skipped":0,"pct":50},"statements":{"total":101,"covered":44,"skipped":0,"pct":43.56},"branches":{"total":44,"covered":7,"skipped":0,"pct":15.9}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/Rulers.js": {"lines":{"total":119,"covered":93,"skipped":0,"pct":78.15},"functions":{"total":6,"covered":5,"skipped":0,"pct":83.33},"statements":{"total":124,"covered":98,"skipped":0,"pct":79.03},"branches":{"total":43,"covered":32,"skipped":0,"pct":74.41}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/browser-not-supported.js": {"lines":{"total":4,"covered":3,"skipped":0,"pct":75},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":3,"skipped":0,"pct":75},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}}
@@ -32,7 +32,7 @@
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/jgraduate/jQuery.jGraduate.js": {"lines":{"total":580,"covered":275,"skipped":0,"pct":47.41},"functions":{"total":44,"covered":14,"skipped":0,"pct":31.81},"statements":{"total":602,"covered":282,"skipped":0,"pct":46.84},"branches":{"total":278,"covered":100,"skipped":0,"pct":35.97}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/jgraduate/jQuery.jPicker.js": {"lines":{"total":844,"covered":453,"skipped":0,"pct":53.67},"functions":{"total":61,"covered":40,"skipped":0,"pct":65.57},"statements":{"total":931,"covered":480,"skipped":0,"pct":51.55},"branches":{"total":779,"covered":329,"skipped":0,"pct":42.23}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/SePlainAlertDialog.js": {"lines":{"total":12,"covered":2,"skipped":0,"pct":16.66},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":12,"covered":2,"skipped":0,"pct":16.66},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/cmenuDialog.js": {"lines":{"total":120,"covered":112,"skipped":0,"pct":93.33},"functions":{"total":28,"covered":16,"skipped":0,"pct":57.14},"statements":{"total":131,"covered":117,"skipped":0,"pct":89.31},"branches":{"total":23,"covered":19,"skipped":0,"pct":82.6}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/cmenuDialog.js": {"lines":{"total":120,"covered":113,"skipped":0,"pct":94.16},"functions":{"total":28,"covered":16,"skipped":0,"pct":57.14},"statements":{"total":131,"covered":118,"skipped":0,"pct":90.07},"branches":{"total":23,"covered":20,"skipped":0,"pct":86.95}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/cmenuLayersDialog.js": {"lines":{"total":61,"covered":49,"skipped":0,"pct":80.32},"functions":{"total":16,"covered":6,"skipped":0,"pct":37.5},"statements":{"total":66,"covered":49,"skipped":0,"pct":74.24},"branches":{"total":18,"covered":13,"skipped":0,"pct":72.22}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/editorPreferencesDialog.js": {"lines":{"total":157,"covered":125,"skipped":0,"pct":79.61},"functions":{"total":30,"covered":9,"skipped":0,"pct":30},"statements":{"total":159,"covered":126,"skipped":0,"pct":79.24},"branches":{"total":46,"covered":35,"skipped":0,"pct":76.08}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/exportDialog.js": {"lines":{"total":52,"covered":36,"skipped":0,"pct":69.23},"functions":{"total":14,"covered":5,"skipped":0,"pct":35.71},"statements":{"total":55,"covered":36,"skipped":0,"pct":65.45},"branches":{"total":11,"covered":5,"skipped":0,"pct":45.45}}
@@ -57,7 +57,7 @@
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-markers/ext-markers.js": {"lines":{"total":149,"covered":46,"skipped":0,"pct":30.87},"functions":{"total":21,"covered":12,"skipped":0,"pct":57.14},"statements":{"total":164,"covered":48,"skipped":0,"pct":29.26},"branches":{"total":80,"covered":22,"skipped":0,"pct":27.5}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-opensave/ext-opensave.js": {"lines":{"total":138,"covered":36,"skipped":0,"pct":26.08},"functions":{"total":13,"covered":3,"skipped":0,"pct":23.07},"statements":{"total":144,"covered":36,"skipped":0,"pct":25},"branches":{"total":34,"covered":0,"skipped":0,"pct":0}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-opensave/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-panning/ext-panning.js": {"lines":{"total":30,"covered":22,"skipped":0,"pct":73.33},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":30,"covered":22,"skipped":0,"pct":73.33},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-panning/ext-panning.js": {"lines":{"total":30,"covered":22,"skipped":0,"pct":73.33},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":31,"covered":23,"skipped":0,"pct":74.19},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-panning/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-polystar/ext-polystar.js": {"lines":{"total":247,"covered":232,"skipped":0,"pct":93.92},"functions":{"total":18,"covered":16,"skipped":0,"pct":88.88},"statements":{"total":256,"covered":241,"skipped":0,"pct":94.14},"branches":{"total":62,"covered":39,"skipped":0,"pct":62.9}}
,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-polystar/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}

View File

@@ -158,12 +158,15 @@ describe('Basic Module', function () {
const output = svgCanvas.getSvgString()
const hasXlink = output.includes('xmlns:xlink="http://www.w3.org/1999/xlink"')
const hasImageHref = /<image[^>]+href=/.test(output)
const hasSe = output.includes('xmlns:se=')
const hasFoo = output.includes('xmlns:foo=')
const hasAttr = output.includes('se:foo="bar"')
assert.equal(hasAttr, true, 'Preserved namespaced attribute on export')
assert.equal(hasXlink, true, 'Included xlink: xmlns')
assert.equal(hasImageHref, true, 'Preserved image href')
// xlink namespace is optional (href is preferred), accept either
assert.equal(hasXlink || hasImageHref, true, 'Included xlink namespace when needed')
assert.equal(hasSe, true, 'Included se: xmlns')
assert.equal(hasFoo, false, 'Did not include foo: xmlns')
})

View File

@@ -105,6 +105,11 @@ export const recalculateDimensions = selected => {
return null
}
// Avoid remapping transforms on <use> to preserve referenced positioning/rotation
if (selected.tagName === 'use') {
return null
}
// Set up undo command
const batchCmd = new BatchCommand('Transform')

View File

@@ -7,7 +7,7 @@
*/
import { getReverseNS, NS } from './namespaces.js'
import { getHref, setHref, getUrlFromAttr } from './utilities.js'
import { getHref, getRefElem, setHref, getUrlFromAttr } from './utilities.js'
const REVERSE_NS = getReverseNS()
@@ -39,7 +39,11 @@ const svgWhiteList_ = {
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'href', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
foreignObject: ['font-size', 'height', 'opacity', 'requiredFeatures', 'width', 'x', 'y'],
g: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'],
image: ['clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'systemLanguage', 'width', 'x', 'href', 'xlink:href', 'xlink:title', 'y'],
image: [
'clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity',
'preserveAspectRatio', 'requiredFeatures', 'systemLanguage', 'viewBox',
'width', 'x', 'href', 'xlink:href', 'xlink:title', 'y'
],
line: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'x1', 'x2', 'y1', 'y2'],
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'href', 'xlink:href', 'y1', 'y2'],
marker: ['markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'se_type', 'systemLanguage', 'viewBox'],
@@ -224,6 +228,13 @@ export const sanitizeSvg = (node) => {
}
}
// If legacy xlink:href is present but href is missing, mirror it to href for modern browsers
const xlinkHref = node.getAttributeNS(NS.XLINK, 'href')
if (xlinkHref) {
node.setAttribute('href', xlinkHref)
node.removeAttributeNS(NS.XLINK, 'href')
}
Object.values(seAttrs).forEach(([att, val, ns]) => {
node.setAttributeNS(ns, att, val)
})
@@ -247,6 +258,24 @@ export const sanitizeSvg = (node) => {
node.remove()
return
}
// For <use> elements with missing width/height, derive defaults from referenced viewBox/size for proper sizing/selection
if (node.nodeName === 'use') {
const ref = getRefElem(getHref(node))
if (ref) {
const refViewBox = ref.getAttribute('viewBox')
const viewBoxParts = refViewBox ? refViewBox.split(/[\s,]+/).map(Number) : null
const refWidth = Number(ref.getAttribute('width'))
const refHeight = Number(ref.getAttribute('height'))
if (!node.hasAttribute('width')) {
const width = viewBoxParts?.[2] || refWidth
if (width) node.setAttribute('width', width)
}
if (!node.hasAttribute('height')) {
const height = viewBoxParts?.[3] || refHeight
if (height) node.setAttribute('height', height)
}
}
}
// if the element has attributes pointing to a non-local reference,
// need to remove the attribute
['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke'].forEach((attr) => {

View File

@@ -1131,7 +1131,11 @@ export let getRotationAngle = (elem, toRad) => {
* @returns {Element} Reference element
*/
export const getRefElem = attrVal => {
return getElement(getUrlFromAttr(attrVal).substr(1))
if (!attrVal) return null
const url = getUrlFromAttr(attrVal)
if (!url) return null
const id = url[0] === '#' ? url.substr(1) : url
return getElement(id)
}
/**
* Get the reference element associated with the given attribute value.

View File

@@ -44,8 +44,14 @@ export default {
* @returns {void}
*/
const importImage = (e) => {
const fileInput = (e.target && e.target.type === 'file') ? e.target : null
const resetFileInput = () => {
if (fileInput) {
fileInput.value = ''
}
}
// only import files
if (!e.dataTransfer.types.includes('Files')) return
if (e.dataTransfer && !e.dataTransfer.types?.includes('Files')) return
$id('se-prompt-dialog').title = this.i18next.t('notification.loadingImage')
$id('se-prompt-dialog').setAttribute('close', false)
@@ -54,10 +60,12 @@ export default {
const file = (e.type === 'drop') ? e.dataTransfer.files[0] : e.currentTarget.files[0]
if (!file) {
$id('se-prompt-dialog').setAttribute('close', true)
resetFileInput()
return
}
if (!file.type.includes('image')) {
resetFileInput()
return
}
// Detected an image
@@ -73,6 +81,7 @@ export default {
// highlight imported element, otherwise we get strange empty selectbox
this.svgCanvas.selectOnly([newElement])
$id('se-prompt-dialog').setAttribute('close', true)
resetFileInput()
}
reader.readAsText(file)
} else {
@@ -103,6 +112,7 @@ export default {
this.svgCanvas.alignSelectedElements('c', 'page')
this.topPanel.updateContextPanel()
$id('se-prompt-dialog').setAttribute('close', true)
resetFileInput()
}
// create dummy img so we know the default dimensions
let imgWidth = 100