Save worktree changes
This commit is contained in:
+183
-33
@@ -3,6 +3,7 @@ import {
|
||||
BlockQuote,
|
||||
Bold,
|
||||
ClassicEditor,
|
||||
Base64UploadAdapter,
|
||||
ClipboardPipeline,
|
||||
Essentials,
|
||||
FontBackgroundColor,
|
||||
@@ -11,6 +12,20 @@ import {
|
||||
FontSize,
|
||||
Heading,
|
||||
HorizontalLine,
|
||||
Image,
|
||||
ImageBlockEditing,
|
||||
ImageCaption,
|
||||
ImageInlineEditing,
|
||||
ImageInsert,
|
||||
ImageInsertUI,
|
||||
ImageInsertViaUrl,
|
||||
ImageInsertViaUrlUI,
|
||||
ImageResize,
|
||||
ImageResizeEditing,
|
||||
ImageResizeHandles,
|
||||
ImageStyle,
|
||||
ImageTextAlternative,
|
||||
ImageUpload,
|
||||
Indent,
|
||||
IndentBlock,
|
||||
Italic,
|
||||
@@ -25,6 +40,8 @@ import {
|
||||
import { createTemplateSelectorLockController } from '/assets/js/slides/slide-form-template-lock.js';
|
||||
|
||||
(function () {
|
||||
var DEFAULT_FONT_SIZE = 32;
|
||||
|
||||
var dataElement = document.getElementById('slide-editor-data');
|
||||
if (!dataElement) {
|
||||
return;
|
||||
@@ -94,7 +111,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
function getCurrentFontSizeValue() {
|
||||
var value = fontSizeCommand.value;
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '';
|
||||
return String(DEFAULT_FONT_SIZE);
|
||||
}
|
||||
|
||||
var parsed = Math.round(Number(String(value).replace(/[^0-9.]/g, '')));
|
||||
@@ -118,6 +135,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
view.element.inputMode = 'numeric';
|
||||
view.element.autocomplete = 'off';
|
||||
view.element.spellcheck = false;
|
||||
view.element.style.width = '3.5em';
|
||||
syncValue();
|
||||
});
|
||||
|
||||
@@ -170,6 +188,10 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
var clipboardPipeline = editor.plugins.get(ClipboardPipeline);
|
||||
|
||||
this.listenTo(clipboardPipeline, 'inputTransformation', function (_event, data) {
|
||||
if (clipboardContainsImageContent(data.dataTransfer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var plainText = data.dataTransfer.getData('text/plain');
|
||||
data.content = editor.data.processor.toView(plainTextToHtml(plainText));
|
||||
}, { priority: 'high' });
|
||||
@@ -211,6 +233,22 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
return paragraphs.length ? paragraphs.join('') : '<p></p>';
|
||||
}
|
||||
|
||||
function clipboardContainsImageContent(dataTransfer) {
|
||||
if (!dataTransfer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var files = Array.from(dataTransfer.files || []);
|
||||
if (files.some(function (file) {
|
||||
return file && String(file.type || '').indexOf('image/') === 0;
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var html = dataTransfer.getData('text/html') || '';
|
||||
return /<img\b|<figure\b[^>]*\bclass=["'][^"']*\bimage\b/i.test(html);
|
||||
}
|
||||
|
||||
function getTemplateById(id) {
|
||||
return templates.find(function (item) { return Number(item.id) === Number(id); }) || null;
|
||||
}
|
||||
@@ -240,30 +278,108 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
}
|
||||
|
||||
function sanitizePreviewHtml(html) {
|
||||
var output = String(html || '');
|
||||
output = output.replace(/<script[\s\S]*?<\/script>/gi, '');
|
||||
output = output.replace(/<style[\s\S]*?<\/style>/gi, '');
|
||||
return output.replace(/<[^>]+>/g, function (tag) {
|
||||
var match = tag.match(/^<\s*(\/?)\s*([a-z0-9]+)([\s\S]*?)(\/?)>$/i);
|
||||
if (!match) {
|
||||
return '';
|
||||
var container = document.createElement('div');
|
||||
container.innerHTML = String(html || '');
|
||||
return sanitizePreviewNode(container);
|
||||
}
|
||||
|
||||
function sanitizePreviewNode(node) {
|
||||
var allowed = ['a', 'b', 'blockquote', 'br', 'code', 'div', 'em', 'figure', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'];
|
||||
var output = '';
|
||||
|
||||
Array.from(node.childNodes || []).forEach(function (child) {
|
||||
if (child.nodeType === Node.TEXT_NODE) {
|
||||
output += escapeHtml(child.textContent);
|
||||
return;
|
||||
}
|
||||
var closing = Boolean(match[1]);
|
||||
var name = String(match[2] || '').toLowerCase();
|
||||
var attrText = String(match[3] || '');
|
||||
var selfClosing = Boolean(match[4]) || name === 'br' || name === 'hr';
|
||||
var allowed = ['a', 'b', 'blockquote', 'br', 'code', 'div', 'em', 'figure', 'figcaption', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'];
|
||||
|
||||
if (child.nodeType !== Node.ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = String(child.tagName || '').toLowerCase();
|
||||
if (allowed.indexOf(name) === -1) {
|
||||
return '';
|
||||
return;
|
||||
}
|
||||
if (closing) {
|
||||
return '</' + name + '>';
|
||||
|
||||
if (name === 'br' || name === 'hr') {
|
||||
output += '<' + name + '>';
|
||||
return;
|
||||
}
|
||||
if (selfClosing) {
|
||||
return '<' + name + sanitizeTagAttributes(name, attrText) + '>';
|
||||
|
||||
var attrs = sanitizePreviewElementAttributes(child, name);
|
||||
if (name === 'img') {
|
||||
output += '<img' + attrs + '>';
|
||||
return;
|
||||
}
|
||||
return '<' + name + sanitizeTagAttributes(name, attrText) + '>';
|
||||
|
||||
output += '<' + name + attrs + '>' + sanitizePreviewNode(child) + '</' + name + '>';
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function sanitizePreviewElementAttributes(element, tagName) {
|
||||
var allowedAttributes = {
|
||||
a: ['href', 'title', 'target', 'rel', 'class', 'style'],
|
||||
blockquote: ['class', 'style'],
|
||||
div: ['class', 'style'],
|
||||
figure: ['class', 'style'],
|
||||
figcaption: ['class', 'style'],
|
||||
h1: ['class', 'style'],
|
||||
h2: ['class', 'style'],
|
||||
h3: ['class', 'style'],
|
||||
h4: ['class', 'style'],
|
||||
h5: ['class', 'style'],
|
||||
h6: ['class', 'style'],
|
||||
img: ['alt', 'class', 'decoding', 'height', 'loading', 'src', 'style', 'title', 'width'],
|
||||
li: ['class', 'style'],
|
||||
ol: ['class', 'style', 'start'],
|
||||
p: ['class', 'style'],
|
||||
pre: ['class', 'style'],
|
||||
span: ['class', 'style'],
|
||||
table: ['class', 'style'],
|
||||
td: ['class', 'style', 'colspan', 'rowspan'],
|
||||
th: ['class', 'style', 'colspan', 'rowspan', 'scope'],
|
||||
tr: ['class', 'style'],
|
||||
ul: ['class', 'style']
|
||||
};
|
||||
var allowed = allowedAttributes[tagName] || [];
|
||||
var attrs = [];
|
||||
|
||||
Array.from(element.attributes || []).forEach(function (attribute) {
|
||||
var lowerKey = String(attribute.name || '').toLowerCase();
|
||||
if (allowed.indexOf(lowerKey) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var value = String(attribute.value || '');
|
||||
if (tagName === 'img' && lowerKey === 'src') {
|
||||
value = sanitizeImageSrc(value);
|
||||
}
|
||||
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(value)) {
|
||||
return;
|
||||
}
|
||||
if (tagName === 'img' && lowerKey === 'src' && !value) {
|
||||
return;
|
||||
}
|
||||
if (lowerKey === 'style' && /(?:expression\s*\(|javascript:|url\s*\()/i.test(value)) {
|
||||
return;
|
||||
}
|
||||
if (lowerKey === 'target') {
|
||||
var targetValue = value.trim();
|
||||
if (targetValue === '_blank') {
|
||||
attrs.push(' target="_blank"');
|
||||
if (attrs.indexOf(' rel="noreferrer noopener"') === -1) {
|
||||
attrs.push(' rel="noreferrer noopener"');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
attrs.push(' ' + lowerKey + '="' + escapeHtml(value) + '"');
|
||||
});
|
||||
|
||||
return attrs.join('');
|
||||
}
|
||||
|
||||
function sanitizeTagAttributes(tagName, attrText) {
|
||||
@@ -279,6 +395,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
h4: ['class', 'style'],
|
||||
h5: ['class', 'style'],
|
||||
h6: ['class', 'style'],
|
||||
img: ['alt', 'class', 'decoding', 'height', 'loading', 'src', 'style', 'title', 'width'],
|
||||
li: ['class', 'style'],
|
||||
ol: ['class', 'style', 'start'],
|
||||
p: ['class', 'style'],
|
||||
@@ -302,9 +419,15 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
return '';
|
||||
}
|
||||
var value = doubleQuoted !== undefined ? doubleQuoted : singleQuoted !== undefined ? singleQuoted : bareValue !== undefined ? bareValue : '';
|
||||
if (tagName === 'img' && lowerKey === 'src') {
|
||||
value = sanitizeImageSrc(value);
|
||||
}
|
||||
if (lowerKey === 'href' && /^(?:\s*javascript:|\s*data:)/i.test(String(value || ''))) {
|
||||
return '';
|
||||
}
|
||||
if (tagName === 'img' && lowerKey === 'src' && !value) {
|
||||
return '';
|
||||
}
|
||||
if (lowerKey === 'style' && /(?:expression\s*\(|javascript:|url\s*\()/i.test(String(value || ''))) {
|
||||
return '';
|
||||
}
|
||||
@@ -325,6 +448,23 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
return attrs.join('');
|
||||
}
|
||||
|
||||
function sanitizeImageSrc(value) {
|
||||
var raw = String(value || '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (/^(?:https?:|\/|\.\.?\/|\/\/)/i.test(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
if (/^data:image\/(?:png|jpe?g|gif|webp|bmp);base64,/i.test(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function renderPreviewListItem(item, tag) {
|
||||
if (item && typeof item === 'object') {
|
||||
var content = item.content !== undefined ? item.content : (item.text !== undefined ? item.text : item.value !== undefined ? item.value : '');
|
||||
@@ -442,7 +582,7 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
var current = existingContent[region.region_key] || {};
|
||||
return {
|
||||
font_family: String(current.font_family || region.font_family || 'Arial').trim() || 'Arial',
|
||||
font_size: Math.max(8, Number(current.font_size || 24)),
|
||||
font_size: Math.max(8, Number(current.font_size || DEFAULT_FONT_SIZE)),
|
||||
font_color: String(current.font_color || region.font_color || '#000000').trim() || '#000000'
|
||||
};
|
||||
}
|
||||
@@ -869,8 +1009,6 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
'undo',
|
||||
'redo',
|
||||
'|',
|
||||
'heading',
|
||||
'|',
|
||||
'fontSizeInput',
|
||||
'fontFamily',
|
||||
'fontColor',
|
||||
@@ -889,12 +1027,20 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
'alignment',
|
||||
'|',
|
||||
'outdent',
|
||||
'indent'
|
||||
'indent',
|
||||
'|',
|
||||
'imageInsert',
|
||||
'imageInsertViaUrl',
|
||||
'imageStyle:block',
|
||||
'imageStyle:inline',
|
||||
'imageTextAlternative',
|
||||
'imageResize'
|
||||
],
|
||||
shouldNotGroupWhenFull: false
|
||||
},
|
||||
plugins: [
|
||||
Alignment,
|
||||
Base64UploadAdapter,
|
||||
BlockQuote,
|
||||
Bold,
|
||||
Essentials,
|
||||
@@ -904,11 +1050,24 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
FontFamily,
|
||||
FontSize,
|
||||
FontSizeInputUI,
|
||||
Heading,
|
||||
HorizontalLine,
|
||||
Indent,
|
||||
IndentBlock,
|
||||
Italic,
|
||||
Image,
|
||||
ImageBlockEditing,
|
||||
ImageCaption,
|
||||
ImageInlineEditing,
|
||||
ImageInsert,
|
||||
ImageInsertUI,
|
||||
ImageInsertViaUrl,
|
||||
ImageInsertViaUrlUI,
|
||||
ImageResize,
|
||||
ImageResizeEditing,
|
||||
ImageResizeHandles,
|
||||
ImageStyle,
|
||||
ImageTextAlternative,
|
||||
ImageUpload,
|
||||
Paragraph,
|
||||
Strikethrough,
|
||||
Subscript,
|
||||
@@ -922,15 +1081,6 @@ import { createTemplateSelectorLockController } from '/assets/js/slides/slide-fo
|
||||
options: [20, 24, 28, 32, 36, 40, 44],
|
||||
supportAllValues: true
|
||||
},
|
||||
heading: {
|
||||
options: [
|
||||
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
|
||||
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
|
||||
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
|
||||
{ model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' },
|
||||
{ model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }
|
||||
]
|
||||
},
|
||||
initialData: normalizeEditorData((source && source.value) || (hidden ? hidden.value : ''))
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user