move untested extensions to the archive folder (see comment)

anyone wishing to test and confirm it should go back to the project can submit an issue (or better propose a PR).
This commit is contained in:
JFH
2021-08-01 23:45:44 +02:00
parent 015767974e
commit a99761152d
28 changed files with 0 additions and 0 deletions

View File

@@ -1,115 +0,0 @@
/**
* @file ext-closepath.js
*
* @license MIT
*
* @copyright 2010 Jeff Schiller
*
*/
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
// This extension adds a simple button to the contextual panel for paths
// The button toggles whether the path is open or closed
export default {
name: 'closepath',
async init ({ _importLocale }) {
const svgEditor = this;
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
let selElems;
const updateButton = function (path) {
const seglist = path.pathSegList;
const closed = seglist.getItem(seglist.numberOfItems - 1).pathSegType === 1;
const showbutton = closed ? 'tool_openpath' : 'tool_closepath';
const hidebutton = closed ? 'tool_closepath' : 'tool_openpath';
$id(hidebutton).style.display = 'none';
$id(showbutton).style.display = 'block';
};
const showPanel = function (on) {
$id('closepath_panel').style.display = (on) ? 'block' : 'none';
if (on) {
const path = selElems[0];
if (path) { updateButton(path); }
}
};
const toggleClosed = function () {
const path = selElems[0];
if (path) {
const seglist = path.pathSegList;
const last = seglist.numberOfItems - 1;
// is closed
if (seglist.getItem(last).pathSegType === 1) {
seglist.removeItem(last);
} else {
seglist.appendItem(path.createSVGPathSegClosePath());
}
updateButton(path);
}
};
const buttons = [
{
id: 'tool_openpath',
icon: 'openpath.png',
type: 'context',
panel: 'closepath_panel',
events: {
click () {
toggleClosed();
}
}
},
{
id: 'tool_closepath',
icon: 'closepath.png',
type: 'context',
panel: 'closepath_panel',
events: {
click () {
toggleClosed();
}
}
}
];
return {
name: strings.name,
svgicons: 'closepath_icons.svg',
buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
}),
callback () {
$id("closepath_panel").style.display = 'none';
},
selectedChanged (opts) {
selElems = opts.elems;
let i = selElems.length;
while (i--) {
const elem = selElems[i];
if (elem && elem.tagName === 'path') {
if (opts.selectedElement && !opts.multiselected) {
showPanel(true);
} else {
showPanel(false);
}
} else {
showPanel(false);
}
}
}
};
}
};

View File

@@ -1,11 +0,0 @@
export default {
name: 'ClosePath',
buttons: [
{
title: 'Open path'
},
{
title: 'Close path'
}
]
};

View File

@@ -1,11 +0,0 @@
export default {
name: '闭合路径',
buttons: [
{
title: '打开路径'
},
{
title: '关闭路径'
}
]
};

View File

@@ -1,315 +0,0 @@
/* globals seConfirm */
/**
* @file ext-foreignobject.js
*
* @license Apache-2.0
*
* @copyright 2010 Jacques Distler, 2010 Alexis Deveria
*
*/
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
export default {
name: 'foreignobject',
async init (S) {
const svgEditor = this;
const { text2xml, NS } = S;
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
const
// {svgcontent} = S,
// addElem = svgCanvas.addSVGElementFromJson,
svgdoc = S.svgroot.parentNode.ownerDocument;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
const properlySourceSizeTextArea = function () {
// TODO: remove magic numbers here and get values from CSS
const height = parseFloat(getComputedStyle($id('svg_source_container'), null).height.replace("px", "")) - 80;
$id('svg_source_textarea').style.height = height + "px";
};
/**
* @param {boolean} on
* @returns {void}
*/
function showPanel (on) {
let fcRules = $id('fc_rules');
if (!fcRules) {
fcRules = document.createElement('style');
fcRules.setAttribute('id', 'fc_rules');
document.getElementsByTagName("head")[0].appendChild(fcRules);
}
fcRules.textContent = !on ? '' : ' #tool_topath { display: none !important; }';
$id('foreignObject_panel').style.display = (on) ? 'block' : 'none';
}
/**
* @param {boolean} on
* @returns {void}
*/
function toggleSourceButtons (on) {
$id('tool_source_save').style.display = (!on) ? 'block' : 'none';
$id('tool_source_cancel').style.display = (!on) ? 'block' : 'none';
$id('foreign_save').style.display = (on) ? 'block' : 'none';
$id('foreign_cancel').style.display = (on) ? 'block' : 'none';
}
let selElems;
let started;
let newFO;
let editingforeign = false;
/**
* This function sets the content of element elt to the input XML.
* @param {string} xmlString - The XML text
* @returns {boolean} This function returns false if the set was unsuccessful, true otherwise.
*/
function setForeignString (xmlString) {
const elt = selElems[0]; // The parent `Element` to append to
try {
// convert string into XML document
const oi = (xmlString.indexOf('xmlns:oi') !== -1) ? ' xmlns:oi="' + NS.OI + '"' : '';
const newDoc = text2xml('<svg xmlns="' + NS.SVG + '" xmlns:xlink="' + NS.XLINK + '" '+ oi +'>' + xmlString + '</svg>');
// run it through our sanitizer to remove anything we do not support
svgCanvas.sanitizeSvg(newDoc.documentElement);
elt.replaceWith(svgdoc.importNode(newDoc.documentElement.firstChild, true));
svgCanvas.call('changed', [ elt ]);
svgCanvas.clearSelection();
} catch (e) {
// Todo: Surface error to user
console.error(e);
return false;
}
return true;
}
/**
*
* @returns {void}
*/
function showForeignEditor () {
const elt = selElems[0];
if (!elt || editingforeign) { return; }
editingforeign = true;
toggleSourceButtons(true);
elt.removeAttribute('fill');
const str = svgCanvas.svgToString(elt, 0);
$id('svg_source_textarea').value = str;
$id('#svg_source_editor').style.display = 'block';
properlySourceSizeTextArea();
$id('svg_source_textarea').focus();
}
/**
* @param {string} attr
* @param {string|Float} val
* @returns {void}
*/
function setAttr (attr, val) {
svgCanvas.changeSelectedAttribute(attr, val);
svgCanvas.call('changed', selElems);
}
const buttons = [ {
id: 'tool_foreign',
icon: 'foreignobject-tool.png',
type: 'mode',
events: {
click () {
svgCanvas.setMode('foreign');
}
}
}, {
id: 'edit_foreign',
icon: 'foreignobject-edit.png',
type: 'context',
panel: 'foreignObject_panel',
events: {
click () {
showForeignEditor();
}
}
} ];
const contextTools = [
{
type: 'input',
panel: 'foreignObject_panel',
id: 'foreign_width',
size: 3,
events: {
change () {
setAttr('width', this.value);
}
}
}, {
type: 'input',
panel: 'foreignObject_panel',
id: 'foreign_height',
events: {
change () {
setAttr('height', this.value);
}
}
}, {
type: 'input',
panel: 'foreignObject_panel',
id: 'foreign_font_size',
size: 2,
defval: 16,
events: {
change () {
setAttr('font-size', this.value);
}
}
}
];
return {
name: strings.name,
svgicons: 'foreignobject-icons.xml',
buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
}),
context_tools: strings.contextTools.map((contextTool, i) => {
return Object.assign(contextTools[i], contextTool);
}),
callback () {
$id("foreignObject_panel").style.display = 'none';
const endChanges = function () {
$id("svg_source_editor").style.display = 'none';
editingforeign = false;
$id('svg_source_textarea').blur();
toggleSourceButtons(false);
};
// TODO: Needs to be done after orig icon loads
setTimeout(function () {
// Create source save/cancel buttons
const toolSourceSave = $id('tool_source_save').cloneNode(true);
toolSourceSave.style.display = 'none';
toolSourceSave.id = 'foreign_save';
// unbind()
// const oldElement = $id('tool_source_save');
// oldElement.parentNode.replaceChild(toolSourceSave, oldElement);
$id('tool_source_back').append(toolSourceSave);
toolSourceSave.addEventListener('click', () => function () {
if (!editingforeign) { return; }
if (!setForeignString($id('svg_source_textarea').value)) {
const ok = seConfirm('Errors found. Revert to original?');
if (!ok) { return; }
endChanges();
} else {
endChanges();
}
// setSelectMode();
});
const oldToolSourceCancel = $id('tool_source_cancel');
const toolSourceCancel = oldToolSourceCancel.cloneNode(true);
toolSourceCancel.style.display = 'none';
toolSourceCancel.id = 'foreign_cancel';
$id('tool_source_back').append(toolSourceCancel);
toolSourceCancel.addEventListener('click', () => function () {
endChanges();
});
// unbind()
// var oldToolSourceCancel = $id('tool_source_cancel');
// oldToolSourceCancel.parentNode.replaceChild(toolSourceCancel, oldToolSourceCancel);
}, 3000);
},
mouseDown (opts) {
// const e = opts.event;
if (svgCanvas.getMode() !== 'foreign') {
return undefined;
}
started = true;
newFO = svgCanvas.addSVGElementFromJson({
element: 'foreignObject',
attr: {
x: opts.start_x,
y: opts.start_y,
id: svgCanvas.getNextId(),
'font-size': 16, // cur_text.font_size,
width: '48',
height: '20',
style: 'pointer-events:inherit'
}
});
const m = svgdoc.createElementNS(NS.MATH, 'math');
m.setAttributeNS(NS.XMLNS, 'xmlns', NS.MATH);
m.setAttribute('display', 'inline');
const mi = svgdoc.createElementNS(NS.MATH, 'mi');
mi.setAttribute('mathvariant', 'normal');
mi.textContent = '\u03A6';
const mo = svgdoc.createElementNS(NS.MATH, 'mo');
mo.textContent = '\u222A';
const mi2 = svgdoc.createElementNS(NS.MATH, 'mi');
mi2.textContent = '\u2133';
m.append(mi, mo, mi2);
newFO.append(m);
return {
started: true
};
},
mouseUp (_opts) {
// const e = opts.event;
if (svgCanvas.getMode() !== 'foreign' || !started) {
return undefined;
}
const attrs = {
width: newFO.getAttribute('width'),
height: newFO.getAttribute('height')
};
const keep = (attrs.width !== '0' || attrs.height !== '0');
svgCanvas.addToSelection([ newFO ], true);
return {
keep,
element: newFO
};
},
selectedChanged (opts) {
// Use this to update the current selected elements
selElems = opts.elems;
let i = selElems.length;
while (i--) {
const elem = selElems[i];
if (elem && elem.tagName === 'foreignObject') {
if (opts.selectedElement && !opts.multiselected) {
$id('foreign_font_size').value = elem.getAttribute('font-size');
$id('foreign_width').value = elem.getAttribute('width');
$id('foreign_height').value = elem.getAttribute('height');
showPanel(true);
} else {
showPanel(false);
}
} else {
showPanel(false);
}
}
},
elementChanged (_opts) {
// const elem = opts.elems[0];
}
};
}
};

View File

@@ -1,25 +0,0 @@
export default {
name: 'foreignObject',
buttons: [
{
title: 'Foreign Object Tool'
},
{
title: 'Edit ForeignObject Content'
}
],
contextTools: [
{
title: "Change foreignObject's width",
label: 'w'
},
{
title: "Change foreignObject's height",
label: 'h'
},
{
title: "Change foreignObject's font size",
label: 'font-size'
}
]
};

View File

@@ -1,25 +0,0 @@
export default {
name: '外部对象',
buttons: [
{
title: '外部对象工具'
},
{
title: '编辑外部对象内容'
}
],
contextTools: [
{
title: '改变外部对象宽度',
label: 'w'
},
{
title: '改变外部对象高度',
label: 'h'
},
{
title: '改变外部对象文字大小',
label: '文字大小'
}
]
};

View File

@@ -1,288 +0,0 @@
/* globals MathJax */
/**
* @file ext-mathjax.js
*
* @license MIT
*
* @copyright 2013 Jo Segaert
*
*/
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
export default {
name: 'mathjax',
async init ({ $ }) {
const svgEditor = this;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
// Configuration of the MathJax extention.
// This will be added to the head tag before MathJax is loaded.
const /* mathjaxConfiguration = `<script type="text/x-mathjax-config">
MathJax.Hub.Config({
extensions: ['tex2jax.js'],
jax: ['input/TeX', 'output/SVG'],
showProcessingMessages: true,
showMathMenu: false,
showMathMenuMSIE: false,
errorSettings: {
message: ['[Math Processing Error]'],
style: {color: '#CC0000', 'font-style': 'italic'}
},
elements: [],
tex2jax: {
ignoreClass: 'tex2jax_ignore2', processClass: 'tex2jax_process2',
},
TeX: {
extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
},
SVG: {
}
});
</script>`, */
// mathjaxSrc = 'http://cdn.mathjax.org/mathjax/latest/MathJax.js',
// Had been on https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_SVG.js
// Obtained Text-AMS-MML_SVG.js from https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.3/config/TeX-AMS-MML_SVG.js
{ uiStrings } = svgEditor;
let
math;
let locationX;
let locationY;
let mathjaxLoaded = false;
// TODO: Implement language support. Move these uiStrings to the locale files and
// the code to the langReady callback. Also i18nize alert and HTML below
$.extend(uiStrings, {
mathjax: {
embed_svg: 'Save as mathematics',
embed_mathml: 'Save as figure',
svg_save_warning: 'The math will be transformed into a figure is ' +
'manipulatable like everything else. You will not be able to ' +
'manipulate the TeX-code anymore.',
mathml_save_warning: 'Advised. The math will be saved as a figure.',
title: 'Mathematics code editor'
}
});
/**
*
* @returns {void}
*/
function saveMath () {
const code = $id('mathjax_code_textarea').value;
// displaystyle to force MathJax NOT to use the inline style. Because it is
// less fancy!
MathJax.Hub.queue.Push([ 'Text', math, '\\displaystyle{' + code + '}' ]);
/*
* The MathJax library doesn't want to bloat your webpage so it creates
* every symbol (glymph) you need only once. These are saved in a `<svg>` on
* the top of your html document, just under the body tag. Each glymph has
* its unique id and is saved as a `<path>` in the `<defs>` tag of the `<svg>`
*
* Then when the symbols are needed in the rest of your html document they
* are refferd to by a `<use>` tag.
* Because of bug 1076 we can't just grab the defs tag on the top and add it
* to your formula's `<svg>` and copy the lot. So we have to replace each
* `<use>` tag by its `<path>`.
*/
MathJax.Hub.queue.Push(
function () {
const mathjaxMath = $('.MathJax_SVG');
const svg = $(mathjaxMath.html());
svg.find('use').each(function () {
// TODO: find a less pragmatic and more elegant solution to this.
const id = $(this).attr('href')
? $(this).attr('href').slice(1) // Works in Chrome.
: $(this).attr('xlink:href').slice(1); // Works in Firefox.
const glymph = $('#' + id).clone().removeAttr('id');
const x = $(this).attr('x');
const y = $(this).attr('y');
const transform = $(this).attr('transform');
if (transform && (x || y)) {
glymph.attr('transform', transform + ' translate(' + x + ',' + y + ')');
} else if (transform) {
glymph.attr('transform', transform);
} else if (x || y) {
glymph.attr('transform', 'translate(' + x + ',' + y + ')');
}
$(this).replaceWith(glymph);
});
// Remove the style tag because it interferes with SVG-Edit.
svg.removeAttr('style');
svg.attr('xmlns', 'http://www.w3.org/2000/svg');
svgCanvas.importSvgString($('<div>').append(svg.clone()).html(), true);
svgCanvas.ungroupSelectedElement();
// TODO: To undo the adding of the Formula you now have to undo twice.
// This should only be once!
svgCanvas.moveSelectedElements(locationX, locationY, true);
}
);
}
const buttons = [ {
id: 'tool_mathjax',
type: 'mode',
icon: 'mathjax.png',
events: {
async click () {
// Set the mode.
svgCanvas.setMode('mathjax');
// Only load Mathjax when needed, we don't want to strain Svg-Edit any more.
// From this point on it is very probable that it will be needed, so load it.
if (mathjaxLoaded === false) {
const div = document.createElement('div');
div.id = 'mathjax';
div.innerHTML = '<!-- Here is where MathJax creates the math -->' +
'<div id="mathjax_creator" class="tex2jax_process" style="display:none">' +
'$${}$$' +
'</div>' +
'<div id="mathjax_overlay"></div>' +
'<div id="mathjax_container">' +
'<div id="tool_mathjax_back" class="toolbar_button">' +
'<button id="tool_mathjax_save">OK</button>' +
'<button id="tool_mathjax_cancel">Cancel</button>' +
'</div>' +
'<fieldset>' +
'<legend id="mathjax_legend">Mathematics Editor</legend>' +
'<label>' +
'<span id="mathjax_explication">Please type your mathematics in ' +
'<a href="https://en.wikipedia.org/wiki/Help:' +
'Displaying_a_formula" target="_blank">TeX</a> code.' +
'</span></label>' +
'<textarea id="mathjax_code_textarea" spellcheck="false"></textarea>' +
'</fieldset>' +
'</div>';
$id('svg_prefs').parentNode.insertBefore(div, $id('svg_prefs').nextSibling);
div.style.display = 'none';
// Add functionality and picture to cancel button.
$('#tool_mathjax_cancel').prepend($.getSvgIcon('cancel', true))
.on('click touched', function () {
$id("mathjax").style.display = 'none';
});
// Add functionality and picture to the save button.
$('#tool_mathjax_save').prepend($.getSvgIcon('ok', true))
.on('click touched', function () {
saveMath();
$id("mathjax").style.display = 'none';
});
// MathJax preprocessing has to ignore most of the page.
document.body.classList.add("tex2jax_ignore");
try {
await import('./mathjax/MathJax.min.js'); // ?config=TeX-AMS-MML_SVG.js');
// When MathJax is loaded get the div where the math will be rendered.
MathJax.Hub.queue.Push(function () {
math = MathJax.Hub.getAllJax('#mathjax_creator')[0];
mathjaxLoaded = true;
console.info('MathJax Loaded');
});
} catch (e) {
console.warn('Failed loading MathJax.');
// eslint-disable-next-line no-alert
alert('Failed loading MathJax. You will not be able to change the mathematics.');
}
}
}
}
} ];
return {
name: strings.name,
svgicons: 'mathjax-icons.xml',
buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
}),
mouseDown () {
if (svgCanvas.getMode() === 'mathjax') {
return { started: true };
}
return undefined;
},
mouseUp (opts) {
if (svgCanvas.getMode() === 'mathjax') {
// Get the coordinates from your mouse.
const zoom = svgCanvas.getZoom();
// Get the actual coordinate by dividing by the zoom value
locationX = opts.mouse_x / zoom;
locationY = opts.mouse_y / zoom;
$id("mathjax").style.display = 'block';
return { started: false }; // Otherwise the last selected object dissapears.
}
return undefined;
},
callback () {
const head = document.head || document.getElementsByTagName('head')[0];
const style = document.createElement('style');
style.textContent = '#mathjax fieldset{' +
'padding: 5px;' +
'margin: 5px;' +
'border: 1px solid #DDD;' +
'}' +
'#mathjax label{' +
'display: block;' +
'margin: .5em;' +
'}' +
'#mathjax legend {' +
'max-width:195px;' +
'}' +
'#mathjax_overlay {' +
'position: absolute;' +
'top: 0;' +
'left: 0;' +
'right: 0;' +
'bottom: 0;' +
'background-color: black;' +
'opacity: 0.6;' +
'z-index: 20000;' +
'}' +
'#mathjax_container {' +
'position: absolute;' +
'top: 50px;' +
'padding: 10px;' +
'background-color: #5a6162;' +
'border: 1px outset #777;' +
'opacity: 1.0;' +
'font-family: Verdana, Helvetica, sans-serif;' +
'font-size: .8em;' +
'z-index: 20001;' +
'}' +
'#tool_mathjax_back {' +
'margin-left: 1em;' +
'overflow: auto;' +
'}' +
'#mathjax_legend{' +
'font-weight: bold;' +
'font-size:1.1em;' +
'}' +
'#mathjax_code_textarea {\\n' +
'margin: 5px .7em;' +
'overflow: hidden;' +
'width: 416px;' +
'display: block;' +
'height: 100px;' +
'}';
head.appendChild(style);
}
};
}
};

View File

@@ -1,8 +0,0 @@
export default {
name: 'MathJax',
buttons: [
{
title: 'Add Mathematics'
}
]
};

View File

@@ -1,8 +0,0 @@
export default {
name: '数学',
buttons: [
{
title: '添加数学计算'
}
]
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,561 +0,0 @@
/**
* @file ext-placemark.js
*
*
* @copyright 2010 CloudCanvas, Inc. All rights reserved
*
*/
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
export default {
name: 'placemark',
async init (_S) {
const svgEditor = this;
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
const addElem = svgCanvas.addSVGElementFromJson;
let
selElems;
// editingitex = false,
// svgdoc = S.svgroot.parentNode.ownerDocument,
let started;
let newPM;
// edg = 0,
// newFOG, newFOGParent, newDef, newImageName, newMaskID,
// undoCommand = 'Not image',
// modeChangeG, ccZoom, wEl, hEl, wOffset, hOffset, ccRgbEl, brushW, brushH;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
const markerTypes = {
nomarker: {},
forwardslash:
{ element: 'path', attr: { d: 'M30,100 L70,0' } },
reverseslash:
{ element: 'path', attr: { d: 'M30,0 L70,100' } },
verticalslash:
{ element: 'path', attr: { d: 'M50,0 L50,100' } },
xmark:
{ element: 'path', attr: { d: 'M20,80 L80,20 M80,80 L20,20' } },
leftarrow:
{ element: 'path', attr: { d: 'M0,50 L100,90 L70,50 L100,10 Z' } },
rightarrow:
{ element: 'path', attr: { d: 'M100,50 L0,90 L30,50 L0,10 Z' } },
box:
{ element: 'path', attr: { d: 'M20,20 L20,80 L80,80 L80,20 Z' } },
star:
{ element: 'path', attr: { d: 'M10,30 L90,30 L20,90 L50,10 L80,90 Z' } },
mcircle:
{ element: 'circle', attr: { r: 30, cx: 50, cy: 50 } },
triangle:
{ element: 'path', attr: { d: 'M10,80 L50,20 L80,80 Z' } }
};
// duplicate shapes to support unfilled (open) marker types with an _o suffix
[ 'leftarrow', 'rightarrow', 'box', 'star', 'mcircle', 'triangle' ].forEach((v) => {
markerTypes[v + '_o'] = markerTypes[v];
});
/**
*
* @param {boolean} on
* @returns {void}
*/
function showPanel (on) {
$id('placemark_panel').style.display = (on) ? 'block' : 'none';
}
/**
* @param {Element} elem - A graphic element will have an attribute like marker-start
* @param {"marker-start"|"marker-mid"|"marker-end"} attr
* @returns {Element} The marker element that is linked to the graphic element
*/
function getLinked (elem, attr) {
if (!elem) { return null; }
const str = elem.getAttribute(attr);
if (!str) { return null; }
// const m = str.match(/\(#(?<id>.+)\)/);
// if (!m || !m.groups.id) {
const m = str.match(/\(#(.*)\)/);
if (!m || m.length !== 2) {
return null;
}
return svgCanvas.getElem(m[1]);
// return svgCanvas.getElem(m.groups.id);
}
/**
* Called when text is changed.
* @param {string} txt
* @returns {void}
*/
function updateText (txt) {
const items = txt.split(';');
selElems.forEach((elem) => {
if (elem && elem.getAttribute('class').includes('placemark')) {
const elements = elem.children;
Array.prototype.forEach.call(elements, function(i, _){
const [ , , type, n ] = i.id.split('_');
if (type === 'txt') {
txt.textContent = items[n];
}
});
}
});
}
/**
* Called when font is changed.
* @param {string} font
* @returns {void}
*/
function updateFont (font) {
font = font.split(' ');
const fontSize = Number.parseInt(font.pop());
font = font.join(' ');
selElems.forEach((elem) => {
if (elem && elem.getAttribute('class').includes('placemark')) {
const elements = elem.children;
Array.prototype.forEach.call(elements, function(i, _){
const [ , , type ] = i.id.split('_');
if (type === 'txt') {
i.style.cssText = 'font-family:' + font + ';font-size:'+fontSize+';';
}
});
}
});
}
/**
* @param {string} id
* @param {""|"\\nomarker"|"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"textmarker_top"|"textmarker_bottom"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} val
* @returns {SVGMarkerElement}
*/
function addMarker (id, val) {
let marker = svgCanvas.getElem(id);
if (marker) { return undefined; }
if (val === '' || val === 'nomarker') { return undefined; }
const color = svgCanvas.getColor('stroke');
// NOTE: Safari didn't like a negative value in viewBox
// so we use a standardized 0 0 100 100
// with 50 50 being mapped to the marker position
const scale = 2;
const strokeWidth = 10;
let refX = 50;
const refY = 50;
const viewBox = '0 0 100 100';
const markerWidth = 5 * scale;
const markerHeight = 5 * scale;
const seType = val;
if (!markerTypes[seType]) { return undefined; } // an unknown type!
// positional markers(arrows) at end of line
if (seType.includes('left')) refX = 0;
if (seType.includes('right')) refX = 100;
// create a generic marker
marker = addElem({
element: 'marker',
attr: {
id,
markerUnits: 'strokeWidth',
orient: 'auto',
style: 'pointer-events:none',
class: seType
}
});
const mel = addElem(markerTypes[seType]);
const fillcolor = (seType.substr(-2) === '_o')
? 'none'
: color;
mel.setAttribute('fill', fillcolor);
mel.setAttribute('stroke', color);
mel.setAttribute('stroke-width', strokeWidth);
marker.append(mel);
marker.setAttribute('viewBox', viewBox);
marker.setAttribute('markerWidth', markerWidth);
marker.setAttribute('markerHeight', markerHeight);
marker.setAttribute('refX', refX);
marker.setAttribute('refY', refY);
svgCanvas.findDefs().append(marker);
return marker;
}
/**
* @param {Element} el
* @param {string} val
* @returns {void}
*/
function setMarker (el, val) {
const markerName = 'marker-start';
const marker = getLinked(el, markerName);
if (marker) { marker.remove(); }
el.removeAttribute(markerName);
if (val === 'nomarker') {
svgCanvas.call('changed', [ el ]);
return;
}
// Set marker on element
const id = 'placemark_marker_' + el.id;
addMarker(id, val);
el.setAttribute(markerName, 'url(#' + id + ')');
svgCanvas.call('changed', [ el ]);
}
/**
* Called when the main system modifies an object. This routine changes
* the associated markers to be the same color.
* @param {Element} el
* @returns {void}
*/
function colorChanged (el) {
const color = el.getAttribute('stroke');
const marker = getLinked(el, 'marker-start');
if (!marker) { return; }
if (!marker.attributes.class) { return; } // not created by this extension
const ch = marker.lastElementChild;
if (!ch) { return; }
const curfill = ch.getAttribute('fill');
const curstroke = ch.getAttribute('stroke');
if (curfill && curfill !== 'none') { ch.setAttribute('fill', color); }
if (curstroke && curstroke !== 'none') { ch.setAttribute('stroke', color); }
}
/**
* Called when the main system creates or modifies an object.
* Its primary purpose is to create new markers for cloned objects.
* @param {Element} el
* @returns {void}
*/
function updateReferences (el) {
const id = 'placemark_marker_' + el.id;
const markerName = 'marker-start';
const marker = getLinked(el, markerName);
if (!marker || !marker.attributes.class) { return; } // not created by this extension
const url = el.getAttribute(markerName);
if (url) {
const len = el.id.length;
const linkid = url.substr(-len - 1, len);
if (el.id !== linkid) {
const val = $id('placemark_marker').getAttribute('value') || 'leftarrow';
addMarker(id, val);
svgCanvas.changeSelectedAttribute(markerName, 'url(#' + id + ')');
svgCanvas.call('changed', selElems);
}
}
}
/**
* @param {Event} ev
* @returns {void}
*/
function setArrowFromButton (_ev) {
const parts = this.id.split('_');
let val = parts[2];
if (parts[3]) { val += '_' + parts[3]; }
$id('placemark_marker').setAttribute('value', val);
}
/**
* @param {"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} id
* @returns {string}
*/
function getTitle (id) {
const { langList } = strings;
const item = langList.find((itm) => {
return itm.id === id;
});
return item ? item.title : id;
}
/**
* Build the toolbar button array from the marker definitions.
* @param {module:SVGEditor.Button[]} buttons
* @returns {module:SVGEditor.Button[]}
*/
function addMarkerButtons (buttons) {
Object.keys(markerTypes).forEach(function (id) {
const title = getTitle(String(id));
buttons.push({
id: 'placemark_marker_' + id,
svgicon: id,
icon: 'markers-' + id + '.png',
title,
type: 'context',
events: { click: setArrowFromButton },
panel: 'placemark_panel',
list: 'placemark_marker',
isDefault: id === 'leftarrow'
});
});
return buttons;
}
const buttons = [ {
id: 'tool_placemark',
icon: 'placemark.png',
type: 'mode',
position: 12,
events: {
click () {
showPanel(true);
svgCanvas.setMode('placemark');
}
}
} ];
const contextTools = [
{
type: 'button-select',
panel: 'placemark_panel',
id: 'placemark_marker',
colnum: 3,
events: { change: setArrowFromButton }
},
{
type: 'input',
panel: 'placemark_panel',
id: 'placemarkText',
size: 20,
defval: '',
events: {
change () {
updateText(this.value);
}
}
}, {
type: 'input',
panel: 'placemark_panel',
id: 'placemarkFont',
size: 7,
defval: 'Arial 10',
events: {
change () {
updateFont(this.value);
}
}
}
];
return {
name: strings.name,
svgicons: 'placemark-icons.xml',
buttons: addMarkerButtons(strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
})),
context_tools: strings.contextTools.map((contextTool, i) => {
return Object.assign(contextTools[i], contextTool);
}),
callback () {
$id("placemark_panel").style.display = 'none';
// const endChanges = function(){};
},
mouseDown (opts) {
// const rgb = svgCanvas.getColor('fill');
const sRgb = svgCanvas.getColor('stroke');
const sWidth = svgCanvas.getStrokeWidth();
if (svgCanvas.getMode() === 'placemark') {
started = true;
const id = svgCanvas.getNextId();
const items = $id('placemarkText').value.split(';');
let font = $id('placemarkFont').value.split(' ');
const fontSize = Number.parseInt(font.pop());
font = font.join(' ');
const x0 = opts.start_x + 10; const y0 = opts.start_y + 10;
let maxlen = 0;
const children = [ {
element: 'line',
attr: {
id: id + '_pline_0',
fill: 'none',
stroke: sRgb,
'stroke-width': sWidth,
'stroke-linecap': 'round',
x1: opts.start_x,
y1: opts.start_y,
x2: x0,
y2: y0
}
} ];
items.forEach((i, n) => {
maxlen = Math.max(maxlen, i.length);
children.push({
element: 'line',
attr: {
id: id + '_tline_' + n,
fill: 'none',
stroke: sRgb,
'stroke-width': sWidth,
'stroke-linecap': 'round',
x1: x0,
y1: y0 + (fontSize + 6) * n,
x2: x0 + i.length * fontSize * 0.5 + fontSize,
y2: y0 + (fontSize + 6) * n
}
});
children.push({
element: 'text',
attr: {
id: id + '_txt_' + n,
fill: sRgb,
stroke: 'none',
'stroke-width': 0,
x: x0 + 3,
y: y0 - 3 + (fontSize + 6) * n,
'font-family': font,
'font-size': fontSize,
'text-anchor': 'start'
},
children: [ i ]
});
});
if (items.length > 0) {
children.push({
element: 'line',
attr: {
id: id + '_vline_0',
fill: 'none',
stroke: sRgb,
'stroke-width': sWidth,
'stroke-linecap': 'round',
x1: x0,
y1: y0,
x2: x0,
y2: y0 + (fontSize + 6) * (items.length - 1)
}
});
}
newPM = svgCanvas.addSVGElementFromJson({
element: 'g',
attr: {
id,
class: 'placemark',
fontSize,
maxlen,
lines: items.length,
x: opts.start_x,
y: opts.start_y,
px: opts.start_x,
py: opts.start_y
},
children
});
setMarker(
newPM.firstElementChild,
$id('placemark_marker').getAttribute('value') || 'leftarrow'
);
return {
started: true
};
}
return undefined;
},
mouseMove (opts) {
if (!started) {
return undefined;
}
if (svgCanvas.getMode() === 'placemark') {
const x = opts.mouse_x / svgCanvas.getZoom();
const y = opts.mouse_y / svgCanvas.getZoom();
const fontSize = newPM.getAttribute('fontSize');
const maxlen = newPM.getAttribute('maxlen');
const lines = newPM.getAttribute('lines');
const px = newPM.getAttribute('px');
const py = newPM.getAttribute('py');
newPM.setAttribute('x', x);
newPM.setAttribute('y', y);
const elements = newPM.children;
Array.prototype.forEach.call(elements, function(i, _){
const [ , , type, n ] = i.id.split('_');
const y0 = y + (fontSize + 6) * n;
const x0 = x + maxlen * fontSize * 0.5 + fontSize;
const nx = (x + (x0 - x) / 2 < px) ? x0 : x;
const ny = (y + ((fontSize + 6) * (lines - 1)) / 2 < py)
? y + (fontSize + 6) * (lines - 1)
: y;
if (type === 'pline') {
i.setAttribute('x2', nx);
i.setAttribute('y2', ny);
}
if (type === 'tline') {
i.setAttribute('x1', x);
i.setAttribute('y1', y0);
i.setAttribute('x2', x0);
i.setAttribute('y2', y0);
}
if (type === 'vline') {
i.setAttribute('x1', nx);
i.setAttribute('y1', y);
i.setAttribute('x2', nx);
i.setAttribute('y2', y + (fontSize + 6) * (lines - 1));
}
if (type === 'txt') {
i.setAttribute('x', x + fontSize / 2);
i.setAttribute('y', y0 - 3);
}
});
return {
started: true
};
}
return undefined;
},
mouseUp () {
if (svgCanvas.getMode() === 'placemark') {
const x = newPM.getAttribute('x');
const y = newPM.getAttribute('y');
const px = newPM.getAttribute('px');
const py = newPM.getAttribute('py');
return {
keep: (x != px && y != py), // eslint-disable-line eqeqeq
element: newPM
};
}
return undefined;
},
selectedChanged (opts) {
// Use this to update the current selected elements
selElems = opts.elems;
selElems.forEach((elem) => {
if (elem && elem.getAttribute('class').includes('placemark')) {
const txt = [];
const elements = elem.children;
Array.prototype.forEach.call(elements, function(i){
const [ , , type ] = i.id.split('_');
if (type === 'txt') {
$id('placemarkFont').value = (
i.getAttribute('font-family') + ' ' + i.getAttribute('font-size')
);
txt.push(i.textContent);
}
});
$id('placemarkText').value = txt.join(';');
showPanel(true);
} else {
showPanel(false);
}
});
},
elementChanged (opts) {
opts.elems.forEach((elem) => {
if (elem.id.includes('pline_0')) { // need update marker of pline_0
colorChanged(elem);
updateReferences(elem);
}
});
}
};
}
};

View File

@@ -1,40 +0,0 @@
export default {
name: 'placemark',
langList: [
{ id: 'nomarker', title: 'No Marker' },
{ id: 'leftarrow', title: 'Left Arrow' },
{ id: 'rightarrow', title: 'Right Arrow' },
{ id: 'forwardslash', title: 'Forward Slash' },
{ id: 'reverseslash', title: 'Reverse Slash' },
{ id: 'verticalslash', title: 'Vertical Slash' },
{ id: 'box', title: 'Box' },
{ id: 'star', title: 'Star' },
{ id: 'xmark', title: 'X' },
{ id: 'triangle', title: 'Triangle' },
{ id: 'mcircle', title: 'Circle' },
{ id: 'leftarrow_o', title: 'Open Left Arrow' },
{ id: 'rightarrow_o', title: 'Open Right Arrow' },
{ id: 'box_o', title: 'Open Box' },
{ id: 'star_o', title: 'Open Star' },
{ id: 'triangle_o', title: 'Open Triangle' },
{ id: 'mcircle_o', title: 'Open Circle' }
],
buttons: [
{
title: 'Placemark Tool'
}
],
contextTools: [
{
title: 'Select Place marker type'
},
{
title: 'Text on separated with ; ',
label: 'Text'
},
{
title: 'Font for text',
label: ''
}
]
};

View File

@@ -1,83 +0,0 @@
/**
* @file ext-server_moinsave.js
*
* @license (MIT OR GPL-2.0-or-later)
*
* @copyright 2010 Alexis Deveria, 2011 MoinMoin:ReimarBauer
* adopted for moinmoins item storage. It sends in one post png and svg data
* (I agree to dual license my work to additional GPLv2 or later)
*/
import { Canvg as canvg } from 'canvg';
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
export default {
name: 'server_moinsave',
async init ({ encode64 }) {
const svgEditor = this;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
const { svgCanvas } = svgEditor;
const { $id } = svgCanvas;
const saveSvgAction = '/+modify';
// Create upload target (hidden iframe)
// Hiding by size instead of display to avoid FF console errors
// with `getBBox` in browser.js `supportsPathBBox_`)
const iframe = document.createElement('IFRAME');
iframe.src="data:text/html;base64,PGh0bWw+PC9odG1sPg==";
document.body.append(iframe);
iframe.name = "output_frame";
iframe.contentWindow.document.title = strings.hiddenframe;
iframe.style.cssText = "width:0;height:0;";
svgEditor.setCustomHandlers({
async save (win, data) {
const svg = '<?xml version="1.0"?>\n' + data;
const { pathname } = new URL(location);
const name = pathname.replace(/\/+get\//, '');
const svgData = encode64(svg);
if (!$id('export_canvas')) {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'export_canvas');
canvas.style.display = 'none';
document.body.appendChild(canvas);
}
const c = $id('export_canvas');
c.style.width = svgCanvas.contentW;
c.style.height = svgCanvas.contentH;
await canvg(c, svg);
const datauri = c.toDataURL('image/png');
// const {uiStrings} = svgEditor;
const pngData = encode64(datauri); // Brett: This encoding seems unnecessary
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', saveSvgAction + '/' + name);
form.setAttribute('target', 'output_frame');
// eslint-disable-next-line no-unsanitized/property
form.innerHTML = `<input type="hidden" name="png_data" value="${pngData}">
<input type="hidden" name="filepath" value="${svgData}">
<input type="hidden" name="filename" value="drawing.svg">
<input type="hidden" name="contenttype" value="application/x-svgdraw">`;
document.body.append(form);
form.submit();
form.remove();
// eslint-disable-next-line no-alert
alert(strings.saved);
top.window.location = '/' + name;
}
});
}
};

View File

@@ -1,4 +0,0 @@
export default {
saved: 'Saved! Return to Item View!',
hiddenframe: 'Moinsave frame to store hidden values'
};

View File

@@ -1,4 +0,0 @@
export default {
saved: '已保存! 返回视图!',
hiddenframe: 'Moinsave frame to store hidden values'
};

View File

@@ -1,12 +0,0 @@
<?php
$allowedMimeTypesBySuffix = array(
'svg' => 'image/svg+xml;charset=UTF-8',
'png' => 'image/png',
'jpeg' => 'image/jpeg',
'bmp' => 'image/bmp',
'webp' => 'image/webp',
'pdf' => 'application/pdf'
);
?>

View File

@@ -1,37 +0,0 @@
// TODO: Might add support for "exportImage" custom
// handler as in "ext-server_opensave.js" (and in savefile.php)
export default {
name: 'php_savefile',
init () {
const svgEditor = this;
const {
canvas: svgCanvas
} = svgEditor;
/**
* Get file name out of SVGEdit document title.
* @returns {string}
*/
function getFileNameFromTitle () {
const title = svgCanvas.getDocumentTitle();
return title.trim();
}
const saveSvgAction = './savefile.php';
svgEditor.setCustomHandlers({
save (win, data) {
const svg = '<?xml version="1.0" encoding="UTF-8"?>\n' + data;
const filename = getFileNameFromTitle();
// $.post(saveSvgAction, { output_svg: svg, filename });
const postData = { output_svg: svg, filename };
fetch(saveSvgAction, {
method: "POST",
body: JSON.stringify(postData)
}).then( (res) => {
return res;
})
.catch( (error) => { console.info('error =', error);});
}
});
}
};

View File

@@ -1,300 +0,0 @@
/**
* @file ext-server_opensave.js
*
* @license MIT
*
* @copyright 2010 Alexis Deveria
*
*/
import { Canvg as canvg } from 'canvg';
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
export default {
name: 'server_opensave',
async init ({ decode64, encode64 }) {
const svgEditor = this;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
const {
curConfig: {
avoidClientSide, // Deprecated
avoidClientSideDownload, avoidClientSideOpen
},
canvas: svgCanvas
} = svgEditor;
const { $id } = svgCanvas;
/**
*
* @returns {string}
*/
function getFileNameFromTitle () {
const title = svgCanvas.getDocumentTitle();
// We convert (to underscore) only those disallowed Win7 file name characters
return title.trim().replace(/[/\\:*?"<>|]/g, '_');
}
/**
* Escapes XML predefined entities for quoted attributes.
* @param {string} str
* @returns {string}
*/
function xhtmlEscape (str) {
return str.replace(/&(?!amp;)/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;'); // < is actually disallowed above anyways
}
/**
*
* @param {string} [filename="image"]
* @param {string} suffix To add to file name
* @param {string} uri
* @returns {boolean}
*/
function clientDownloadSupport (filename, suffix, uri) {
if (avoidClientSide || avoidClientSideDownload) {
return false;
}
const support = document.querySelector('a').download === '';
let a;
if (support) {
a = document.createElement("a");
a.text = 'hidden';
a.download = (filename || 'image') + suffix;
a.href = uri;
a.style.dispaly = 'none';
document.body.appendChild(a);
a.click();
return true;
}
return false;
}
const
saveSvgAction = './filesave.php';
const saveImgAction = './filesave.php';
// Create upload target (hidden iframe)
let cancelled = false;
// Hiding by size instead of display to avoid FF console errors
// with `getBBox` in browser.js `supportsPathBBox_`)
const iframe = document.createElement('IFRAME');
iframe.src="data:text/html;base64,PGh0bWw+";
document.body.append(iframe);
iframe.name = "output_frame";
iframe.contentWindow.document.title = strings.hiddenframe;
iframe.style.cssText = "width:0;height:0;";
svgEditor.setCustomHandlers({
save (win, data) {
// Firefox doesn't seem to know it is UTF-8 (no matter whether we use or skip the clientDownload code) despite the Content-Disposition header containing UTF-8, but adding the encoding works
const svg = '<?xml version="1.0" encoding="UTF-8"?>\n' + data;
const filename = getFileNameFromTitle();
if (clientDownloadSupport(filename, '.svg', 'data:image/svg+xml;charset=UTF-8;base64,' + encode64(svg))) {
return;
}
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', saveSvgAction);
form.setAttribute('target', 'output_frame');
// eslint-disable-next-line no-unsanitized/property
form.innerHTML = `<input type="hidden" name="output_svg" value="${xhtmlEscape(svg)}">
<input type="hidden" name="filename" value="${xhtmlEscape(filename)}">`;
document.body.append(form);
form.submit();
form.remove();
},
exportPDF (win, data) {
const filename = getFileNameFromTitle();
const datauri = data.output;
if (clientDownloadSupport(filename, '.pdf', datauri)) {
return;
}
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', saveImgAction);
form.setAttribute('target', 'output_frame');
// eslint-disable-next-line no-unsanitized/property
form.innerHTML = `<input type="hidden" name="output_img" value="${datauri}">
<input type="hidden" name="mime" value="application/pdf">
<input type="hidden" name="filename" value="${xhtmlEscape(filename)}">`;
document.body.append(form);
form.submit();
form.remove();
},
// Todo: Integrate this extension with a new built-in exportWindowType, "download"
async exportImage (win, data) {
const { issues, mimeType, quality } = data;
if (!$id('export_canvas')) {
const canvasx = document.createElement("CANVAS");
canvasx.id = 'export_canvas';
canvasx.style.display = 'none';
document.body.appendChild(canvasx);
}
const c = $id('export_canvas');
c.style.width = svgCanvas.contentW;
c.style.height = svgCanvas.contentH;
await canvg(c, data.svg);
const datauri = quality ? c.toDataURL(mimeType, quality) : c.toDataURL(mimeType);
// Check if there are issues
let pre; let note = '';
if (issues.length) {
pre = '\n \u2022 '; // Bullet
note += ('\n\n' + pre + issues.join(pre));
}
if (note.length) {
// eslint-disable-next-line no-alert
alert(note);
}
const filename = getFileNameFromTitle();
const suffix = '.' + data.type.toLowerCase();
if (clientDownloadSupport(filename, suffix, datauri)) {
return;
}
const form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', saveImgAction);
form.setAttribute('target', 'output_frame');
// eslint-disable-next-line no-unsanitized/property
form.innerHTML = `<input type="hidden" name="output_img" value="${datauri}">
<input type="hidden" name="mime" value="${mimeType}">
<input type="hidden" name="filename" value="${xhtmlEscape(filename)}">`;
document.body.append(form);
form.submit();
form.remove();
}
});
// Do nothing if client support is found
if (window.FileReader && !avoidClientSideOpen) { return; }
// Change these to appropriate script file
const openSvgAction = './fileopen.php?type=load_svg';
const importSvgAction = './fileopen.php?type=import_svg';
const importImgAction = './fileopen.php?type=import_img';
// Set up function for PHP uploader to use
svgEditor.processFile = function (str64, type) {
let xmlstr;
if (cancelled) {
cancelled = false;
return;
}
if($id("dialog_box") != null) $id("dialog_box").style.display = 'none';
if (type !== 'import_img') {
xmlstr = decode64(str64);
}
switch (type) {
case 'load_svg':
svgCanvas.clear();
svgCanvas.setSvgString(xmlstr);
svgEditor.updateCanvas();
break;
case 'import_svg':
svgCanvas.importSvgString(xmlstr);
svgEditor.updateCanvas();
break;
case 'import_img':
svgCanvas.setGoodImage(str64);
break;
}
};
// Create upload form
const openSvgForm = document.createElement("FORM");
openSvgForm.action = openSvgAction;
openSvgForm.enctype = 'multipart/form-data';
openSvgForm.method = 'post';
openSvgForm.target = 'output_frame';
// Create import form
const importSvgForm = openSvgForm.cloneNode(true);
importSvgForm.action = importSvgAction;
// Create image form
const importImgForm = openSvgForm.cloneNode(true);
importImgForm.action = importImgAction;
// It appears necessary to rebuild this input every time a file is
// selected so the same file can be picked and the change event can fire.
/**
*
* @param {external:jQuery} form
* @returns {void}
*/
function rebuildInput (form) {
form.empty();
const inp = document.createElement('input');
inp.type = 'file';
inp.name = 'svg_file';
form.appendChild(inp);
/**
* Submit the form, empty its contents for reuse and show
* uploading message.
* @returns {Promise<void>}
*/
async function submit () {
// This submits the form, which returns the file data using `svgEditor.processFile()`
form.submit();
rebuildInput(form);
// await $.process_cancel(strings.uploading);
cancelled = true;
if($id("dialog_box") != null) $id("dialog_box").style.display = 'none';
}
if (form[0] === openSvgForm[0]) {
inp.change(async function () {
// This takes care of the "are you sure" dialog box
const ok = await svgEditor.openPrep();
if (!ok) {
rebuildInput(form);
return;
}
await submit();
});
} else {
inp.change(async function () {
// This submits the form, which returns the file data using svgEditor.processFile()
await submit();
});
}
}
// Create the input elements
rebuildInput(openSvgForm);
rebuildInput(importSvgForm);
rebuildInput(importImgForm);
// Add forms to buttons
$id("tool_open").style.display = 'block';
$id("tool_import").style.display = 'block';
$id('tool_open').insertBefore(openSvgForm, $id('tool_open').firstChild);
$id('tool_import').insertBefore(importSvgForm, $id('tool_import').firstChild);
$id('tool_image').insertBefore(importImgForm, $id('tool_image').firstChild);
}
};

View File

@@ -1,58 +0,0 @@
<!DOCTYPE html>
<?php
/*
* fileopen.php
* To be used with ext-server_opensave.js for SVG-edit
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Alexis Deveria
*
*/
// Very minimal PHP file, all we do is Base64 encode the uploaded file and
// return it to the editor
if (!isset($_REQUEST['type'])) {
echo 'No type given';
exit;
}
$type = $_REQUEST['type'];
if (!in_array($type, array('load_svg', 'import_svg', 'import_img'))) {
echo 'Not a recognized type';
exit;
}
require('allowedMimeTypes.php');
$file = $_FILES['svg_file']['tmp_name'];
$output = file_get_contents($file);
$prefix = '';
// Make Data URL prefix for import image
if ($type == 'import_img') {
$info = getimagesize($file);
if (!in_array($info['mime'], $allowedMimeTypesBySuffix)) {
echo 'Disallowed MIME for supplied file';
exit;
}
$prefix = 'data:' . $info['mime'] . ';base64,';
}
?>
<html>
<head>
<meta charset="utf-8" />
<title>-</title>
<script>
top.svgEditor.processFile("<?php
// This should be safe since SVG edit does its own filtering (e.g., if an SVG file contains scripts)
echo $prefix . base64_encode($output);
?>", "<?php echo $type; ?>");
</script>
</head>
<body></body>
</html>

View File

@@ -1,60 +0,0 @@
<?php
/*
* filesave.php
* To be used with ext-server_opensave.js for SVG-edit
*
* Licensed under the MIT License
*
* Copyright(c) 2010 Alexis Deveria
*
*/
function encodeRFC5987ValueChars ($str) {
// See https://tools.ietf.org/html/rfc5987#section-3.2.1
// For better readability within headers, add back the characters escaped by rawurlencode but still allowable
// Although RFC3986 reserves "!" (%21), RFC5987 does not
return preg_replace_callback('@%(2[1346B]|5E|60|7C)@', function ($matches) {
return chr('0x' . $matches[1]);
}, rawurlencode($str));
}
require('allowedMimeTypes.php');
$mime = (!isset($_POST['mime']) || !in_array($_POST['mime'], $allowedMimeTypesBySuffix)) ? 'image/svg+xml;charset=UTF-8' : $_POST['mime'];
if (!isset($_POST['output_svg']) && !isset($_POST['output_img'])) {
die('post fail');
}
$file = '';
$suffix = '.' . array_search($mime, $allowedMimeTypesBySuffix);
if (isset($_POST['filename']) && strlen($_POST['filename']) > 0) {
$file = $_POST['filename'] . $suffix;
} else {
$file = 'image' . $suffix;
}
if ($suffix == '.svg') {
$contents = $_POST['output_svg'];
} else {
$contents = $_POST['output_img'];
$pos = (strpos($contents, 'base64,') + 7);
$contents = base64_decode(substr($contents, $pos));
}
header('Cache-Control: public');
header('Content-Description: File Transfer');
// See https://tools.ietf.org/html/rfc6266#section-4.1
header("Content-Disposition: attachment; filename*=UTF-8''" . encodeRFC5987ValueChars(
// preg_replace('@[\\\\/:*?"<>|]@', '', $file) // If we wanted to strip Windows-disallowed characters server-side (but not a security issue, so we can strip client-side instead)
$file
));
header('Content-Type: ' . $mime);
header('Content-Transfer-Encoding: binary');
echo $contents;
?>

View File

@@ -1,4 +0,0 @@
export default {
uploading: 'Uploading...',
hiddenframe: 'Opensave frame to store hidden values'
};

View File

@@ -1,4 +0,0 @@
export default {
uploading: '正在上传...',
hiddenframe: 'Opensave frame to store hidden values'
};

View File

@@ -1,16 +0,0 @@
<?php
// You must first create a file "savefile_config.php" in this extensions directory and do whatever
// checking of user credentials, etc. that you wish; otherwise anyone will be able to post SVG
// files to your server which may cause disk space or possibly security problems
require('savefile_config.php');
if (!isset($_POST['output_svg'])) {
print 'You must supply output_svg';
exit;
}
$svg = $_POST['output_svg'];
$filename = (isset($_POST['filename']) && !empty($_POST['filename']) ? preg_replace('@[\\\\/:*?"<>|]@u', '_', $_POST['filename']) : 'saved') . '.svg'; // These characters are indicated as prohibited by Windows
$fh = fopen($filename, 'w') or die("Can't open file");
fwrite($fh, $svg);
fclose($fh);
?>

View File

@@ -1,116 +0,0 @@
/**
* Depends on Firefox add-on and executables from
* {@link https://github.com/brettz9/webappfind}.
* @author Brett Zamir
* @license MIT
* @todo See WebAppFind Readme for SVG-related todos
*/
const loadExtensionTranslation = async function (lang) {
let translationModule;
try {
// eslint-disable-next-line no-unsanitized/method
translationModule = await import(`./locale/${encodeURIComponent(lang)}.js`);
} catch (_error) {
// eslint-disable-next-line no-console
console.error(`Missing translation (${lang}) - using 'en'`);
translationModule = await import(`./locale/en.js`);
}
return translationModule.default;
};
export default {
name: 'webappfind',
async init () {
const svgEditor = this;
const strings = await loadExtensionTranslation(svgEditor.configObj.pref('lang'));
const saveMessage = 'save';
const readMessage = 'read';
const excludedMessages = [ readMessage, saveMessage ];
let pathID;
this.canvas.bind(
'message',
/**
*
* @param {external:Window} win external Window handler
* @param {*} param1 info
* @returns {void}
*/
(win, { data, origin }) => {
let type; let content;
try {
({ type, pathID, content } = data.webappfind); // May throw if data is not an object
if (origin !== location.origin || // We are only interested in a message sent as though within this URL by our browser add-on
// Avoid our post below (other msgs might be possible which may need to be excluded if code makes assumptions on the type of message)
excludedMessages.includes(type)
) {
return;
}
} catch (err) {
return;
}
switch (type) {
case 'view':
// Populate the contents
svgEditor.loadFromString(content);
break;
case 'save-end':
// eslint-disable-next-line no-alert
alert(`save complete for pathID ${pathID}!`);
break;
default:
throw new Error('Unexpected WebAppFind event type');
}
}
);
/*
window.postMessage({
webappfind: {
type: readMessage
}
}, window.location.origin === 'null'
// Avoid "null" string error for `file:` protocol (even though
// file protocol not currently supported by Firefox)
? '*'
: window.location.origin
);
*/
const buttons = [ {
id: 'webappfind_save', //
icon: 'webappfind.png',
type: 'app_menu',
position: 4, // Before 0-based index position 4 (after the regular "Save Image (S)")
events: {
click () {
if (!pathID) { // Not ready yet as haven't received first payload
return;
}
window.postMessage(
{
webappfind: {
type: saveMessage,
pathID,
content: svgEditor.svgCanvas.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
);
}
}
} ];
return {
name: strings.name,
svgicons: 'webappfind-icon.svg',
buttons: strings.buttons.map((button, i) => {
return Object.assign(buttons[i], button);
})
};
}
};

View File

@@ -1,8 +0,0 @@
export default {
name: 'WebAppFind',
buttons: [
{
title: 'Save Image back to Disk'
}
]
};

View File

@@ -1,8 +0,0 @@
export default {
name: 'WebAppFind',
buttons: [
{
title: '保存图片到磁盘'
}
]
};

View File

@@ -1,49 +0,0 @@
/**
* Should not be needed for same domain control (just call via child frame),
* but an API common for cross-domain and same domain use can be found
* in embedapi.js with a demo at embedapi.html.
*/
export default {
name: 'xdomain-messaging',
init () {
const svgEditor = this;
const { svgCanvas } = svgEditor;
try {
window.addEventListener('message', function (e) {
// We accept and post strings for the sake of IE9 support
if (!e.data || ![ 'string', 'object' ].includes(typeof e.data) || e.data.charAt() === '|') {
return;
}
const data = typeof e.data === 'object' ? e.data : JSON.parse(e.data);
if (!data || typeof data !== 'object' || data.namespace !== 'svgCanvas') {
return;
}
// The default is not to allow any origins, including even the same domain or
// if run on a `file:///` URL. See `svgedit-config-es.js` for an example of how
// to configure
const { allowedOrigins } = svgEditor.configObj.curConfig;
if (!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin)) {
console.warn(`Origin ${e.origin} not whitelisted for posting to ${window.origin}`);
return;
}
const cbid = data.id;
const { name, args } = data;
const message = {
namespace: 'svg-edit',
id: cbid
};
try {
// Now that we know the origin is trusted, we perform otherwise
// unsafe arbitrary canvas method execution
message.result = svgCanvas[name](...args); // lgtm [js/remote-property-injection]
} catch (err) {
message.error = err.message;
}
e.source.postMessage(JSON.stringify(message), '*');
});
} catch (err) {
console.error('Error with xdomain message listener: ' + err);
}
}
};