mirror of
https://github.com/fzumpe/foundry-usernotes.git
synced 2026-06-06 21:00:03 +02:00
Implement Jodit Wysiwyg editor and sanitize functions
This commit is contained in:
parent
6a37be1ce8
commit
b6fd4d5f4c
211
scripts/user-notes-sanitize.js
Normal file
211
scripts/user-notes-sanitize.js
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
const USER_NOTES_ALLOWED_TAGS = new Set([
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"BLOCKQUOTE",
|
||||||
|
"BR",
|
||||||
|
"CAPTION",
|
||||||
|
"CODE",
|
||||||
|
"COL",
|
||||||
|
"COLGROUP",
|
||||||
|
"DIV",
|
||||||
|
"EM",
|
||||||
|
"H1",
|
||||||
|
"H2",
|
||||||
|
"H3",
|
||||||
|
"H4",
|
||||||
|
"H5",
|
||||||
|
"H6",
|
||||||
|
"HR",
|
||||||
|
"I",
|
||||||
|
"LI",
|
||||||
|
"OL",
|
||||||
|
"P",
|
||||||
|
"PRE",
|
||||||
|
"S",
|
||||||
|
"SPAN",
|
||||||
|
"STRONG",
|
||||||
|
"SUB",
|
||||||
|
"SUP",
|
||||||
|
"TABLE",
|
||||||
|
"TBODY",
|
||||||
|
"TD",
|
||||||
|
"TFOOT",
|
||||||
|
"TH",
|
||||||
|
"THEAD",
|
||||||
|
"TR",
|
||||||
|
"U",
|
||||||
|
"UL"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const USER_NOTES_ALLOWED_ATTRIBUTES = new Set([
|
||||||
|
"class",
|
||||||
|
"colspan",
|
||||||
|
"href",
|
||||||
|
"rel",
|
||||||
|
"rowspan",
|
||||||
|
"style",
|
||||||
|
"target",
|
||||||
|
"title"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const USER_NOTES_ALLOWED_CSS_PROPERTIES = new Set([
|
||||||
|
"background-color",
|
||||||
|
"border",
|
||||||
|
"border-bottom",
|
||||||
|
"border-color",
|
||||||
|
"border-left",
|
||||||
|
"border-radius",
|
||||||
|
"border-right",
|
||||||
|
"border-style",
|
||||||
|
"border-top",
|
||||||
|
"border-width",
|
||||||
|
"color",
|
||||||
|
"font-family",
|
||||||
|
"font-size",
|
||||||
|
"font-style",
|
||||||
|
"font-weight",
|
||||||
|
"margin-left",
|
||||||
|
"padding",
|
||||||
|
"text-align",
|
||||||
|
"text-decoration"
|
||||||
|
]);
|
||||||
|
|
||||||
|
function userNotesSanitizeStyle(styleValue) {
|
||||||
|
const safeRules = [];
|
||||||
|
|
||||||
|
for (const rule of String(styleValue ?? "").split(";")) {
|
||||||
|
const [rawProperty, ...rawValueParts] = rule.split(":");
|
||||||
|
|
||||||
|
if (!rawProperty || rawValueParts.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const property = rawProperty.trim().toLowerCase();
|
||||||
|
const value = rawValueParts.join(":").trim();
|
||||||
|
|
||||||
|
if (!USER_NOTES_ALLOWED_CSS_PROPERTIES.has(property)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowered = value.toLowerCase();
|
||||||
|
|
||||||
|
if (
|
||||||
|
lowered.includes("url(") ||
|
||||||
|
lowered.includes("expression(") ||
|
||||||
|
lowered.includes("javascript:") ||
|
||||||
|
lowered.includes("data:") ||
|
||||||
|
lowered.includes("@import") ||
|
||||||
|
lowered.includes("behavior:")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
safeRules.push(`${property}: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return safeRules.join("; ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function userNotesIsSafeHref(value) {
|
||||||
|
const href = String(value ?? "").trim();
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (href.startsWith("#")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(href, window.location.origin);
|
||||||
|
return ["http:", "https:", "mailto:"].includes(url.protocol);
|
||||||
|
} catch (_err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function userNotesSanitizeElement(element) {
|
||||||
|
if (!USER_NOTES_ALLOWED_TAGS.has(element.tagName)) {
|
||||||
|
const text = document.createTextNode(element.textContent ?? "");
|
||||||
|
element.replaceWith(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attribute of [...element.attributes]) {
|
||||||
|
const name = attribute.name.toLowerCase();
|
||||||
|
const value = attribute.value;
|
||||||
|
|
||||||
|
if (name.startsWith("on")) {
|
||||||
|
element.removeAttribute(attribute.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!USER_NOTES_ALLOWED_ATTRIBUTES.has(name)) {
|
||||||
|
element.removeAttribute(attribute.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === "href") {
|
||||||
|
if (!userNotesIsSafeHref(value)) {
|
||||||
|
element.removeAttribute(attribute.name);
|
||||||
|
} else {
|
||||||
|
element.setAttribute("rel", "noopener noreferrer");
|
||||||
|
element.setAttribute("target", "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === "target" && value !== "_blank") {
|
||||||
|
element.removeAttribute(attribute.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === "style") {
|
||||||
|
const sanitizedStyle = userNotesSanitizeStyle(value);
|
||||||
|
|
||||||
|
if (sanitizedStyle) {
|
||||||
|
element.setAttribute("style", sanitizedStyle);
|
||||||
|
} else {
|
||||||
|
element.removeAttribute(attribute.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userNotesSanitizeHtml(html) {
|
||||||
|
const template = document.createElement("template");
|
||||||
|
template.innerHTML = String(html ?? "");
|
||||||
|
|
||||||
|
for (const element of [...template.content.querySelectorAll("*")]) {
|
||||||
|
userNotesSanitizeElement(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.innerHTML.replace(/<br>/gi, "<br />");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userNotesEscapePlainTextAsHtml(text) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.textContent = String(text ?? "");
|
||||||
|
|
||||||
|
return div.innerHTML.replace(/\n/g, "<br />");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userNotesLooksLikeHtml(value) {
|
||||||
|
return /<\/?[a-z][\s\S]*>/i.test(String(value ?? ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userNotesNormalizeStoredNotesForEditor(value) {
|
||||||
|
const content = String(value ?? "");
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userNotesLooksLikeHtml(content)) {
|
||||||
|
return userNotesSanitizeHtml(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userNotesEscapePlainTextAsHtml(content);
|
||||||
|
}
|
||||||
@ -18,6 +18,11 @@ import {
|
|||||||
userNotesApplyWindowSettings
|
userNotesApplyWindowSettings
|
||||||
} from "./user-notes-settings.js";
|
} from "./user-notes-settings.js";
|
||||||
|
|
||||||
|
import {
|
||||||
|
userNotesNormalizeStoredNotesForEditor,
|
||||||
|
userNotesSanitizeHtml
|
||||||
|
} from "./user-notes-sanitize.js";
|
||||||
|
|
||||||
let userNotesSaveTimer = null;
|
let userNotesSaveTimer = null;
|
||||||
|
|
||||||
export function userNotesSetStatus(text) {
|
export function userNotesSetStatus(text) {
|
||||||
@ -35,7 +40,7 @@ export function userNotesDebouncedSave(value) {
|
|||||||
userNotesSetStatus("Ungespeichert …");
|
userNotesSetStatus("Ungespeichert …");
|
||||||
|
|
||||||
userNotesSaveTimer = window.setTimeout(() => {
|
userNotesSaveTimer = window.setTimeout(() => {
|
||||||
userNotesSaveNotes(value);
|
userNotesSaveNotes(userNotesSanitizeHtml(value));
|
||||||
userNotesSetStatus("Gespeichert");
|
userNotesSetStatus("Gespeichert");
|
||||||
}, 250);
|
}, 250);
|
||||||
}
|
}
|
||||||
@ -241,6 +246,144 @@ export function userNotesMakeDraggable(win) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function userNotesGetEditorValue(win) {
|
||||||
|
const editor = win.__userNotesEditor;
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
return userNotesSanitizeHtml(editor.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const textarea = win.querySelector(".user-notes-editor");
|
||||||
|
|
||||||
|
if (textarea instanceof HTMLTextAreaElement) {
|
||||||
|
return userNotesSanitizeHtml(textarea.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function userNotesDestroyEditor(win) {
|
||||||
|
const editor = win.__userNotesEditor;
|
||||||
|
|
||||||
|
if (editor && typeof editor.destruct === "function") {
|
||||||
|
editor.destruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
win.__userNotesEditor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function userNotesInsertSymbol(editor, symbol) {
|
||||||
|
editor.s.insertHTML(`${symbol} `);
|
||||||
|
}
|
||||||
|
|
||||||
|
function userNotesCreateEditor(win, editorElement) {
|
||||||
|
const initialContent = userNotesNormalizeStoredNotesForEditor(userNotesLoadNotes());
|
||||||
|
|
||||||
|
if (!globalThis.Jodit) {
|
||||||
|
console.warn("User Notes | Jodit is not available. Falling back to plain textarea.");
|
||||||
|
|
||||||
|
editorElement.value = initialContent
|
||||||
|
.replace(/<br\s*\/?>/gi, "\n")
|
||||||
|
.replace(/<\/p>/gi, "\n")
|
||||||
|
.replace(/<[^>]+>/g, "");
|
||||||
|
|
||||||
|
editorElement.addEventListener("input", event => {
|
||||||
|
userNotesDebouncedSave(event.currentTarget.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = globalThis.Jodit.make(editorElement, {
|
||||||
|
height: "100%",
|
||||||
|
minHeight: 150,
|
||||||
|
toolbarAdaptive: false,
|
||||||
|
askBeforePasteHTML: false,
|
||||||
|
askBeforePasteFromWord: false,
|
||||||
|
defaultActionOnPaste: "insert_clear_html",
|
||||||
|
disablePlugins: [
|
||||||
|
"about",
|
||||||
|
"add-new-line",
|
||||||
|
"ai-assistant",
|
||||||
|
"file",
|
||||||
|
"image",
|
||||||
|
"media",
|
||||||
|
"paste-storage",
|
||||||
|
"powered-by-jodit",
|
||||||
|
"preview",
|
||||||
|
"print",
|
||||||
|
"source",
|
||||||
|
"speech-recognize",
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
"bold",
|
||||||
|
"italic",
|
||||||
|
"underline",
|
||||||
|
"strikethrough",
|
||||||
|
"|",
|
||||||
|
"ul",
|
||||||
|
"ol",
|
||||||
|
"|",
|
||||||
|
"font",
|
||||||
|
"fontsize",
|
||||||
|
"brush",
|
||||||
|
"|",
|
||||||
|
"table",
|
||||||
|
"|",
|
||||||
|
"undo",
|
||||||
|
"redo",
|
||||||
|
"|",
|
||||||
|
{
|
||||||
|
name: "checkbox-empty",
|
||||||
|
tooltip: "Kästchen einfügen",
|
||||||
|
icon: "☐",
|
||||||
|
exec: editorInstance => userNotesInsertSymbol(editorInstance, "☐")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "checkbox-checked",
|
||||||
|
tooltip: "Angehaktes Kästchen einfügen",
|
||||||
|
icon: "☑",
|
||||||
|
exec: editorInstance => userNotesInsertSymbol(editorInstance, "☑")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "checkmark",
|
||||||
|
tooltip: "Haken einfügen",
|
||||||
|
icon: "✓",
|
||||||
|
exec: editorInstance => userNotesInsertSymbol(editorInstance, "✓")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "crossmark",
|
||||||
|
tooltip: "Kreuz einfügen",
|
||||||
|
icon: "✗",
|
||||||
|
exec: editorInstance => userNotesInsertSymbol(editorInstance, "✗")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bordered-text",
|
||||||
|
tooltip: "Rahmen um markierten Text",
|
||||||
|
icon: "▣",
|
||||||
|
exec: editorInstance => {
|
||||||
|
const selected = editorInstance.s.html || " ";
|
||||||
|
|
||||||
|
editorInstance.s.insertHTML(
|
||||||
|
`<span style="border: 1px solid currentColor; padding: 2px 4px;">${selected}</span>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.value = initialContent;
|
||||||
|
|
||||||
|
editor.events.on("change", () => {
|
||||||
|
userNotesDebouncedSave(editor.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
win.__userNotesEditor = editor;
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
export function userNotesOpenNotes() {
|
export function userNotesOpenNotes() {
|
||||||
let win = document.getElementById(USER_NOTES_WINDOW_ID);
|
let win = document.getElementById(USER_NOTES_WINDOW_ID);
|
||||||
|
|
||||||
@ -249,7 +392,15 @@ export function userNotesOpenNotes() {
|
|||||||
userNotesRestorePosition(win);
|
userNotesRestorePosition(win);
|
||||||
userNotesApplyWindowSettings(win);
|
userNotesApplyWindowSettings(win);
|
||||||
userNotesBringToFront(win);
|
userNotesBringToFront(win);
|
||||||
win.querySelector("textarea")?.focus();
|
|
||||||
|
const editor = win.__userNotesEditor;
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
editor.focus();
|
||||||
|
} else {
|
||||||
|
win.querySelector(".user-notes-editor")?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +429,7 @@ export function userNotesOpenNotes() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="user-notes-content">
|
<main class="user-notes-content">
|
||||||
<textarea class="user-notes-textarea" spellcheck="true" placeholder="Notizen für diese Welt und diesen Benutzer …"></textarea>
|
<textarea class="user-notes-editor" spellcheck="true"></textarea>
|
||||||
</main>
|
</main>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -287,27 +438,23 @@ export function userNotesOpenNotes() {
|
|||||||
userNotesApplyWindowSettings(win);
|
userNotesApplyWindowSettings(win);
|
||||||
userNotesRestorePosition(win);
|
userNotesRestorePosition(win);
|
||||||
|
|
||||||
const textarea = win.querySelector(".user-notes-textarea");
|
const editorElement = win.querySelector(".user-notes-editor");
|
||||||
|
|
||||||
if (!(textarea instanceof HTMLTextAreaElement)) {
|
if (!(editorElement instanceof HTMLTextAreaElement)) {
|
||||||
console.error(`${USER_NOTES_MODULE_ID} | Notes textarea could not be created.`);
|
console.error(`${USER_NOTES_MODULE_ID} | Notes editor could not be created.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea.value = userNotesLoadNotes();
|
userNotesCreateEditor(win, editorElement);
|
||||||
|
|
||||||
textarea.addEventListener("input", event => {
|
|
||||||
userNotesDebouncedSave(event.currentTarget.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
win.querySelector(".user-notes-save")?.addEventListener("click", () => {
|
win.querySelector(".user-notes-save")?.addEventListener("click", () => {
|
||||||
userNotesSaveNotes(textarea.value);
|
userNotesSaveNotes(userNotesGetEditorValue(win));
|
||||||
userNotesSetStatus("Gespeichert");
|
userNotesSetStatus("Gespeichert");
|
||||||
userNotesSavePosition(win);
|
userNotesSavePosition(win);
|
||||||
});
|
});
|
||||||
|
|
||||||
win.querySelector(".user-notes-close")?.addEventListener("click", () => {
|
win.querySelector(".user-notes-close")?.addEventListener("click", () => {
|
||||||
userNotesSaveNotes(textarea.value);
|
userNotesSaveNotes(userNotesGetEditorValue(win));
|
||||||
userNotesSavePosition(win);
|
userNotesSavePosition(win);
|
||||||
win.hidden = true;
|
win.hidden = true;
|
||||||
});
|
});
|
||||||
@ -334,7 +481,12 @@ export function userNotesOpenNotes() {
|
|||||||
|
|
||||||
userNotesMakeDraggable(win);
|
userNotesMakeDraggable(win);
|
||||||
userNotesBringToFront(win);
|
userNotesBringToFront(win);
|
||||||
textarea.focus();
|
|
||||||
|
if (win.__userNotesEditor) {
|
||||||
|
win.__userNotesEditor.focus();
|
||||||
|
} else {
|
||||||
|
editorElement.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function userNotesRefreshOpenWindow() {
|
export function userNotesRefreshOpenWindow() {
|
||||||
@ -344,15 +496,10 @@ export function userNotesRefreshOpenWindow() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const textarea = oldWin.querySelector(".user-notes-textarea");
|
userNotesSaveNotes(userNotesGetEditorValue(oldWin));
|
||||||
|
|
||||||
if (textarea instanceof HTMLTextAreaElement) {
|
|
||||||
userNotesSaveNotes(textarea.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
userNotesSavePosition(oldWin);
|
userNotesSavePosition(oldWin);
|
||||||
|
userNotesDestroyEditor(oldWin);
|
||||||
oldWin.remove();
|
oldWin.remove();
|
||||||
|
|
||||||
userNotesOpenNotes();
|
userNotesOpenNotes();
|
||||||
}
|
}
|
||||||
@ -8,11 +8,13 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
userNotesOpenNotes,
|
userNotesOpenNotes,
|
||||||
|
userNotesRefreshOpenWindow,
|
||||||
userNotesResetPositionAndSize
|
userNotesResetPositionAndSize
|
||||||
} from "./user-notes-window.js";
|
} from "./user-notes-window.js";
|
||||||
|
|
||||||
globalThis.UserNotes = {
|
globalThis.UserNotes = {
|
||||||
open: userNotesOpenNotes,
|
open: userNotesOpenNotes,
|
||||||
|
refresh: userNotesRefreshOpenWindow,
|
||||||
resetPosition: userNotesResetPositionAndSize
|
resetPosition: userNotesResetPositionAndSize
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,4 +44,4 @@ Hooks.on("getSceneControlButtons", controls => {
|
|||||||
"User Notes: Fehler beim Registrieren des Token-Controls."
|
"User Notes: Fehler beim Registrieren des Token-Controls."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -102,7 +102,35 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-notes-textarea {
|
.user-notes-content .jodit-container {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-notes-content .jodit-workplace {
|
||||||
|
min-height: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-notes-content .jodit-wysiwyg {
|
||||||
|
min-height: 0 !important;
|
||||||
|
background: var(--user-notes-textarea-background, rgba(255, 255, 255, 0.92));
|
||||||
|
color: var(--user-notes-textarea-color, #111111);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-notes-content .jodit-toolbar__box {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-notes-content .jodit-status-bar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-notes-editor {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -127,7 +155,18 @@
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-notes-textarea:focus {
|
.user-notes-editor:focus {
|
||||||
outline: 1px solid var(--color-border-highlight, #ff6400);
|
outline: 1px solid var(--color-border-highlight, #ff6400);
|
||||||
outline-offset: 0;
|
outline-offset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-notes-window table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-notes-window th,
|
||||||
|
.user-notes-window td {
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
padding: 0.25rem 0.4rem;
|
||||||
|
}
|
||||||
5568
vendor/jodit/CHANGELOG.md
vendored
Normal file
5568
vendor/jodit/CHANGELOG.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
vendor/jodit/LICENSE.txt
vendored
Normal file
19
vendor/jodit/LICENSE.txt
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2013-2026 https://xdsoft.net
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
3
vendor/jodit/VERSION.txt
vendored
Normal file
3
vendor/jodit/VERSION.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Jodit Editor 4.12.2
|
||||||
|
Source: https://github.com/xdan/jodit/tree/4.12.2
|
||||||
|
License: MIT
|
||||||
1
vendor/jodit/jodit.min.css
vendored
Normal file
1
vendor/jodit/jodit.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
272
vendor/jodit/jodit.min.js
vendored
Normal file
272
vendor/jodit/jodit.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user