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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
export default {
|
||||
name: 'ClosePath',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Open path'
|
||||
},
|
||||
{
|
||||
title: 'Close path'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
export default {
|
||||
name: '闭合路径',
|
||||
buttons: [
|
||||
{
|
||||
title: '打开路径'
|
||||
},
|
||||
{
|
||||
title: '关闭路径'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
export default {
|
||||
name: '外部对象',
|
||||
buttons: [
|
||||
{
|
||||
title: '外部对象工具'
|
||||
},
|
||||
{
|
||||
title: '编辑外部对象内容'
|
||||
}
|
||||
],
|
||||
contextTools: [
|
||||
{
|
||||
title: '改变外部对象宽度',
|
||||
label: 'w'
|
||||
},
|
||||
{
|
||||
title: '改变外部对象高度',
|
||||
label: 'h'
|
||||
},
|
||||
{
|
||||
title: '改变外部对象文字大小',
|
||||
label: '文字大小'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
name: 'MathJax',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Add Mathematics'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -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: ''
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export default {
|
||||
saved: 'Saved! Return to Item View!',
|
||||
hiddenframe: 'Moinsave frame to store hidden values'
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export default {
|
||||
saved: '已保存! 返回视图!',
|
||||
hiddenframe: 'Moinsave frame to store hidden values'
|
||||
};
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
?>
|
||||
@@ -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);});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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, '&').replace(/"/g, '"').replace(/</g, '<'); // < 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);
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
?>
|
||||
@@ -1,4 +0,0 @@
|
||||
export default {
|
||||
uploading: 'Uploading...',
|
||||
hiddenframe: 'Opensave frame to store hidden values'
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
export default {
|
||||
uploading: '正在上传...',
|
||||
hiddenframe: 'Opensave frame to store hidden values'
|
||||
};
|
||||
@@ -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);
|
||||
?>
|
||||
@@ -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);
|
||||
})
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
name: 'WebAppFind',
|
||||
buttons: [
|
||||
{
|
||||
title: 'Save Image back to Disk'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
export default {
|
||||
name: 'WebAppFind',
|
||||
buttons: [
|
||||
{
|
||||
title: '保存图片到磁盘'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user