@wrrdev/fenix-ui 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/components/editor/commands.d.ts +27 -0
- package/dist/components/editor/commands.d.ts.map +1 -0
- package/dist/components/editor/commands.js +235 -0
- package/dist/components/editor/commands.js.map +1 -0
- package/dist/components/editor/editor.d.ts +83 -0
- package/dist/components/editor/editor.d.ts.map +1 -0
- package/dist/components/editor/editor.js +1951 -0
- package/dist/components/editor/editor.js.map +1 -0
- package/dist/components/editor/index.d.ts +12 -0
- package/dist/components/editor/index.d.ts.map +1 -0
- package/dist/components/editor/index.js +7 -0
- package/dist/components/editor/index.js.map +1 -0
- package/dist/components/editor/normalize-html.d.ts +33 -0
- package/dist/components/editor/normalize-html.d.ts.map +1 -0
- package/dist/components/editor/normalize-html.js +316 -0
- package/dist/components/editor/normalize-html.js.map +1 -0
- package/dist/components/editor/sanitize-html.d.ts +37 -0
- package/dist/components/editor/sanitize-html.d.ts.map +1 -0
- package/dist/components/editor/sanitize-html.js +247 -0
- package/dist/components/editor/sanitize-html.js.map +1 -0
- package/dist/components/editor/schema.d.ts +36 -0
- package/dist/components/editor/schema.d.ts.map +1 -0
- package/dist/components/editor/schema.js +232 -0
- package/dist/components/editor/schema.js.map +1 -0
- package/dist/components/editor/selection.d.ts +22 -0
- package/dist/components/editor/selection.d.ts.map +1 -0
- package/dist/components/editor/selection.js +97 -0
- package/dist/components/editor/selection.js.map +1 -0
- package/dist/components/editor/styles.d.ts +10 -0
- package/dist/components/editor/styles.d.ts.map +1 -0
- package/dist/components/editor/styles.js +446 -0
- package/dist/components/editor/styles.js.map +1 -0
- package/dist/components/editor/table-utils.d.ts +33 -0
- package/dist/components/editor/table-utils.d.ts.map +1 -0
- package/dist/components/editor/table-utils.js +150 -0
- package/dist/components/editor/table-utils.js.map +1 -0
- package/dist/components/input/input.d.ts +5 -1
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +15 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts +3 -0
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +19 -9
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/table/table.d.ts.map +1 -1
- package/dist/components/table/table.js +20 -3
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/toast/toast.d.ts +10 -10
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +46 -13
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/core/jsx.d.ts +25 -1
- package/dist/core/jsx.d.ts.map +1 -1
- package/dist/core/jsx.js.map +1 -1
- package/dist/core/page-range.d.ts +31 -0
- package/dist/core/page-range.d.ts.map +1 -0
- package/dist/core/page-range.js +41 -0
- package/dist/core/page-range.js.map +1 -0
- package/dist/core/vue.d.ts +3 -1
- package/dist/core/vue.d.ts.map +1 -1
- package/dist/core/vue.js.map +1 -1
- package/dist/docs/componentes/editor.doc.d.ts +6 -0
- package/dist/docs/componentes/editor.doc.d.ts.map +1 -0
- package/dist/docs/componentes/index.d.ts.map +1 -1
- package/dist/docs/componentes/input.doc.d.ts.map +1 -1
- package/dist/docs/componentes/table.doc.d.ts.map +1 -1
- package/dist/fenix-ui.umd.min.js +774 -256
- package/dist/fenix-ui2.esm.js.map +1 -1
- package/dist/fenix-ui4.esm.js +3877 -181
- package/dist/fenix-ui4.esm.js.map +1 -1
- package/dist/fenix-ui5.esm.js +1 -0
- package/dist/fenix-ui5.esm.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/auto-import.d.ts.map +1 -1
- package/dist/plugins/auto-import.js +1 -0
- package/dist/plugins/auto-import.js.map +1 -1
- package/package.json +5 -1
package/dist/fenix-ui4.esm.js
CHANGED
|
@@ -2,9 +2,19 @@ var __typeError = (msg) => {
|
|
|
2
2
|
throw TypeError(msg);
|
|
3
3
|
};
|
|
4
4
|
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
5
6
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
6
8
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
7
|
-
var
|
|
9
|
+
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
10
|
+
set _(value) {
|
|
11
|
+
__privateSet(obj, member, value, setter);
|
|
12
|
+
},
|
|
13
|
+
get _() {
|
|
14
|
+
return __privateGet(obj, member, getter);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
var _built, _eventsBound, _initialized, _defaultValue, _value, _dirty, _sourceMode, _lastRange, _rafId, _emitTimer, _lastLimitError, _snapshotTimer, _history, _historyIndex, _imagePicker, _mediaFigure, _linkAnchor, _resizeDispose, _dialogReturnFocus, _dialogResolve, _disposers, _selectionRaf, _bodyOverflow, _FxEditor_instances, on_fn, bindEvents_fn, syncChrome_fn, applyCssVars_fn, updateEmpty_fn, updateCount_fn, fits_fn, limitError_fn, validate_fn, cleanHtml_fn, serialize_fn, applyValue_fn, scheduleEmit_fn, emitNow_fn, emitChange_fn, pushHistory_fn, scheduleSnapshot_fn, historyUndo_fn, historyRedo_fn, bindGlobalSelection_fn, restoreSelection_fn, syncBodyOverflow_fn, updateSourceUi_fn, releaseDialog_fn, renderToolbar_fn, updateToolbar_fn, handleToolbarKeydown_fn, moveToolbarTabStop_fn, trapDialogFocus_fn, execCommand_fn, afterCommand_fn, action_fn, handleKeydown_fn, tabInTable_fn, handleBeforeInput_fn, handlePaste_fn, handleDrop_fn, insertPlainText_fn, toggleSource_fn, applySource_fn, cancelSource_fn, openDialog_fn, fieldHtml_fn, submitDialog_fn, openLinkDialog_fn, openImageDialog_fn, toEmbedUrl_fn, openVideoDialog_fn, decorateMediaFigures_fn, syncQuickActions_fn, selectAnchor_fn, handleLinkOverlayClick_fn, openLink_fn, removeLink_fn, showLinkOverlay_fn, hideLinkOverlay_fn, positionLinkOverlay_fn, handleEditorClick_fn, showMediaOverlay_fn, hideMediaOverlay_fn, deleteMediaFigure_fn, positionMediaOverlay_fn, startResize_fn, endResize_fn, handleDoubleClick_fn, insertImage_fn, insertVideo_fn, insertEmbed_fn, insertTable_fn, insertColumns_fn, showTablePanel_fn, showColumnsPanel_fn, hidePanel_fn, currentTable_fn, tableAction_fn, handleImageFile_fn, fileToDataUrl_fn, error_fn, _FxToast_instances, wirePause_fn, startTimer_fn, pauseTimer_fn, resumeTimer_fn, syncProgress_fn, render_fn, syncMore_fn;
|
|
8
18
|
import { a as FENIX_ICON_BASE_CSS, f as fenixIconHtml, g as esc, l as loadFenixIconsFont, i as isFenixIconName } from "./assets/font-DPeWTz-n.js";
|
|
9
19
|
import { h, j } from "./assets/font-DPeWTz-n.js";
|
|
10
20
|
const lightTokens = {
|
|
@@ -1177,6 +1187,11 @@ function defineFxSelect() {
|
|
|
1177
1187
|
return defineElement("fx-select", FxSelect);
|
|
1178
1188
|
}
|
|
1179
1189
|
defineFxSelect();
|
|
1190
|
+
const INPUT_TYPES = /* @__PURE__ */ new Set(["text", "number", "password"]);
|
|
1191
|
+
function normalizeInputType(value) {
|
|
1192
|
+
const type = (value || "text").toLowerCase();
|
|
1193
|
+
return INPUT_TYPES.has(type) ? type : "text";
|
|
1194
|
+
}
|
|
1180
1195
|
const _FxInput = class _FxInput extends FxFormElement {
|
|
1181
1196
|
// `value` fica FORA da observação: refleti-lo a cada tecla não pode
|
|
1182
1197
|
// re-renderizar o template, senão o campo perde o foco ao digitar.
|
|
@@ -1200,6 +1215,13 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1200
1215
|
"valid"
|
|
1201
1216
|
];
|
|
1202
1217
|
}
|
|
1218
|
+
/** Tipo do input interno. Padrão: `'text'`. */
|
|
1219
|
+
get type() {
|
|
1220
|
+
return normalizeInputType(this.getAttr("type", "text"));
|
|
1221
|
+
}
|
|
1222
|
+
set type(value) {
|
|
1223
|
+
this.setAttribute("type", normalizeInputType(value));
|
|
1224
|
+
}
|
|
1203
1225
|
/** Tamanho do campo. Padrão: `'md'`. */
|
|
1204
1226
|
get size() {
|
|
1205
1227
|
const s = this.getAttr("size", "md");
|
|
@@ -1223,7 +1245,7 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1223
1245
|
this.toggleAttr("disabled", Boolean(value));
|
|
1224
1246
|
}
|
|
1225
1247
|
render() {
|
|
1226
|
-
const type = this.
|
|
1248
|
+
const type = this.type;
|
|
1227
1249
|
const placeholder = this.getAttr("placeholder");
|
|
1228
1250
|
const readonly = this.hasAttr("readonly");
|
|
1229
1251
|
const disabled = this.hasAttr("disabled");
|
|
@@ -1292,6 +1314,8 @@ const _FxInput = class _FxInput extends FxFormElement {
|
|
|
1292
1314
|
syncInternalControl() {
|
|
1293
1315
|
const field = this.root.querySelector(".field");
|
|
1294
1316
|
const value = this.getAttr("value");
|
|
1317
|
+
const type = this.type;
|
|
1318
|
+
if (field && field.type !== type) field.type = type;
|
|
1295
1319
|
if (field && field.value !== value) field.value = value;
|
|
1296
1320
|
}
|
|
1297
1321
|
};
|
|
@@ -3313,6 +3337,43 @@ function defineFxRadio() {
|
|
|
3313
3337
|
return defineElement("fx-radio", FxRadio);
|
|
3314
3338
|
}
|
|
3315
3339
|
defineFxRadio();
|
|
3340
|
+
function toInt(value, fallback) {
|
|
3341
|
+
return Number.isFinite(value) ? Math.trunc(value) : fallback;
|
|
3342
|
+
}
|
|
3343
|
+
function range(start, end) {
|
|
3344
|
+
const out = [];
|
|
3345
|
+
for (let p = start; p <= end; p++) out.push(p);
|
|
3346
|
+
return out;
|
|
3347
|
+
}
|
|
3348
|
+
function pageRange(current, count, options = {}) {
|
|
3349
|
+
const pages = Math.max(0, toInt(count, 0));
|
|
3350
|
+
if (pages === 0) return [];
|
|
3351
|
+
const boundaries = Math.max(0, toInt(options.boundaries ?? 1, 1));
|
|
3352
|
+
const siblings = Math.max(0, toInt(options.siblings ?? 1, 1));
|
|
3353
|
+
const page = Math.min(Math.max(1, toInt(current, 1)), pages);
|
|
3354
|
+
if (pages <= boundaries * 2 + siblings * 2 + 3) return range(1, pages);
|
|
3355
|
+
const startPages = range(1, Math.min(boundaries, pages));
|
|
3356
|
+
const endPages = range(
|
|
3357
|
+
Math.max(pages - boundaries + 1, boundaries + 1),
|
|
3358
|
+
pages
|
|
3359
|
+
);
|
|
3360
|
+
const siblingsStart = Math.max(
|
|
3361
|
+
Math.min(page - siblings, pages - boundaries - siblings * 2 - 1),
|
|
3362
|
+
boundaries + 2
|
|
3363
|
+
);
|
|
3364
|
+
const siblingsEnd = Math.min(
|
|
3365
|
+
Math.max(page + siblings, boundaries + siblings * 2 + 2),
|
|
3366
|
+
endPages.length > 0 ? endPages[0] - 2 : pages - 1
|
|
3367
|
+
);
|
|
3368
|
+
const items = [...startPages];
|
|
3369
|
+
if (siblingsStart > boundaries + 2) items.push("ellipsis");
|
|
3370
|
+
else if (boundaries + 1 < pages - boundaries) items.push(boundaries + 1);
|
|
3371
|
+
items.push(...range(siblingsStart, siblingsEnd));
|
|
3372
|
+
if (siblingsEnd < pages - boundaries - 1) items.push("ellipsis");
|
|
3373
|
+
else if (pages - boundaries > boundaries) items.push(pages - boundaries);
|
|
3374
|
+
items.push(...endPages);
|
|
3375
|
+
return items;
|
|
3376
|
+
}
|
|
3316
3377
|
const LOCALE = "pt-BR";
|
|
3317
3378
|
const PIPES = {
|
|
3318
3379
|
/** Formata como moeda brasileira (R$). */
|
|
@@ -3795,13 +3856,13 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
3795
3856
|
return [...this.querySelectorAll("fx-column")].map((c) => {
|
|
3796
3857
|
const tpl = this._resolveCellTemplate(c);
|
|
3797
3858
|
const direct = tpl ? void 0 : c.innerHTML.trim();
|
|
3798
|
-
const
|
|
3859
|
+
const align2 = c.getAttribute("align") ?? "left";
|
|
3799
3860
|
return {
|
|
3800
3861
|
field: c.getAttribute("field") ?? "",
|
|
3801
3862
|
header: c.getAttribute("header") ?? c.getAttribute("field") ?? "",
|
|
3802
3863
|
sortable: c.hasAttribute("sortable"),
|
|
3803
3864
|
filterable: c.hasAttribute("filterable"),
|
|
3804
|
-
align: ["left", "center", "right", "justify", "start", "end"].includes(
|
|
3865
|
+
align: ["left", "center", "right", "justify", "start", "end"].includes(align2) ? align2 : "left",
|
|
3805
3866
|
template: tpl ?? void 0,
|
|
3806
3867
|
directTemplate: direct || void 0
|
|
3807
3868
|
};
|
|
@@ -3967,12 +4028,15 @@ const _FxTable = class _FxTable extends FxElement {
|
|
|
3967
4028
|
const bodyHtml = rows.length ? rows.map(
|
|
3968
4029
|
(row, i) => `<tr part="row" data-index="${i}">${cols.map((c) => `<td part="cell" style="text-align:${c.align}">${this.cellHtml(c, row)}</td>`).join("")}</tr>`
|
|
3969
4030
|
).join("") : `<tr><td class="empty" colspan="${cols.length || 1}">${esc(this.getAttr("empty-message", "Nenhum registro encontrado"))}</td></tr>`;
|
|
4031
|
+
const pageButtons = pageRange(this.page + 1, pages).map(
|
|
4032
|
+
(item) => item === "ellipsis" ? `<span class="ellipsis" part="ellipsis" aria-hidden="true">…</span>` : `<button type="button" class="pg-btn" part="page" data-pg="${item - 1}"${item === this.page + 1 ? ' aria-current="page"' : ""}>${item}</button>`
|
|
4033
|
+
).join("");
|
|
3970
4034
|
const pagerHtml = !paginated ? "" : `
|
|
3971
4035
|
<div class="pager" part="pager">
|
|
3972
4036
|
<span class="info">Página ${this.page + 1} de ${pages} · ${total} registros</span>
|
|
3973
4037
|
<button type="button" class="pg-btn" part="first" aria-label="Primeira página" data-pg="first" ${this.page === 0 ? "disabled" : ""}>${fenixIconHtml("first_page")}</button>
|
|
3974
4038
|
<button type="button" class="pg-btn" part="prev" aria-label="Página anterior" data-pg="prev" ${this.page === 0 ? "disabled" : ""}>${fenixIconHtml("navigate_before")}</button>
|
|
3975
|
-
${
|
|
4039
|
+
${pageButtons}
|
|
3976
4040
|
<button type="button" class="pg-btn" part="next" aria-label="Próxima página" data-pg="next" ${this.page >= pages - 1 ? "disabled" : ""}>${fenixIconHtml("navigate_next")}</button>
|
|
3977
4041
|
<button type="button" class="pg-btn" part="last" aria-label="Última página" data-pg="last" ${this.page >= pages - 1 ? "disabled" : ""}>${fenixIconHtml("last_page")}</button>
|
|
3978
4042
|
<label class="info">${esc("Por página:")}
|
|
@@ -4328,7 +4392,7 @@ _FxTable.styles = css`
|
|
|
4328
4392
|
.pg-btn .fx-icon {
|
|
4329
4393
|
font-size: calc(var(--fx-font-size) - 1px);
|
|
4330
4394
|
}
|
|
4331
|
-
.pg-btn:hover:not(:disabled):not([aria-current="
|
|
4395
|
+
.pg-btn:hover:not(:disabled):not([aria-current="page"]) {
|
|
4332
4396
|
border-color: var(--fx-color-primary);
|
|
4333
4397
|
color: var(--fx-color-primary);
|
|
4334
4398
|
}
|
|
@@ -4336,12 +4400,25 @@ _FxTable.styles = css`
|
|
|
4336
4400
|
opacity: 0.5;
|
|
4337
4401
|
cursor: not-allowed;
|
|
4338
4402
|
}
|
|
4339
|
-
.pg-btn[aria-current="
|
|
4403
|
+
.pg-btn[aria-current="page"] {
|
|
4340
4404
|
background: var(--fx-color-primary);
|
|
4341
4405
|
border-color: var(--fx-color-primary);
|
|
4342
4406
|
color: #fff;
|
|
4343
4407
|
font-weight: var(--fx-font-weight);
|
|
4344
4408
|
}
|
|
4409
|
+
/* Elipse da janela de páginas (1 … 499 [500] 501 … 1000): ocupa o
|
|
4410
|
+
mesmo espaço de um botão para o rodapé não "pular" ao navegar,
|
|
4411
|
+
mas não é clicável. */
|
|
4412
|
+
.pager .ellipsis {
|
|
4413
|
+
display: inline-flex;
|
|
4414
|
+
align-items: center;
|
|
4415
|
+
justify-content: center;
|
|
4416
|
+
min-width: var(--fx-size-sm);
|
|
4417
|
+
height: var(--fx-size-sm);
|
|
4418
|
+
color: var(--fx-text-muted);
|
|
4419
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
4420
|
+
user-select: none;
|
|
4421
|
+
}
|
|
4345
4422
|
/* O seletor "Por página" só mostra números, então não pode herdar a
|
|
4346
4423
|
largura padrão do <fx-select> (200px / 180px em size="sm"), que
|
|
4347
4424
|
deixava o rodapé da paginação desproporcional. O limite tem que ser
|
|
@@ -4885,174 +4962,3742 @@ function defineFxTextarea() {
|
|
|
4885
4962
|
return defineElement("fx-textarea", FxTextarea);
|
|
4886
4963
|
}
|
|
4887
4964
|
defineFxTextarea();
|
|
4888
|
-
const
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4965
|
+
const ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
4966
|
+
"p",
|
|
4967
|
+
"br",
|
|
4968
|
+
"strong",
|
|
4969
|
+
"b",
|
|
4970
|
+
"em",
|
|
4971
|
+
"i",
|
|
4972
|
+
"u",
|
|
4973
|
+
"s",
|
|
4974
|
+
"del",
|
|
4975
|
+
"ins",
|
|
4976
|
+
"sub",
|
|
4977
|
+
"sup",
|
|
4978
|
+
"code",
|
|
4979
|
+
"pre",
|
|
4980
|
+
"blockquote",
|
|
4981
|
+
"h1",
|
|
4982
|
+
"h2",
|
|
4983
|
+
"h3",
|
|
4984
|
+
"h4",
|
|
4985
|
+
"h5",
|
|
4986
|
+
"h6",
|
|
4987
|
+
"ul",
|
|
4988
|
+
"ol",
|
|
4989
|
+
"li",
|
|
4990
|
+
"a",
|
|
4991
|
+
"img",
|
|
4992
|
+
"figure",
|
|
4993
|
+
"figcaption",
|
|
4994
|
+
"video",
|
|
4995
|
+
"source",
|
|
4996
|
+
"iframe",
|
|
4997
|
+
"table",
|
|
4998
|
+
"thead",
|
|
4999
|
+
"tbody",
|
|
5000
|
+
"tfoot",
|
|
5001
|
+
"tr",
|
|
5002
|
+
"th",
|
|
5003
|
+
"td",
|
|
5004
|
+
"colgroup",
|
|
5005
|
+
"col",
|
|
5006
|
+
"hr",
|
|
5007
|
+
"span",
|
|
5008
|
+
"div",
|
|
5009
|
+
"small",
|
|
5010
|
+
"mark"
|
|
5011
|
+
]);
|
|
5012
|
+
const DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
|
|
5013
|
+
"script",
|
|
5014
|
+
"style",
|
|
5015
|
+
"template",
|
|
5016
|
+
"noscript",
|
|
5017
|
+
"svg",
|
|
5018
|
+
"math",
|
|
5019
|
+
"object",
|
|
5020
|
+
"embed",
|
|
5021
|
+
"form",
|
|
5022
|
+
"input",
|
|
5023
|
+
"button",
|
|
5024
|
+
"textarea",
|
|
5025
|
+
"select",
|
|
5026
|
+
"option",
|
|
5027
|
+
"meta",
|
|
5028
|
+
"link",
|
|
5029
|
+
"base",
|
|
5030
|
+
"title",
|
|
5031
|
+
"head",
|
|
5032
|
+
"audio",
|
|
5033
|
+
"canvas",
|
|
5034
|
+
"map",
|
|
5035
|
+
"area"
|
|
5036
|
+
]);
|
|
5037
|
+
const GLOBAL_ATTRS = /* @__PURE__ */ new Set([
|
|
5038
|
+
"title",
|
|
5039
|
+
"dir",
|
|
5040
|
+
"lang",
|
|
5041
|
+
"role"
|
|
5042
|
+
]);
|
|
5043
|
+
const TAG_ATTRS = {
|
|
5044
|
+
a: /* @__PURE__ */ new Set(["href", "target", "rel", "download"]),
|
|
5045
|
+
img: /* @__PURE__ */ new Set(["src", "alt", "width", "height", "loading", "decoding"]),
|
|
5046
|
+
video: /* @__PURE__ */ new Set(["src", "poster", "controls", "preload", "width", "height", "muted", "loop", "playsinline"]),
|
|
5047
|
+
source: /* @__PURE__ */ new Set(["src", "type"]),
|
|
5048
|
+
iframe: /* @__PURE__ */ new Set([
|
|
5049
|
+
"src",
|
|
5050
|
+
"title",
|
|
5051
|
+
"width",
|
|
5052
|
+
"height",
|
|
5053
|
+
"allow",
|
|
5054
|
+
"allowfullscreen",
|
|
5055
|
+
"sandbox",
|
|
5056
|
+
"referrerpolicy",
|
|
5057
|
+
"loading"
|
|
5058
|
+
]),
|
|
5059
|
+
td: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope", "style", "class", "data-align"]),
|
|
5060
|
+
th: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope", "style", "class", "data-align"]),
|
|
5061
|
+
table: /* @__PURE__ */ new Set(["style", "class", "role", "data-cols", "data-stack"]),
|
|
5062
|
+
col: /* @__PURE__ */ new Set(["span", "style", "width"]),
|
|
5063
|
+
colgroup: /* @__PURE__ */ new Set(["span", "style"]),
|
|
5064
|
+
figure: /* @__PURE__ */ new Set(["class", "style", "data-align", "data-cols"]),
|
|
5065
|
+
figcaption: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5066
|
+
span: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5067
|
+
div: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5068
|
+
p: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5069
|
+
h1: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5070
|
+
h2: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5071
|
+
h3: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5072
|
+
h4: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5073
|
+
h5: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5074
|
+
h6: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5075
|
+
blockquote: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5076
|
+
pre: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5077
|
+
code: /* @__PURE__ */ new Set(["class"]),
|
|
5078
|
+
li: /* @__PURE__ */ new Set(["class", "style", "value"]),
|
|
5079
|
+
ol: /* @__PURE__ */ new Set(["class", "style", "start", "type"]),
|
|
5080
|
+
ul: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5081
|
+
hr: /* @__PURE__ */ new Set(["class", "style"]),
|
|
5082
|
+
small: /* @__PURE__ */ new Set(["class"]),
|
|
5083
|
+
mark: /* @__PURE__ */ new Set(["class", "style"])
|
|
5084
|
+
};
|
|
5085
|
+
const ALLOWED_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
5086
|
+
"color",
|
|
5087
|
+
"background-color",
|
|
5088
|
+
"text-align",
|
|
5089
|
+
"font-size",
|
|
5090
|
+
"font-family",
|
|
5091
|
+
"font-weight",
|
|
5092
|
+
"font-style",
|
|
5093
|
+
"text-decoration",
|
|
5094
|
+
"text-decoration-line",
|
|
5095
|
+
"vertical-align",
|
|
5096
|
+
"width",
|
|
5097
|
+
"height",
|
|
5098
|
+
"max-width",
|
|
5099
|
+
"min-width",
|
|
5100
|
+
"margin",
|
|
5101
|
+
"margin-top",
|
|
5102
|
+
"margin-right",
|
|
5103
|
+
"margin-bottom",
|
|
5104
|
+
"margin-left",
|
|
5105
|
+
"padding",
|
|
5106
|
+
"padding-top",
|
|
5107
|
+
"padding-right",
|
|
5108
|
+
"padding-bottom",
|
|
5109
|
+
"padding-left",
|
|
5110
|
+
"border",
|
|
5111
|
+
"border-top",
|
|
5112
|
+
"border-right",
|
|
5113
|
+
"border-bottom",
|
|
5114
|
+
"border-left",
|
|
5115
|
+
"border-collapse",
|
|
5116
|
+
"border-spacing",
|
|
5117
|
+
"border-radius",
|
|
5118
|
+
"float",
|
|
5119
|
+
"display",
|
|
5120
|
+
"line-height",
|
|
5121
|
+
"white-space",
|
|
5122
|
+
"word-break",
|
|
5123
|
+
"overflow-wrap",
|
|
5124
|
+
"list-style-type"
|
|
5125
|
+
]);
|
|
5126
|
+
const ALLOWED_CLASS_RE = /^fx-[a-z0-9-]+$/i;
|
|
5127
|
+
const ALLOWED_EMBED_HOSTS = /* @__PURE__ */ new Set([
|
|
5128
|
+
"www.youtube.com",
|
|
5129
|
+
"youtube.com",
|
|
5130
|
+
"www.youtube-nocookie.com",
|
|
5131
|
+
"youtube-nocookie.com",
|
|
5132
|
+
"player.vimeo.com"
|
|
5133
|
+
]);
|
|
5134
|
+
const SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
5135
|
+
const SAFE_MEDIA_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
5136
|
+
const SAFE_IMAGE_DATA_RE = /^data:image\/(?:png|jpe?g|gif|webp|avif);base64,[a-z0-9+/=\s]+$/i;
|
|
5137
|
+
const DEFAULT_MAX_DATA_IMAGE_BYTES = 2 * 1024 * 1024;
|
|
5138
|
+
const DEFAULT_MAX_DATA_VIDEO_BYTES = 4 * 1024 * 1024;
|
|
5139
|
+
const FORBIDDEN_ATTRS = /* @__PURE__ */ new Set([
|
|
5140
|
+
"contenteditable",
|
|
5141
|
+
"spellcheck",
|
|
5142
|
+
"tabindex",
|
|
5143
|
+
"autofocus",
|
|
5144
|
+
"id",
|
|
5145
|
+
"onclick",
|
|
5146
|
+
"ondblclick",
|
|
5147
|
+
"onmousedown",
|
|
5148
|
+
"onmouseup",
|
|
5149
|
+
"onmouseover",
|
|
5150
|
+
"onmouseout",
|
|
5151
|
+
"onmousemove",
|
|
5152
|
+
"onkeydown",
|
|
5153
|
+
"onkeyup",
|
|
5154
|
+
"onkeypress",
|
|
5155
|
+
"onload",
|
|
5156
|
+
"onerror",
|
|
5157
|
+
"onfocus",
|
|
5158
|
+
"onblur",
|
|
5159
|
+
"oninput",
|
|
5160
|
+
"onchange",
|
|
5161
|
+
"onsubmit",
|
|
5162
|
+
"onreset",
|
|
5163
|
+
"onselect",
|
|
5164
|
+
"ondrag",
|
|
5165
|
+
"ondrop",
|
|
5166
|
+
"onpaste",
|
|
5167
|
+
"oncopy",
|
|
5168
|
+
"oncut",
|
|
5169
|
+
"onscroll",
|
|
5170
|
+
"onwheel",
|
|
5171
|
+
"onanimationstart",
|
|
5172
|
+
"onanimationend",
|
|
5173
|
+
"ontransitionend",
|
|
5174
|
+
"formaction",
|
|
5175
|
+
"srcdoc",
|
|
5176
|
+
"xlink:href",
|
|
5177
|
+
"xmlns",
|
|
5178
|
+
"http-equiv",
|
|
5179
|
+
"ping"
|
|
5180
|
+
]);
|
|
5181
|
+
const DEFAULTS = {
|
|
5182
|
+
allowBase64Images: true,
|
|
5183
|
+
allowBase64Videos: false,
|
|
5184
|
+
allowEmbeds: true,
|
|
5185
|
+
maxDataImageBytes: DEFAULT_MAX_DATA_IMAGE_BYTES,
|
|
5186
|
+
maxDataVideoBytes: DEFAULT_MAX_DATA_VIDEO_BYTES
|
|
5187
|
+
};
|
|
5188
|
+
const SAFE_DATA_VIDEO_RE = /^data:video\/(?:mp4|webm);base64,[a-z0-9+/=\s]+$/i;
|
|
5189
|
+
function cleanRawUrl(value) {
|
|
5190
|
+
return value.replace(/[\u0000-\u001F\u007F\s]+/g, "").trim();
|
|
5191
|
+
}
|
|
5192
|
+
function isRelativeUrl(value) {
|
|
5193
|
+
return !value.startsWith("//") && /^(?:#|\/|\.{1,2}\/)/.test(value);
|
|
5194
|
+
}
|
|
5195
|
+
function sanitizeLinkUrl(value) {
|
|
5196
|
+
const raw = cleanRawUrl(value);
|
|
5197
|
+
if (!raw) return "";
|
|
5198
|
+
if (isRelativeUrl(raw)) return raw;
|
|
5199
|
+
const lower = raw.toLowerCase();
|
|
5200
|
+
if (lower.startsWith("javascript:") || lower.startsWith("vbscript:") || lower.startsWith("data:") || lower.startsWith("file:") || lower.startsWith("blob:")) {
|
|
5201
|
+
return "";
|
|
4892
5202
|
}
|
|
4893
|
-
|
|
4894
|
-
|
|
5203
|
+
try {
|
|
5204
|
+
const url = new URL(raw);
|
|
5205
|
+
return SAFE_LINK_PROTOCOLS.has(url.protocol) ? raw : "";
|
|
5206
|
+
} catch {
|
|
5207
|
+
return /^[a-z0-9][a-z0-9._~!$&'()*+,;=:@%/?#-]*$/i.test(raw) ? raw : "";
|
|
4895
5208
|
}
|
|
4896
|
-
|
|
4897
|
-
|
|
5209
|
+
}
|
|
5210
|
+
function sanitizeMediaUrl(value, tag, options = {}) {
|
|
5211
|
+
const opts = { ...DEFAULTS, ...options };
|
|
5212
|
+
const raw = cleanRawUrl(value);
|
|
5213
|
+
if (!raw) return "";
|
|
5214
|
+
if (raw.toLowerCase().startsWith("data:")) {
|
|
5215
|
+
if (tag === "img" && opts.allowBase64Images) {
|
|
5216
|
+
return SAFE_IMAGE_DATA_RE.test(raw) && raw.length <= opts.maxDataImageBytes ? raw : "";
|
|
5217
|
+
}
|
|
5218
|
+
if ((tag === "video" || tag === "source") && opts.allowBase64Videos) {
|
|
5219
|
+
return SAFE_DATA_VIDEO_RE.test(raw) && raw.length <= opts.maxDataVideoBytes ? raw : "";
|
|
5220
|
+
}
|
|
5221
|
+
return "";
|
|
4898
5222
|
}
|
|
4899
|
-
|
|
4900
|
-
|
|
5223
|
+
if (isRelativeUrl(raw)) return raw;
|
|
5224
|
+
const lower = raw.toLowerCase();
|
|
5225
|
+
if (lower.startsWith("javascript:") || lower.startsWith("vbscript:") || lower.startsWith("file:") || lower.startsWith("blob:")) {
|
|
5226
|
+
return "";
|
|
4901
5227
|
}
|
|
4902
|
-
|
|
4903
|
-
const
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
<div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
|
|
4908
|
-
<header>
|
|
4909
|
-
<h2>${esc(heading)}</h2>
|
|
4910
|
-
<button type="button" class="close" part="close" aria-label="Fechar">×</button>
|
|
4911
|
-
</header>
|
|
4912
|
-
<div class="body"><slot></slot></div>
|
|
4913
|
-
<footer part="footer"><slot name="footer"></slot></footer>
|
|
4914
|
-
</div>
|
|
4915
|
-
</div>
|
|
4916
|
-
`);
|
|
4917
|
-
if (!isOpen) return;
|
|
4918
|
-
this._cleanup?.();
|
|
4919
|
-
this._cleanup = void 0;
|
|
4920
|
-
const overlay = this.root.querySelector(".overlay");
|
|
4921
|
-
const closeBtn = this.root.querySelector(".close");
|
|
4922
|
-
const close = () => {
|
|
4923
|
-
this.open = false;
|
|
4924
|
-
this._cleanup?.();
|
|
4925
|
-
this._cleanup = void 0;
|
|
4926
|
-
this._restoreFocus();
|
|
4927
|
-
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
4928
|
-
};
|
|
4929
|
-
overlay?.addEventListener("click", (e) => {
|
|
4930
|
-
if (e.target === overlay) close();
|
|
4931
|
-
});
|
|
4932
|
-
closeBtn?.addEventListener("click", close);
|
|
4933
|
-
const onKey = (e) => {
|
|
4934
|
-
if (e.key === "Escape") {
|
|
4935
|
-
e.stopPropagation();
|
|
4936
|
-
close();
|
|
4937
|
-
return;
|
|
4938
|
-
}
|
|
4939
|
-
if (e.key === "Tab") this._trapFocus(e);
|
|
4940
|
-
};
|
|
4941
|
-
document.addEventListener("keydown", onKey);
|
|
4942
|
-
this._cleanup = () => document.removeEventListener("keydown", onKey);
|
|
4943
|
-
requestAnimationFrame(() => {
|
|
4944
|
-
this._previouslyFocused = document.activeElement;
|
|
4945
|
-
this.root.querySelector(".dialog")?.focus();
|
|
4946
|
-
});
|
|
4947
|
-
this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
|
|
5228
|
+
try {
|
|
5229
|
+
const url = new URL(raw);
|
|
5230
|
+
return SAFE_MEDIA_PROTOCOLS.has(url.protocol) ? raw : "";
|
|
5231
|
+
} catch {
|
|
5232
|
+
return "";
|
|
4948
5233
|
}
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
5234
|
+
}
|
|
5235
|
+
function sanitizeEmbedUrl(value) {
|
|
5236
|
+
let raw = cleanRawUrl(value);
|
|
5237
|
+
if (!raw) return "";
|
|
5238
|
+
if (raw.startsWith("//")) raw = `https:${raw}`;
|
|
5239
|
+
let url;
|
|
5240
|
+
try {
|
|
5241
|
+
url = new URL(raw);
|
|
5242
|
+
} catch {
|
|
5243
|
+
return "";
|
|
5244
|
+
}
|
|
5245
|
+
if (url.protocol !== "https:") return "";
|
|
5246
|
+
if (!ALLOWED_EMBED_HOSTS.has(url.hostname.toLowerCase())) return "";
|
|
5247
|
+
const path = url.pathname.toLowerCase();
|
|
5248
|
+
const isValidPath = url.hostname.includes("youtube") && path.startsWith("/embed/") || url.hostname.includes("vimeo") && path.startsWith("/video/");
|
|
5249
|
+
if (!isValidPath) return "";
|
|
5250
|
+
return url.toString();
|
|
5251
|
+
}
|
|
5252
|
+
function sanitizeStyle(value) {
|
|
5253
|
+
if (!value) return "";
|
|
5254
|
+
const probe = document.createElement("span");
|
|
5255
|
+
probe.setAttribute("style", value);
|
|
5256
|
+
const out = [];
|
|
5257
|
+
for (let i = 0; i < probe.style.length; i++) {
|
|
5258
|
+
const prop = probe.style.item(i);
|
|
5259
|
+
if (!ALLOWED_STYLE_PROPS.has(prop)) continue;
|
|
5260
|
+
const val = probe.style.getPropertyValue(prop).trim();
|
|
5261
|
+
if (!val) continue;
|
|
5262
|
+
if (/\\/.test(val)) continue;
|
|
5263
|
+
if (/url\s*\(|expression\s*\(|javascript\s*:|@import|behavior\s*:|-moz-binding/i.test(val)) continue;
|
|
5264
|
+
out.push(`${prop}: ${val}`);
|
|
5265
|
+
}
|
|
5266
|
+
return out.join("; ");
|
|
5267
|
+
}
|
|
5268
|
+
function sanitizeAttributes(el, tag, opts) {
|
|
5269
|
+
for (const attr of Array.from(el.attributes)) {
|
|
5270
|
+
const name = attr.name.toLowerCase();
|
|
5271
|
+
const value = attr.value;
|
|
5272
|
+
if (FORBIDDEN_ATTRS.has(name) || name.startsWith("on")) {
|
|
5273
|
+
el.removeAttribute(attr.name);
|
|
5274
|
+
continue;
|
|
5275
|
+
}
|
|
5276
|
+
const allowed = GLOBAL_ATTRS.has(name) || TAG_ATTRS[tag]?.has(name);
|
|
5277
|
+
if (!allowed) {
|
|
5278
|
+
el.removeAttribute(attr.name);
|
|
5279
|
+
continue;
|
|
5280
|
+
}
|
|
5281
|
+
if (name === "style") {
|
|
5282
|
+
const safe = sanitizeStyle(value);
|
|
5283
|
+
if (safe) el.setAttribute("style", safe);
|
|
5284
|
+
else el.removeAttribute("style");
|
|
5285
|
+
continue;
|
|
5286
|
+
}
|
|
5287
|
+
if (name === "class") {
|
|
5288
|
+
const classes = value.split(/\s+/).filter((c) => ALLOWED_CLASS_RE.test(c));
|
|
5289
|
+
if (classes.length) el.setAttribute("class", classes.join(" "));
|
|
5290
|
+
else el.removeAttribute("class");
|
|
5291
|
+
continue;
|
|
5292
|
+
}
|
|
5293
|
+
if (name === "href") {
|
|
5294
|
+
const safe = sanitizeLinkUrl(value);
|
|
5295
|
+
if (safe) el.setAttribute("href", safe);
|
|
5296
|
+
else el.removeAttribute("href");
|
|
5297
|
+
continue;
|
|
5298
|
+
}
|
|
5299
|
+
if (name === "src") {
|
|
5300
|
+
const safe = sanitizeMediaUrl(value, tag, opts);
|
|
5301
|
+
if (safe) el.setAttribute("src", safe);
|
|
5302
|
+
else el.removeAttribute("src");
|
|
5303
|
+
continue;
|
|
5304
|
+
}
|
|
5305
|
+
if (name === "poster") {
|
|
5306
|
+
const safe = sanitizeMediaUrl(value, "img", opts);
|
|
5307
|
+
if (safe) el.setAttribute("poster", safe);
|
|
5308
|
+
else el.removeAttribute("poster");
|
|
5309
|
+
continue;
|
|
5310
|
+
}
|
|
5311
|
+
if (name === "target") {
|
|
5312
|
+
const safe = value === "_self" || value === "_blank" ? value : "_blank";
|
|
5313
|
+
el.setAttribute("target", safe);
|
|
5314
|
+
continue;
|
|
5315
|
+
}
|
|
5316
|
+
if (name === "colspan" || name === "rowspan" || name === "span" || name === "start") {
|
|
5317
|
+
const n = Number(value);
|
|
5318
|
+
if (Number.isInteger(n) && n > 0 && n <= 1e3) el.setAttribute(name, String(n));
|
|
5319
|
+
else el.removeAttribute(name);
|
|
5320
|
+
continue;
|
|
5321
|
+
}
|
|
5322
|
+
if (name === "width" || name === "height") {
|
|
5323
|
+
if (/^\d{1,4}(?:px|%)?$/i.test(value.trim())) el.setAttribute(name, value.trim());
|
|
5324
|
+
else el.removeAttribute(name);
|
|
5325
|
+
continue;
|
|
4966
5326
|
}
|
|
4967
5327
|
}
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
5328
|
+
if (tag === "a") {
|
|
5329
|
+
const href = el.getAttribute("href");
|
|
5330
|
+
if (!href) {
|
|
5331
|
+
el.removeAttribute("target");
|
|
5332
|
+
el.removeAttribute("rel");
|
|
5333
|
+
} else if (el.getAttribute("target") === "_blank") {
|
|
5334
|
+
el.setAttribute("rel", "noopener noreferrer");
|
|
5335
|
+
}
|
|
4971
5336
|
}
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
5337
|
+
if (tag === "iframe") {
|
|
5338
|
+
el.setAttribute("sandbox", "allow-scripts allow-same-origin allow-presentation");
|
|
5339
|
+
el.setAttribute("allowfullscreen", "");
|
|
5340
|
+
el.setAttribute("loading", "lazy");
|
|
5341
|
+
el.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
4977
5342
|
}
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
.
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
5343
|
+
if (tag === "img") {
|
|
5344
|
+
if (!el.hasAttribute("alt")) el.setAttribute("alt", "");
|
|
5345
|
+
el.setAttribute("loading", "lazy");
|
|
5346
|
+
el.setAttribute("decoding", "async");
|
|
5347
|
+
}
|
|
5348
|
+
if (tag === "video") {
|
|
5349
|
+
el.setAttribute("controls", "");
|
|
5350
|
+
el.setAttribute("preload", "metadata");
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
function sanitizeIframe(el) {
|
|
5354
|
+
const src = el.getAttribute("src") ?? "";
|
|
5355
|
+
const safe = sanitizeEmbedUrl(src);
|
|
5356
|
+
if (!safe) return false;
|
|
5357
|
+
el.setAttribute("src", safe);
|
|
5358
|
+
return true;
|
|
5359
|
+
}
|
|
5360
|
+
function unwrap$2(el, opts) {
|
|
5361
|
+
const parent = el.parentNode;
|
|
5362
|
+
if (!parent) {
|
|
5363
|
+
el.remove();
|
|
5364
|
+
return;
|
|
5365
|
+
}
|
|
5366
|
+
for (const child of Array.from(el.childNodes)) {
|
|
5367
|
+
sanitizeNode(child, opts);
|
|
5368
|
+
parent.insertBefore(child, el);
|
|
5369
|
+
}
|
|
5370
|
+
el.remove();
|
|
5371
|
+
}
|
|
5372
|
+
function sanitizeNode(node, opts) {
|
|
5373
|
+
if (node.nodeType === Node.TEXT_NODE) return;
|
|
5374
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
5375
|
+
node.parentNode?.removeChild(node);
|
|
5376
|
+
return;
|
|
5377
|
+
}
|
|
5378
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
5379
|
+
node.parentNode?.removeChild(node);
|
|
5380
|
+
return;
|
|
5381
|
+
}
|
|
5382
|
+
const el = node;
|
|
5383
|
+
const tag = el.tagName.toLowerCase();
|
|
5384
|
+
if (DROP_CONTENT_TAGS.has(tag)) {
|
|
5385
|
+
el.remove();
|
|
5386
|
+
return;
|
|
5387
|
+
}
|
|
5388
|
+
if (tag === "iframe") {
|
|
5389
|
+
if (!opts.allowEmbeds || !sanitizeIframe(el)) el.remove();
|
|
5390
|
+
else sanitizeAttributes(el, tag, opts);
|
|
5391
|
+
return;
|
|
5392
|
+
}
|
|
5393
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
5394
|
+
unwrap$2(el, opts);
|
|
5395
|
+
return;
|
|
5396
|
+
}
|
|
5397
|
+
sanitizeAttributes(el, tag, opts);
|
|
5398
|
+
if ((tag === "img" || tag === "video" || tag === "source") && !el.hasAttribute("src")) {
|
|
5399
|
+
el.remove();
|
|
5400
|
+
return;
|
|
5401
|
+
}
|
|
5402
|
+
for (const child of Array.from(el.childNodes)) sanitizeNode(child, opts);
|
|
5403
|
+
}
|
|
5404
|
+
function sanitizeBody(html, options = {}) {
|
|
5405
|
+
const opts = { ...DEFAULTS, ...options };
|
|
5406
|
+
if (!html) return null;
|
|
5407
|
+
if (typeof DOMParser === "undefined" || typeof document === "undefined") return null;
|
|
5408
|
+
const doc = new DOMParser().parseFromString(
|
|
5409
|
+
`<!doctype html><html><body>${html}</body></html>`,
|
|
5410
|
+
"text/html"
|
|
5411
|
+
);
|
|
5412
|
+
for (const child of Array.from(doc.body.childNodes)) sanitizeNode(child, opts);
|
|
5413
|
+
return doc.body;
|
|
5414
|
+
}
|
|
5415
|
+
function sanitizeHtml(html, options = {}) {
|
|
5416
|
+
const body = sanitizeBody(html, options);
|
|
5417
|
+
return body ? body.innerHTML : "";
|
|
5418
|
+
}
|
|
5419
|
+
const BLOCK_TAGS = /* @__PURE__ */ new Set([
|
|
5420
|
+
"p",
|
|
5421
|
+
"h1",
|
|
5422
|
+
"h2",
|
|
5423
|
+
"h3",
|
|
5424
|
+
"h4",
|
|
5425
|
+
"h5",
|
|
5426
|
+
"h6",
|
|
5427
|
+
"blockquote",
|
|
5428
|
+
"pre",
|
|
5429
|
+
"ul",
|
|
5430
|
+
"ol",
|
|
5431
|
+
"li",
|
|
5432
|
+
"table",
|
|
5433
|
+
"figure",
|
|
5434
|
+
"hr",
|
|
5435
|
+
"div"
|
|
5436
|
+
]);
|
|
5437
|
+
const INLINE_TAGS = /* @__PURE__ */ new Set([
|
|
5438
|
+
"strong",
|
|
5439
|
+
"b",
|
|
5440
|
+
"em",
|
|
5441
|
+
"i",
|
|
5442
|
+
"u",
|
|
5443
|
+
"s",
|
|
5444
|
+
"del",
|
|
5445
|
+
"ins",
|
|
5446
|
+
"sub",
|
|
5447
|
+
"sup",
|
|
5448
|
+
"code",
|
|
5449
|
+
"a",
|
|
5450
|
+
"span",
|
|
5451
|
+
"small",
|
|
5452
|
+
"mark",
|
|
5453
|
+
"br"
|
|
5454
|
+
]);
|
|
5455
|
+
function isElement(node) {
|
|
5456
|
+
return node.nodeType === Node.ELEMENT_NODE;
|
|
5457
|
+
}
|
|
5458
|
+
function tagOf(node) {
|
|
5459
|
+
return isElement(node) ? node.tagName.toLowerCase() : "";
|
|
5460
|
+
}
|
|
5461
|
+
function hasBlockChild(el) {
|
|
5462
|
+
return Array.from(el.children).some((child) => BLOCK_TAGS.has(tagOf(child)) || tagOf(child) === "figure");
|
|
5463
|
+
}
|
|
5464
|
+
function unwrap$1(el) {
|
|
5465
|
+
const parent = el.parentNode;
|
|
5466
|
+
if (!parent) {
|
|
5467
|
+
el.remove();
|
|
5468
|
+
return;
|
|
5469
|
+
}
|
|
5470
|
+
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
5471
|
+
el.remove();
|
|
5472
|
+
}
|
|
5473
|
+
function rename(el, tag) {
|
|
5474
|
+
const doc = el.ownerDocument;
|
|
5475
|
+
const next = doc.createElement(tag);
|
|
5476
|
+
for (const attr of Array.from(el.attributes)) next.setAttribute(attr.name, attr.value);
|
|
5477
|
+
while (el.firstChild) next.appendChild(el.firstChild);
|
|
5478
|
+
el.replaceWith(next);
|
|
5479
|
+
return next;
|
|
5480
|
+
}
|
|
5481
|
+
function normalizeChildren(parent, options) {
|
|
5482
|
+
for (const node of Array.from(parent.childNodes)) {
|
|
5483
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
5484
|
+
node.remove();
|
|
5485
|
+
continue;
|
|
4992
5486
|
}
|
|
4993
|
-
.
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
background: var(--fx-surface-background);
|
|
4997
|
-
border: 1px solid var(--fx-border-default);
|
|
4998
|
-
border-radius: var(--fx-radius-lg);
|
|
4999
|
-
box-shadow: var(--fx-shadow-xl);
|
|
5000
|
-
/* Largura customizável por instância: defina --fx-dialog-width no elemento. */
|
|
5001
|
-
width: min(var(--fx-dialog-width, 560px), calc(100vw - 32px));
|
|
5002
|
-
max-height: calc(100vh - 64px);
|
|
5003
|
-
display: flex;
|
|
5004
|
-
flex-direction: column;
|
|
5005
|
-
font-family: var(--fx-font-family);
|
|
5006
|
-
animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);
|
|
5487
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
5488
|
+
node.nodeValue = (node.nodeValue ?? "").replace(/\u00a0/g, " ");
|
|
5489
|
+
continue;
|
|
5007
5490
|
}
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5491
|
+
if (!isElement(node)) continue;
|
|
5492
|
+
let el = node;
|
|
5493
|
+
let tag = tagOf(el);
|
|
5494
|
+
if (tag === "b") el = rename(el, "strong");
|
|
5495
|
+
else if (tag === "i") el = rename(el, "em");
|
|
5496
|
+
else if (tag === "strike" || tag === "del") el = rename(el, "s");
|
|
5497
|
+
tag = tagOf(el);
|
|
5498
|
+
if (tag === "font") {
|
|
5499
|
+
const span = rename(el, "span");
|
|
5500
|
+
const styles = [];
|
|
5501
|
+
const color = el.getAttribute("color");
|
|
5502
|
+
const face = el.getAttribute("face");
|
|
5503
|
+
if (color) styles.push(`color: ${color}`);
|
|
5504
|
+
if (face) styles.push(`font-family: ${face}`);
|
|
5505
|
+
if (styles.length) span.setAttribute("style", styles.join("; "));
|
|
5506
|
+
el = span;
|
|
5507
|
+
tag = "span";
|
|
5508
|
+
}
|
|
5509
|
+
if (tag === "div" || tag === "section" || tag === "article" || tag === "header" || tag === "footer" || tag === "main" || tag === "aside") {
|
|
5510
|
+
if (hasBlockChild(el)) {
|
|
5511
|
+
unwrap$1(el);
|
|
5512
|
+
continue;
|
|
5016
5513
|
}
|
|
5514
|
+
el = rename(el, "p");
|
|
5515
|
+
tag = "p";
|
|
5017
5516
|
}
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
justify-content: space-between;
|
|
5022
|
-
padding: var(--fx-space-lg) var(--fx-space-xl);
|
|
5023
|
-
border-bottom: 1px solid var(--fx-border-default);
|
|
5517
|
+
if (tag === "p" && hasBlockChild(el)) {
|
|
5518
|
+
unwrap$1(el);
|
|
5519
|
+
continue;
|
|
5024
5520
|
}
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
font-weight: 600;
|
|
5029
|
-
color: var(--fx-text-default);
|
|
5521
|
+
if (tag === "span" && !el.hasAttribute("style") && !el.hasAttribute("class")) {
|
|
5522
|
+
unwrap$1(el);
|
|
5523
|
+
continue;
|
|
5030
5524
|
}
|
|
5031
|
-
.
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5525
|
+
if (!options.forEditor) {
|
|
5526
|
+
el.removeAttribute("contenteditable");
|
|
5527
|
+
el.removeAttribute("spellcheck");
|
|
5528
|
+
el.removeAttribute("tabindex");
|
|
5529
|
+
}
|
|
5530
|
+
if (tag === "a") {
|
|
5531
|
+
if (!el.getAttribute("href")) {
|
|
5532
|
+
el.removeAttribute("target");
|
|
5533
|
+
el.removeAttribute("rel");
|
|
5534
|
+
} else if (el.getAttribute("target") === "_blank") {
|
|
5535
|
+
el.setAttribute("rel", "noopener noreferrer");
|
|
5536
|
+
}
|
|
5040
5537
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5538
|
+
normalizeChildren(el, options);
|
|
5539
|
+
}
|
|
5540
|
+
}
|
|
5541
|
+
function wrapInlineChildren(cell, doc) {
|
|
5542
|
+
if (Array.from(cell.children).some((child) => BLOCK_TAGS.has(tagOf(child)))) return;
|
|
5543
|
+
const p = doc.createElement("p");
|
|
5544
|
+
while (cell.firstChild) p.appendChild(cell.firstChild);
|
|
5545
|
+
if (!p.textContent?.trim() && !p.querySelector("img,video,iframe,br")) {
|
|
5546
|
+
p.innerHTML = "<br>";
|
|
5547
|
+
}
|
|
5548
|
+
cell.appendChild(p);
|
|
5549
|
+
}
|
|
5550
|
+
function normalizeFigures(body, options) {
|
|
5551
|
+
const doc = body.ownerDocument;
|
|
5552
|
+
for (const media of Array.from(body.querySelectorAll("img, video, iframe"))) {
|
|
5553
|
+
if (media.closest("figure")) continue;
|
|
5554
|
+
const anchor = media.closest("a");
|
|
5555
|
+
const wrapper = anchor && anchor.querySelectorAll("*").length === 1 ? anchor : media;
|
|
5556
|
+
const figure = doc.createElement("figure");
|
|
5557
|
+
const kind = tagOf(media);
|
|
5558
|
+
figure.className = kind === "img" ? "fx-image" : kind === "video" ? "fx-video" : "fx-embed";
|
|
5559
|
+
if (kind === "img") figure.setAttribute("data-align", "center");
|
|
5560
|
+
const p = wrapper.closest("p");
|
|
5561
|
+
if (p) p.after(figure);
|
|
5562
|
+
else wrapper.parentNode?.insertBefore(figure, wrapper);
|
|
5563
|
+
figure.appendChild(wrapper);
|
|
5564
|
+
if (options.forEditor) figure.setAttribute("contenteditable", "false");
|
|
5565
|
+
}
|
|
5566
|
+
for (const figure of Array.from(body.querySelectorAll("figure"))) {
|
|
5567
|
+
if (figure.classList.contains("fx-table") || figure.querySelector("table")) {
|
|
5568
|
+
figure.classList.add("fx-table");
|
|
5569
|
+
figure.removeAttribute("contenteditable");
|
|
5570
|
+
continue;
|
|
5047
5571
|
}
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5572
|
+
const hasImg = Boolean(figure.querySelector("img"));
|
|
5573
|
+
const hasVideo = Boolean(figure.querySelector("video"));
|
|
5574
|
+
const hasIframe = Boolean(figure.querySelector("iframe"));
|
|
5575
|
+
if (!hasImg && !hasVideo && !hasIframe) {
|
|
5576
|
+
figure.remove();
|
|
5577
|
+
continue;
|
|
5054
5578
|
}
|
|
5055
|
-
|
|
5579
|
+
const classes = new Set((figure.getAttribute("class") ?? "").split(/\s+/).filter(Boolean));
|
|
5580
|
+
if (hasImg) classes.add("fx-image");
|
|
5581
|
+
if (hasVideo) classes.add("fx-video");
|
|
5582
|
+
if (hasIframe) classes.add("fx-embed");
|
|
5583
|
+
figure.setAttribute("class", Array.from(classes).join(" "));
|
|
5584
|
+
if (!figure.hasAttribute("data-align")) figure.setAttribute("data-align", "center");
|
|
5585
|
+
if (options.forEditor) figure.setAttribute("contenteditable", "false");
|
|
5586
|
+
else figure.removeAttribute("contenteditable");
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
function normalizeTables(body) {
|
|
5590
|
+
const doc = body.ownerDocument;
|
|
5591
|
+
for (const table of Array.from(body.querySelectorAll("table"))) {
|
|
5592
|
+
const isGrid = table.classList.contains("fx-grid") || table.getAttribute("role") === "presentation";
|
|
5593
|
+
if (isGrid) {
|
|
5594
|
+
table.classList.add("fx-grid");
|
|
5595
|
+
table.setAttribute("role", "presentation");
|
|
5596
|
+
const firstRow = table.querySelector("tr");
|
|
5597
|
+
const cols = firstRow ? firstRow.children.length : 0;
|
|
5598
|
+
if (cols) table.setAttribute("data-cols", String(cols));
|
|
5599
|
+
} else {
|
|
5600
|
+
table.removeAttribute("role");
|
|
5601
|
+
const parentFigure = table.closest("figure");
|
|
5602
|
+
if (!parentFigure || !parentFigure.classList.contains("fx-table")) {
|
|
5603
|
+
const figure = doc.createElement("figure");
|
|
5604
|
+
figure.className = "fx-table";
|
|
5605
|
+
table.parentNode?.insertBefore(figure, table);
|
|
5606
|
+
figure.appendChild(table);
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
let tbody = Array.from(table.children).find((child) => tagOf(child) === "tbody");
|
|
5610
|
+
const looseRows = Array.from(table.children).filter((child) => tagOf(child) === "tr");
|
|
5611
|
+
if (looseRows.length) {
|
|
5612
|
+
if (!tbody) {
|
|
5613
|
+
tbody = doc.createElement("tbody");
|
|
5614
|
+
table.appendChild(tbody);
|
|
5615
|
+
}
|
|
5616
|
+
for (const row of looseRows) tbody.appendChild(row);
|
|
5617
|
+
}
|
|
5618
|
+
if (!Array.from(table.children).some((child) => tagOf(child) === "colgroup")) {
|
|
5619
|
+
const firstRow = table.querySelector("tr");
|
|
5620
|
+
const count = firstRow ? Array.from(firstRow.children).filter((c) => tagOf(c) === "td" || tagOf(c) === "th").length : 0;
|
|
5621
|
+
if (count > 0) {
|
|
5622
|
+
const colgroup = doc.createElement("colgroup");
|
|
5623
|
+
for (let i = 0; i < count; i++) {
|
|
5624
|
+
const col = doc.createElement("col");
|
|
5625
|
+
col.style.width = `${(100 / count).toFixed(4).replace(/\.?0+$/, "")}%`;
|
|
5626
|
+
colgroup.appendChild(col);
|
|
5627
|
+
}
|
|
5628
|
+
table.insertBefore(colgroup, table.firstChild);
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
for (const th of Array.from(table.querySelectorAll("th"))) {
|
|
5632
|
+
if (!th.getAttribute("scope")) th.setAttribute("scope", "col");
|
|
5633
|
+
}
|
|
5634
|
+
for (const cell of Array.from(table.querySelectorAll("th, td"))) {
|
|
5635
|
+
wrapInlineChildren(cell, doc);
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
5638
|
+
}
|
|
5639
|
+
function normalizeRoot(body, options) {
|
|
5640
|
+
const doc = body.ownerDocument;
|
|
5641
|
+
let buffer = [];
|
|
5642
|
+
const flush = (before) => {
|
|
5643
|
+
if (!buffer.length) return;
|
|
5644
|
+
const p = doc.createElement("p");
|
|
5645
|
+
body.insertBefore(p, before);
|
|
5646
|
+
for (const node of buffer) p.appendChild(node);
|
|
5647
|
+
buffer = [];
|
|
5648
|
+
if (!p.textContent?.trim() && !p.querySelector("img,video,iframe,br")) p.innerHTML = "<br>";
|
|
5649
|
+
};
|
|
5650
|
+
for (const node of Array.from(body.childNodes)) {
|
|
5651
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
5652
|
+
if ((node.nodeValue ?? "").trim()) buffer.push(node);
|
|
5653
|
+
else node.remove();
|
|
5654
|
+
continue;
|
|
5655
|
+
}
|
|
5656
|
+
if (!isElement(node)) {
|
|
5657
|
+
node.remove();
|
|
5658
|
+
continue;
|
|
5659
|
+
}
|
|
5660
|
+
const tag = tagOf(node);
|
|
5661
|
+
if (BLOCK_TAGS.has(tag) || tag === "figure") {
|
|
5662
|
+
flush(node);
|
|
5663
|
+
continue;
|
|
5664
|
+
}
|
|
5665
|
+
if (INLINE_TAGS.has(tag) || tag === "img" || tag === "video" || tag === "iframe") {
|
|
5666
|
+
buffer.push(node);
|
|
5667
|
+
continue;
|
|
5668
|
+
}
|
|
5669
|
+
while (node.firstChild) buffer.push(node.firstChild), body.insertBefore(node.firstChild, node);
|
|
5670
|
+
node.remove();
|
|
5671
|
+
}
|
|
5672
|
+
flush(null);
|
|
5673
|
+
if (options.forEditor && !body.querySelector("p,h1,h2,h3,h4,h5,h6,ul,ol,blockquote,pre,table,figure,hr")) {
|
|
5674
|
+
body.innerHTML = "<p><br></p>";
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
function stripEmpty(body, forEditor) {
|
|
5678
|
+
for (const span of Array.from(body.querySelectorAll("span"))) {
|
|
5679
|
+
if (!span.textContent?.trim() && !span.querySelector("img,video,iframe,br") && !span.hasAttribute("style")) {
|
|
5680
|
+
span.remove();
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
for (const p of Array.from(body.querySelectorAll("p"))) {
|
|
5684
|
+
if (p.querySelector("img,video,iframe,br,table,figure")) continue;
|
|
5685
|
+
if (p.textContent?.trim()) continue;
|
|
5686
|
+
if (forEditor && p === body.lastElementChild) continue;
|
|
5687
|
+
p.remove();
|
|
5688
|
+
}
|
|
5689
|
+
for (const heading of Array.from(body.querySelectorAll("h1,h2,h3,h4,h5,h6,blockquote"))) {
|
|
5690
|
+
if (!heading.textContent?.trim() && !heading.querySelector("img,video,iframe,br")) heading.remove();
|
|
5691
|
+
}
|
|
5692
|
+
for (const figure of Array.from(body.querySelectorAll("figure"))) {
|
|
5693
|
+
if (!figure.querySelector("img,video,iframe,table")) figure.remove();
|
|
5694
|
+
}
|
|
5695
|
+
for (const table of Array.from(body.querySelectorAll("table"))) {
|
|
5696
|
+
if (!table.querySelector("td,th")) table.remove();
|
|
5697
|
+
}
|
|
5698
|
+
if (forEditor && !body.querySelector("p,h1,h2,h3,h4,h5,h6,ul,ol,blockquote,pre,table,figure,hr")) {
|
|
5699
|
+
body.innerHTML = "<p><br></p>";
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
function normalizeElement(body, options = {}) {
|
|
5703
|
+
normalizeChildren(body, options);
|
|
5704
|
+
normalizeFigures(body, options);
|
|
5705
|
+
normalizeTables(body);
|
|
5706
|
+
normalizeChildren(body, options);
|
|
5707
|
+
normalizeRoot(body, options);
|
|
5708
|
+
stripEmpty(body, Boolean(options.forEditor));
|
|
5709
|
+
const result = body.innerHTML.trim();
|
|
5710
|
+
if (!result) return options.forEditor ? "<p><br></p>" : "";
|
|
5711
|
+
return result;
|
|
5712
|
+
}
|
|
5713
|
+
function normalizeHtml(html, options = {}) {
|
|
5714
|
+
if (!html) return options.forEditor ? "<p><br></p>" : "";
|
|
5715
|
+
if (typeof DOMParser === "undefined") return html;
|
|
5716
|
+
const doc = new DOMParser().parseFromString(
|
|
5717
|
+
`<!doctype html><html><body>${html}</body></html>`,
|
|
5718
|
+
"text/html"
|
|
5719
|
+
);
|
|
5720
|
+
return normalizeElement(doc.body, options);
|
|
5721
|
+
}
|
|
5722
|
+
function normalizeForStorage(html) {
|
|
5723
|
+
return normalizeHtml(html, { forEditor: false });
|
|
5724
|
+
}
|
|
5725
|
+
function normalizeForEditor(html) {
|
|
5726
|
+
return normalizeHtml(html, { forEditor: true });
|
|
5727
|
+
}
|
|
5728
|
+
const FX_EDITOR_CONTENT_CSS = `
|
|
5729
|
+
.content { line-height: 1.55; color: var(--fx-text-default); }
|
|
5730
|
+
.content p { margin: 0 0 0.65em; }
|
|
5731
|
+
.content h1, .content h2, .content h3, .content h4, .content h5, .content h6 {
|
|
5732
|
+
margin: 0.8em 0 0.4em; line-height: 1.25; font-weight: 600; color: var(--fx-text-default);
|
|
5733
|
+
}
|
|
5734
|
+
.content h1 { font-size: 1.8em; }
|
|
5735
|
+
.content h2 { font-size: 1.5em; }
|
|
5736
|
+
.content h3 { font-size: 1.25em; }
|
|
5737
|
+
.content h4 { font-size: 1.1em; }
|
|
5738
|
+
.content h5, .content h6 { font-size: 1em; }
|
|
5739
|
+
.content ul, .content ol { margin: 0 0 0.65em; padding-left: 1.6em; }
|
|
5740
|
+
.content li { margin: 0.15em 0; }
|
|
5741
|
+
.content blockquote {
|
|
5742
|
+
margin: 0.5em 0; padding: 0.25em 0 0.25em 1em;
|
|
5743
|
+
border-left: 3px solid var(--fx-color-primary); color: var(--fx-text-muted);
|
|
5744
|
+
}
|
|
5745
|
+
.content pre {
|
|
5746
|
+
margin: 0.6em 0; padding: var(--fx-space-md); overflow: auto;
|
|
5747
|
+
background: var(--fx-surface-surface); border-radius: var(--fx-radius-sm);
|
|
5748
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9em;
|
|
5749
|
+
}
|
|
5750
|
+
.content code {
|
|
5751
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
5752
|
+
font-size: 0.92em; background: var(--fx-surface-surface);
|
|
5753
|
+
padding: 0.1em 0.3em; border-radius: 3px;
|
|
5754
|
+
}
|
|
5755
|
+
.content pre code { background: none; padding: 0; }
|
|
5756
|
+
.content hr { border: none; border-top: 1px solid var(--fx-border-default); margin: 1em 0; }
|
|
5757
|
+
.content a { color: var(--fx-color-primary); text-decoration: underline; cursor: pointer; }
|
|
5758
|
+
.content img { max-width: 100%; height: auto; display: block; }
|
|
5759
|
+
.content figure { margin: 1em 0; }
|
|
5760
|
+
.content figure.fx-image img { margin: 0 auto; }
|
|
5761
|
+
.content figure.fx-image[data-align="left"] { float: left; max-width: 50%; margin: 0 1em 0.5em 0; }
|
|
5762
|
+
.content figure.fx-image[data-align="right"] { float: right; max-width: 50%; margin: 0 0 0.5em 1em; }
|
|
5763
|
+
.content figure.fx-image[data-align="center"] { margin-left: auto; margin-right: auto; }
|
|
5764
|
+
.content figure.fx-video[data-align="left"],
|
|
5765
|
+
.content figure.fx-embed[data-align="left"] { float: left; max-width: 50%; margin: 0 1em 0.5em 0; }
|
|
5766
|
+
.content figure.fx-video[data-align="right"],
|
|
5767
|
+
.content figure.fx-embed[data-align="right"] { float: right; max-width: 50%; margin: 0 0 0.5em 1em; }
|
|
5768
|
+
.content figure.fx-video[data-align="center"],
|
|
5769
|
+
.content figure.fx-embed[data-align="center"] { margin-left: auto; margin-right: auto; }
|
|
5770
|
+
.content figcaption {
|
|
5771
|
+
margin-top: 0.4em; color: var(--fx-text-muted);
|
|
5772
|
+
font-size: 0.85em; text-align: center;
|
|
5773
|
+
}
|
|
5774
|
+
.content video { display: block; width: 100%; background: #000; border-radius: var(--fx-radius-sm); }
|
|
5775
|
+
.content iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
|
|
5776
|
+
.content table { width: 100%; border-collapse: collapse; margin: 1em 0; }
|
|
5777
|
+
.content th, .content td { border: 1px solid var(--fx-border-default); padding: var(--fx-space-xs) var(--fx-space-sm); vertical-align: top; }
|
|
5778
|
+
.content th { background: var(--fx-surface-surface); text-align: left; font-weight: 600; }
|
|
5779
|
+
.content table.fx-grid { border: none; margin: 0.6em 0; }
|
|
5780
|
+
.content table.fx-grid > tbody > tr > td {
|
|
5781
|
+
border: 1px dashed var(--fx-border-default); padding: var(--fx-space-sm);
|
|
5782
|
+
vertical-align: top;
|
|
5783
|
+
}
|
|
5784
|
+
@media (max-width: 640px) {
|
|
5785
|
+
.content table.fx-grid[data-stack] > tbody > tr > td {
|
|
5786
|
+
display: block; width: 100% !important; border: none; padding: var(--fx-space-xs) 0;
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
`;
|
|
5790
|
+
const EDITOR_STYLES = `
|
|
5791
|
+
:host {
|
|
5792
|
+
display: block;
|
|
5793
|
+
width: 100%;
|
|
5794
|
+
font-family: var(--fx-font-family);
|
|
5795
|
+
font-size: var(--fx-font-size);
|
|
5796
|
+
color: var(--fx-text-default);
|
|
5797
|
+
--fx-editor-min-height: 240px;
|
|
5798
|
+
--fx-editor-max-height: none;
|
|
5799
|
+
--fx-editor-radius: var(--fx-radius-md);
|
|
5800
|
+
--fx-editor-toolbar-bg: var(--fx-surface-surface);
|
|
5801
|
+
--fx-editor-content-padding: var(--fx-space-md) var(--fx-space-lg);
|
|
5802
|
+
}
|
|
5803
|
+
:host([full]) { width: 100%; }
|
|
5804
|
+
.editor {
|
|
5805
|
+
display: flex;
|
|
5806
|
+
flex-direction: column;
|
|
5807
|
+
overflow: hidden;
|
|
5808
|
+
background: var(--fx-surface-background);
|
|
5809
|
+
border: 1px solid var(--fx-border-default);
|
|
5810
|
+
border-radius: var(--fx-editor-radius);
|
|
5811
|
+
transition: border-color var(--fx-motion-duration-normal) var(--fx-motion-easing),
|
|
5812
|
+
box-shadow var(--fx-motion-duration-normal) var(--fx-motion-easing);
|
|
5813
|
+
}
|
|
5814
|
+
.editor:focus-within {
|
|
5815
|
+
border-color: var(--fx-color-primary);
|
|
5816
|
+
box-shadow: var(--fx-effect-focus-ring, none);
|
|
5817
|
+
}
|
|
5818
|
+
:host([error]) .editor, :host([invalid]) .editor {
|
|
5819
|
+
border-color: var(--fx-color-danger, #dc2626);
|
|
5820
|
+
box-shadow: var(--fx-effect-error-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-danger, #dc2626) 18%, transparent));
|
|
5821
|
+
}
|
|
5822
|
+
:host([success]) .editor, :host([valid]) .editor {
|
|
5823
|
+
border-color: var(--fx-color-success, #16a34a);
|
|
5824
|
+
box-shadow: var(--fx-effect-success-ring, 0 0 0 3px color-mix(in srgb, var(--fx-color-success, #16a34a) 18%, transparent));
|
|
5825
|
+
}
|
|
5826
|
+
:host([disabled]) { opacity: 0.55; pointer-events: none; }
|
|
5827
|
+
:host([readonly]) .toolbar { display: none; }
|
|
5828
|
+
|
|
5829
|
+
.toolbar {
|
|
5830
|
+
position: relative;
|
|
5831
|
+
display: flex;
|
|
5832
|
+
flex-wrap: wrap;
|
|
5833
|
+
align-items: center;
|
|
5834
|
+
gap: 1px;
|
|
5835
|
+
padding: var(--fx-space-xs);
|
|
5836
|
+
background: var(--fx-editor-toolbar-bg);
|
|
5837
|
+
border-bottom: 1px solid var(--fx-border-default);
|
|
5838
|
+
user-select: none;
|
|
5839
|
+
}
|
|
5840
|
+
.toolbar[hidden] { display: none; }
|
|
5841
|
+
.tb-group {
|
|
5842
|
+
display: flex;
|
|
5843
|
+
align-items: center;
|
|
5844
|
+
gap: 1px;
|
|
5845
|
+
padding: 0 var(--fx-space-xs);
|
|
5846
|
+
border-right: 1px solid var(--fx-border-default);
|
|
5847
|
+
}
|
|
5848
|
+
.tb-group:last-child { border-right: none; }
|
|
5849
|
+
.tb-btn {
|
|
5850
|
+
display: inline-flex;
|
|
5851
|
+
align-items: center;
|
|
5852
|
+
justify-content: center;
|
|
5853
|
+
width: 30px;
|
|
5854
|
+
height: 30px;
|
|
5855
|
+
padding: 0;
|
|
5856
|
+
border: none;
|
|
5857
|
+
border-radius: var(--fx-radius-sm);
|
|
5858
|
+
background: transparent;
|
|
5859
|
+
color: var(--fx-text-default);
|
|
5860
|
+
font-size: 19px;
|
|
5861
|
+
cursor: pointer;
|
|
5862
|
+
transition: background var(--fx-motion-duration-fast) var(--fx-motion-easing);
|
|
5863
|
+
}
|
|
5864
|
+
.tb-btn:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06)); }
|
|
5865
|
+
.tb-btn:focus-visible { outline: 2px solid var(--fx-color-primary); outline-offset: -2px; }
|
|
5866
|
+
.tb-btn.active {
|
|
5867
|
+
background: color-mix(in srgb, var(--fx-color-primary) 14%, transparent);
|
|
5868
|
+
color: var(--fx-color-primary);
|
|
5869
|
+
}
|
|
5870
|
+
.tb-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
5871
|
+
.tb-select {
|
|
5872
|
+
height: 30px;
|
|
5873
|
+
padding: 0 var(--fx-space-xs);
|
|
5874
|
+
border: 1px solid var(--fx-border-default);
|
|
5875
|
+
border-radius: var(--fx-radius-sm);
|
|
5876
|
+
background: var(--fx-surface-background);
|
|
5877
|
+
color: var(--fx-text-default);
|
|
5878
|
+
font-family: inherit;
|
|
5879
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
5880
|
+
cursor: pointer;
|
|
5881
|
+
}
|
|
5882
|
+
.tb-color {
|
|
5883
|
+
width: 30px;
|
|
5884
|
+
height: 30px;
|
|
5885
|
+
padding: 2px;
|
|
5886
|
+
border: none;
|
|
5887
|
+
background: transparent;
|
|
5888
|
+
cursor: pointer;
|
|
5889
|
+
}
|
|
5890
|
+
.tb-color input { width: 26px; height: 26px; padding: 0; border: none; background: none; cursor: pointer; }
|
|
5891
|
+
.body { position: relative; display: flex; flex-direction: column; flex: 1 1 auto; }
|
|
5892
|
+
.content {
|
|
5893
|
+
position: relative;
|
|
5894
|
+
min-height: var(--fx-editor-min-height);
|
|
5895
|
+
max-height: var(--fx-editor-max-height);
|
|
5896
|
+
overflow: auto;
|
|
5897
|
+
padding: var(--fx-editor-content-padding);
|
|
5898
|
+
outline: none;
|
|
5899
|
+
box-sizing: border-box;
|
|
5900
|
+
}
|
|
5901
|
+
.content[hidden] { display: none; }
|
|
5902
|
+
.content.is-empty::before {
|
|
5903
|
+
content: attr(data-placeholder);
|
|
5904
|
+
color: var(--fx-text-muted);
|
|
5905
|
+
pointer-events: none;
|
|
5906
|
+
position: absolute;
|
|
5907
|
+
}
|
|
5908
|
+
.content[contenteditable="false"] { background: var(--fx-surface-surface-hover, rgba(0,0,0,.02)); }
|
|
5909
|
+
.content figure.fx-image { touch-action: none; }
|
|
5910
|
+
|
|
5911
|
+
.media-overlay {
|
|
5912
|
+
position: absolute;
|
|
5913
|
+
z-index: 5;
|
|
5914
|
+
pointer-events: none;
|
|
5915
|
+
border: 1px solid var(--fx-color-primary);
|
|
5916
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--fx-color-primary) 30%, transparent);
|
|
5917
|
+
}
|
|
5918
|
+
.media-overlay[hidden] { display: none; }
|
|
5919
|
+
.media-handle {
|
|
5920
|
+
position: absolute;
|
|
5921
|
+
width: 11px;
|
|
5922
|
+
height: 11px;
|
|
5923
|
+
border: 1px solid #fff;
|
|
5924
|
+
border-radius: 50%;
|
|
5925
|
+
background: var(--fx-color-primary);
|
|
5926
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
|
5927
|
+
pointer-events: auto;
|
|
5928
|
+
touch-action: none;
|
|
5929
|
+
}
|
|
5930
|
+
.media-handle.nw { left: -6px; top: -6px; cursor: nwse-resize; }
|
|
5931
|
+
.media-handle.ne { right: -6px; top: -6px; cursor: nesw-resize; }
|
|
5932
|
+
.media-handle.sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
|
|
5933
|
+
.media-handle.se { right: -6px; bottom: -6px; cursor: nwse-resize; }
|
|
5934
|
+
.media-size {
|
|
5935
|
+
position: absolute;
|
|
5936
|
+
top: -24px;
|
|
5937
|
+
left: 0;
|
|
5938
|
+
padding: 2px 6px;
|
|
5939
|
+
border-radius: var(--fx-radius-sm);
|
|
5940
|
+
background: var(--fx-color-primary);
|
|
5941
|
+
color: #fff;
|
|
5942
|
+
font-size: 11px;
|
|
5943
|
+
line-height: 1.4;
|
|
5944
|
+
white-space: nowrap;
|
|
5945
|
+
}
|
|
5946
|
+
.media-delete {
|
|
5947
|
+
position: absolute;
|
|
5948
|
+
top: -32px;
|
|
5949
|
+
right: -1px;
|
|
5950
|
+
display: inline-flex;
|
|
5951
|
+
align-items: center;
|
|
5952
|
+
justify-content: center;
|
|
5953
|
+
width: 24px;
|
|
5954
|
+
height: 24px;
|
|
5955
|
+
padding: 0;
|
|
5956
|
+
border: none;
|
|
5957
|
+
border-radius: 50%;
|
|
5958
|
+
background: var(--fx-color-danger, #dc2626);
|
|
5959
|
+
color: #fff;
|
|
5960
|
+
font-size: 15px;
|
|
5961
|
+
line-height: 1;
|
|
5962
|
+
cursor: pointer;
|
|
5963
|
+
pointer-events: auto;
|
|
5964
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
|
5965
|
+
}
|
|
5966
|
+
.media-delete:hover { filter: brightness(0.92); }
|
|
5967
|
+
.media-delete:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
|
|
5968
|
+
|
|
5969
|
+
.content figure.fx-image,
|
|
5970
|
+
.content figure.fx-video,
|
|
5971
|
+
.content figure.fx-embed { position: relative; }
|
|
5972
|
+
.fx-media-quick {
|
|
5973
|
+
position: absolute;
|
|
5974
|
+
top: 6px;
|
|
5975
|
+
z-index: 4;
|
|
5976
|
+
display: inline-flex;
|
|
5977
|
+
align-items: center;
|
|
5978
|
+
justify-content: center;
|
|
5979
|
+
width: 24px;
|
|
5980
|
+
height: 24px;
|
|
5981
|
+
padding: 0;
|
|
5982
|
+
border: 1px solid color-mix(in srgb, var(--fx-color-primary) 35%, transparent);
|
|
5983
|
+
border-radius: var(--fx-radius-sm);
|
|
5984
|
+
background: var(--fx-surface-background);
|
|
5985
|
+
color: var(--fx-text-default);
|
|
5986
|
+
cursor: pointer;
|
|
5987
|
+
opacity: 0;
|
|
5988
|
+
transition: opacity var(--fx-motion-duration-fast) var(--fx-motion-easing), background var(--fx-motion-duration-fast) var(--fx-motion-easing);
|
|
5989
|
+
}
|
|
5990
|
+
.content figure:hover > .fx-media-quick,
|
|
5991
|
+
.content figure:focus-within > .fx-media-quick { opacity: 1; }
|
|
5992
|
+
.content figure.fx-video > .fx-media-quick,
|
|
5993
|
+
.content figure.fx-embed > .fx-media-quick { opacity: 0.92; }
|
|
5994
|
+
.fx-media-quick:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06)); }
|
|
5995
|
+
.fx-media-quick.active { background: var(--fx-color-primary); color: #fff; }
|
|
5996
|
+
.fx-media-quick.quick-left { right: 82px; }
|
|
5997
|
+
.fx-media-quick.quick-center { right: 56px; }
|
|
5998
|
+
.fx-media-quick.quick-right { right: 30px; }
|
|
5999
|
+
.fx-media-quick.quick-delete { right: 4px; background: var(--fx-color-danger, #dc2626); color: #fff; border-color: transparent; }
|
|
6000
|
+
.fx-media-quick:focus-visible { outline: 2px solid var(--fx-color-primary); outline-offset: 1px; }
|
|
6001
|
+
|
|
6002
|
+
.link-overlay {
|
|
6003
|
+
position: absolute;
|
|
6004
|
+
z-index: 6;
|
|
6005
|
+
display: flex;
|
|
6006
|
+
gap: 2px;
|
|
6007
|
+
padding: 3px;
|
|
6008
|
+
background: var(--fx-surface-background);
|
|
6009
|
+
border: 1px solid var(--fx-border-default);
|
|
6010
|
+
border-radius: var(--fx-radius-sm);
|
|
6011
|
+
box-shadow: var(--fx-shadow-md, 0 4px 12px rgba(0, 0, 0, 0.16));
|
|
6012
|
+
}
|
|
6013
|
+
.link-overlay[hidden] { display: none; }
|
|
6014
|
+
.link-overlay button {
|
|
6015
|
+
border: none;
|
|
6016
|
+
background: none;
|
|
6017
|
+
color: var(--fx-text-default);
|
|
6018
|
+
font: inherit;
|
|
6019
|
+
font-size: 12px;
|
|
6020
|
+
line-height: 1.4;
|
|
6021
|
+
padding: 4px 8px;
|
|
6022
|
+
border-radius: var(--fx-radius-sm);
|
|
6023
|
+
cursor: pointer;
|
|
6024
|
+
}
|
|
6025
|
+
.link-overlay button:hover { background: var(--fx-surface-surface-hover, rgba(0, 0, 0, 0.06)); }
|
|
6026
|
+
.link-overlay button[data-link-action="remove"] { color: var(--fx-color-danger, #dc2626); }
|
|
6027
|
+
.link-overlay button:focus-visible { outline: 2px solid var(--fx-color-primary); outline-offset: 1px; }
|
|
6028
|
+
|
|
6029
|
+
.source {
|
|
6030
|
+
display: block;
|
|
6031
|
+
width: 100%;
|
|
6032
|
+
min-height: var(--fx-editor-min-height);
|
|
6033
|
+
max-height: var(--fx-editor-max-height);
|
|
6034
|
+
padding: var(--fx-editor-content-padding);
|
|
6035
|
+
border: none;
|
|
6036
|
+
outline: none;
|
|
6037
|
+
resize: vertical;
|
|
6038
|
+
background: var(--fx-surface-background);
|
|
6039
|
+
color: var(--fx-text-default);
|
|
6040
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
6041
|
+
font-size: 13px;
|
|
6042
|
+
line-height: 1.55;
|
|
6043
|
+
box-sizing: border-box;
|
|
6044
|
+
}
|
|
6045
|
+
.source[hidden] { display: none; }
|
|
6046
|
+
|
|
6047
|
+
.statusbar {
|
|
6048
|
+
display: flex;
|
|
6049
|
+
align-items: center;
|
|
6050
|
+
justify-content: space-between;
|
|
6051
|
+
gap: var(--fx-space-sm);
|
|
6052
|
+
min-height: 28px;
|
|
6053
|
+
padding: 2px var(--fx-space-md);
|
|
6054
|
+
border-top: 1px solid var(--fx-border-default);
|
|
6055
|
+
color: var(--fx-text-muted);
|
|
6056
|
+
font-size: calc(var(--fx-font-size) - 3px);
|
|
6057
|
+
}
|
|
6058
|
+
.statusbar:empty { display: none; }
|
|
6059
|
+
.statusbar[hidden] { display: none; }
|
|
6060
|
+
.source-actions { display: inline-flex; gap: var(--fx-space-xs); }
|
|
6061
|
+
.source-actions[hidden] { display: none; }
|
|
6062
|
+
|
|
6063
|
+
.backdrop {
|
|
6064
|
+
position: fixed;
|
|
6065
|
+
inset: 0;
|
|
6066
|
+
z-index: var(--fx-z-modal, 1100);
|
|
6067
|
+
display: flex;
|
|
6068
|
+
align-items: center;
|
|
6069
|
+
justify-content: center;
|
|
6070
|
+
background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);
|
|
6071
|
+
}
|
|
6072
|
+
.backdrop[hidden] { display: none; }
|
|
6073
|
+
.dialog {
|
|
6074
|
+
width: min(460px, calc(100vw - 32px));
|
|
6075
|
+
max-height: calc(100vh - 48px);
|
|
6076
|
+
overflow: auto;
|
|
6077
|
+
background: var(--fx-surface-background);
|
|
6078
|
+
border: 1px solid var(--fx-border-default);
|
|
6079
|
+
border-radius: var(--fx-radius-lg);
|
|
6080
|
+
box-shadow: var(--fx-shadow-xl);
|
|
6081
|
+
font-family: var(--fx-font-family);
|
|
6082
|
+
font-size: var(--fx-font-size);
|
|
6083
|
+
}
|
|
6084
|
+
.dialog header {
|
|
6085
|
+
display: flex;
|
|
6086
|
+
align-items: center;
|
|
6087
|
+
justify-content: space-between;
|
|
6088
|
+
padding: var(--fx-space-md) var(--fx-space-lg);
|
|
6089
|
+
border-bottom: 1px solid var(--fx-border-default);
|
|
6090
|
+
}
|
|
6091
|
+
.dialog h3 { margin: 0; font-size: 1rem; font-weight: 600; }
|
|
6092
|
+
.dialog-close {
|
|
6093
|
+
border: none; background: transparent; color: var(--fx-text-muted);
|
|
6094
|
+
font-size: 1.4rem; line-height: 1; cursor: pointer;
|
|
6095
|
+
}
|
|
6096
|
+
.dialog-body { display: flex; flex-direction: column; gap: var(--fx-space-sm); padding: var(--fx-space-lg); }
|
|
6097
|
+
.field { display: flex; flex-direction: column; gap: 3px; font-size: calc(var(--fx-font-size) - 2px); color: var(--fx-text-muted); }
|
|
6098
|
+
.field input, .field select {
|
|
6099
|
+
height: var(--fx-size-md);
|
|
6100
|
+
padding: 0 var(--fx-space-sm);
|
|
6101
|
+
border: 1px solid var(--fx-border-default);
|
|
6102
|
+
border-radius: var(--fx-radius-sm);
|
|
6103
|
+
background: var(--fx-surface-background);
|
|
6104
|
+
color: var(--fx-text-default);
|
|
6105
|
+
font: inherit;
|
|
6106
|
+
font-size: var(--fx-font-size);
|
|
6107
|
+
}
|
|
6108
|
+
.field input:focus, .field select:focus { outline: none; border-color: var(--fx-color-primary); box-shadow: var(--fx-effect-focus-ring, none); }
|
|
6109
|
+
.dialog footer {
|
|
6110
|
+
display: flex; justify-content: flex-end; gap: var(--fx-space-sm);
|
|
6111
|
+
padding: var(--fx-space-md) var(--fx-space-lg);
|
|
6112
|
+
border-top: 1px solid var(--fx-border-default);
|
|
6113
|
+
}
|
|
6114
|
+
.btn {
|
|
6115
|
+
min-height: var(--fx-size-sm);
|
|
6116
|
+
padding: 0 var(--fx-space-md);
|
|
6117
|
+
border: 1px solid var(--fx-border-default);
|
|
6118
|
+
border-radius: var(--fx-radius-sm);
|
|
6119
|
+
background: var(--fx-surface-background);
|
|
6120
|
+
color: var(--fx-text-default);
|
|
6121
|
+
font: inherit; font-size: calc(var(--fx-font-size) - 2px);
|
|
6122
|
+
cursor: pointer;
|
|
6123
|
+
}
|
|
6124
|
+
.btn:hover { background: var(--fx-surface-surface-hover, rgba(0,0,0,.05)); }
|
|
6125
|
+
.btn.primary { background: var(--fx-color-primary); border-color: transparent; color: #fff; }
|
|
6126
|
+
.btn.primary:hover { filter: brightness(0.92); }
|
|
6127
|
+
|
|
6128
|
+
.panel {
|
|
6129
|
+
position: absolute;
|
|
6130
|
+
top: calc(100% + 4px);
|
|
6131
|
+
left: var(--fx-space-md);
|
|
6132
|
+
z-index: 6;
|
|
6133
|
+
padding: var(--fx-space-sm);
|
|
6134
|
+
background: var(--fx-surface-background);
|
|
6135
|
+
border: 1px solid var(--fx-border-default);
|
|
6136
|
+
border-radius: var(--fx-radius-md);
|
|
6137
|
+
box-shadow: var(--fx-shadow-lg);
|
|
6138
|
+
}
|
|
6139
|
+
.panel[hidden] { display: none; }
|
|
6140
|
+
.grid-picker { display: grid; grid-template-columns: repeat(8, 18px); gap: 3px; }
|
|
6141
|
+
.grid-picker button {
|
|
6142
|
+
width: 18px; height: 18px; padding: 0;
|
|
6143
|
+
border: 1px solid var(--fx-border-default);
|
|
6144
|
+
border-radius: 2px; background: var(--fx-surface-background); cursor: pointer;
|
|
6145
|
+
}
|
|
6146
|
+
.grid-picker button.on { background: var(--fx-color-primary); border-color: var(--fx-color-primary); }
|
|
6147
|
+
.panel-title { margin: 0 0 6px; font-size: 11px; color: var(--fx-text-muted); }
|
|
6148
|
+
.panel-actions { display: flex; flex-wrap: wrap; gap: 4px; max-width: 210px; }
|
|
6149
|
+
.panel-actions .btn { min-height: 26px; padding: 0 6px; font-size: 11px; }
|
|
6150
|
+
|
|
6151
|
+
:host([fullscreen]) {
|
|
6152
|
+
position: fixed;
|
|
6153
|
+
inset: 0;
|
|
6154
|
+
z-index: var(--fx-z-modal, 1100);
|
|
6155
|
+
background: var(--fx-surface-background);
|
|
6156
|
+
}
|
|
6157
|
+
:host([fullscreen]) .editor { height: 100%; border: none; border-radius: 0; }
|
|
6158
|
+
:host([fullscreen]) .content, :host([fullscreen]) .source { flex: 1 1 auto; max-height: none; }
|
|
6159
|
+
|
|
6160
|
+
@media (max-width: 640px) {
|
|
6161
|
+
.toolbar { gap: 0; }
|
|
6162
|
+
.tb-group { padding: 0 1px; }
|
|
6163
|
+
.tb-btn { width: 28px; height: 28px; }
|
|
6164
|
+
}
|
|
6165
|
+
${FX_EDITOR_CONTENT_CSS}
|
|
6166
|
+
|
|
6167
|
+
|
|
6168
|
+
`;
|
|
6169
|
+
const BLOCK_SELECTOR = "p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,td,th,figcaption,div";
|
|
6170
|
+
function rootOf(editor) {
|
|
6171
|
+
const root = editor.getRootNode();
|
|
6172
|
+
return typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot ? root : document;
|
|
6173
|
+
}
|
|
6174
|
+
function isRangeInside(editor, range2) {
|
|
6175
|
+
if (!range2) return false;
|
|
6176
|
+
const start = range2.startContainer;
|
|
6177
|
+
const end = range2.endContainer;
|
|
6178
|
+
return editor.contains(start) && editor.contains(end);
|
|
6179
|
+
}
|
|
6180
|
+
function readSelectionRange(root) {
|
|
6181
|
+
const selection = document.getSelection();
|
|
6182
|
+
if (!selection) return null;
|
|
6183
|
+
if (root instanceof ShadowRoot && selection.getComposedRanges) {
|
|
6184
|
+
try {
|
|
6185
|
+
const ranges = selection.getComposedRanges({ shadowRoots: [root] });
|
|
6186
|
+
const first = ranges[0];
|
|
6187
|
+
if (first) {
|
|
6188
|
+
const range2 = document.createRange();
|
|
6189
|
+
range2.setStart(first.startContainer, first.startOffset);
|
|
6190
|
+
range2.setEnd(first.endContainer, first.endOffset);
|
|
6191
|
+
return range2;
|
|
6192
|
+
}
|
|
6193
|
+
} catch {
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6196
|
+
return selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
|
|
6197
|
+
}
|
|
6198
|
+
function getRange(editor, fallback) {
|
|
6199
|
+
const range2 = readSelectionRange(rootOf(editor));
|
|
6200
|
+
if (isRangeInside(editor, range2)) return range2;
|
|
6201
|
+
if (isRangeInside(editor, fallback ?? null)) return fallback ?? null;
|
|
6202
|
+
return null;
|
|
6203
|
+
}
|
|
6204
|
+
function saveRange(editor) {
|
|
6205
|
+
const range2 = getRange(editor);
|
|
6206
|
+
return range2 ? range2.cloneRange() : null;
|
|
6207
|
+
}
|
|
6208
|
+
function restoreRange(editor, range2) {
|
|
6209
|
+
if (!range2 || !isRangeInside(editor, range2)) return;
|
|
6210
|
+
editor.focus({ preventScroll: true });
|
|
6211
|
+
const selection = document.getSelection();
|
|
6212
|
+
if (!selection) return;
|
|
6213
|
+
selection.removeAllRanges();
|
|
6214
|
+
selection.addRange(range2);
|
|
6215
|
+
}
|
|
6216
|
+
function placeCaretAtEnd(editor) {
|
|
6217
|
+
const range2 = document.createRange();
|
|
6218
|
+
range2.selectNodeContents(editor);
|
|
6219
|
+
range2.collapse(false);
|
|
6220
|
+
const selection = document.getSelection();
|
|
6221
|
+
if (!selection) return;
|
|
6222
|
+
selection.removeAllRanges();
|
|
6223
|
+
selection.addRange(range2);
|
|
6224
|
+
}
|
|
6225
|
+
function getBlock(editor, range2) {
|
|
6226
|
+
const active = range2 ?? getRange(editor);
|
|
6227
|
+
if (!active) return null;
|
|
6228
|
+
const node = active.startContainer;
|
|
6229
|
+
const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
6230
|
+
const block = el?.closest(BLOCK_SELECTOR) ?? null;
|
|
6231
|
+
return block && editor.contains(block) ? block : null;
|
|
6232
|
+
}
|
|
6233
|
+
function getSelectedBlocks(editor, range2) {
|
|
6234
|
+
const active = getRange(editor);
|
|
6235
|
+
if (!active) return [];
|
|
6236
|
+
if (active.collapsed) {
|
|
6237
|
+
const block = getBlock(editor, active);
|
|
6238
|
+
return block ? [block] : [];
|
|
6239
|
+
}
|
|
6240
|
+
return Array.from(editor.querySelectorAll(BLOCK_SELECTOR)).filter((block) => {
|
|
6241
|
+
try {
|
|
6242
|
+
return active.intersectsNode(block);
|
|
6243
|
+
} catch {
|
|
6244
|
+
return false;
|
|
6245
|
+
}
|
|
6246
|
+
});
|
|
6247
|
+
}
|
|
6248
|
+
function closestInEditor(editor, node, selector) {
|
|
6249
|
+
const el = node && (node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement);
|
|
6250
|
+
const match = el?.closest(selector) ?? null;
|
|
6251
|
+
return match && editor.contains(match) ? match : null;
|
|
6252
|
+
}
|
|
6253
|
+
function setSelection(range2) {
|
|
6254
|
+
const selection = document.getSelection();
|
|
6255
|
+
if (!selection) return;
|
|
6256
|
+
selection.removeAllRanges();
|
|
6257
|
+
selection.addRange(range2);
|
|
6258
|
+
}
|
|
6259
|
+
function nativeExec(command, value) {
|
|
6260
|
+
const exec = document.execCommand;
|
|
6261
|
+
if (typeof exec !== "function") return false;
|
|
6262
|
+
try {
|
|
6263
|
+
return exec.call(document, command, false, value);
|
|
6264
|
+
} catch {
|
|
6265
|
+
return false;
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
function insertHtml(editor, html) {
|
|
6269
|
+
if (nativeExec("insertHTML", html)) return true;
|
|
6270
|
+
const range2 = getRange(editor);
|
|
6271
|
+
if (!range2) {
|
|
6272
|
+
editor.insertAdjacentHTML("beforeend", html);
|
|
6273
|
+
placeCaretAtEnd(editor);
|
|
6274
|
+
return true;
|
|
6275
|
+
}
|
|
6276
|
+
range2.deleteContents();
|
|
6277
|
+
const fragment = range2.createContextualFragment(html);
|
|
6278
|
+
const last = fragment.lastChild;
|
|
6279
|
+
range2.insertNode(fragment);
|
|
6280
|
+
if (last) {
|
|
6281
|
+
const next = document.createRange();
|
|
6282
|
+
next.setStartAfter(last);
|
|
6283
|
+
next.collapse(true);
|
|
6284
|
+
setSelection(next);
|
|
6285
|
+
}
|
|
6286
|
+
return true;
|
|
6287
|
+
}
|
|
6288
|
+
function unwrap(el) {
|
|
6289
|
+
const parent = el.parentNode;
|
|
6290
|
+
if (!parent) return;
|
|
6291
|
+
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
6292
|
+
el.remove();
|
|
6293
|
+
}
|
|
6294
|
+
function wrapSelection(editor, tag) {
|
|
6295
|
+
const range2 = getRange(editor);
|
|
6296
|
+
if (!range2) return false;
|
|
6297
|
+
const el = document.createElement(tag);
|
|
6298
|
+
if (range2.collapsed) {
|
|
6299
|
+
el.appendChild(document.createTextNode(""));
|
|
6300
|
+
range2.insertNode(el);
|
|
6301
|
+
const caret = document.createRange();
|
|
6302
|
+
caret.setStart(el.firstChild ?? el, 1);
|
|
6303
|
+
caret.collapse(true);
|
|
6304
|
+
setSelection(caret);
|
|
6305
|
+
return true;
|
|
6306
|
+
}
|
|
6307
|
+
try {
|
|
6308
|
+
range2.surroundContents(el);
|
|
6309
|
+
} catch {
|
|
6310
|
+
el.appendChild(range2.extractContents());
|
|
6311
|
+
range2.insertNode(el);
|
|
6312
|
+
}
|
|
6313
|
+
return true;
|
|
6314
|
+
}
|
|
6315
|
+
function toggleInline(editor, tag) {
|
|
6316
|
+
const nativeMap = {
|
|
6317
|
+
strong: "bold",
|
|
6318
|
+
b: "bold",
|
|
6319
|
+
em: "italic",
|
|
6320
|
+
i: "italic",
|
|
6321
|
+
u: "underline",
|
|
6322
|
+
s: "strikeThrough",
|
|
6323
|
+
strike: "strikeThrough"
|
|
6324
|
+
};
|
|
6325
|
+
const nativeCommand = nativeMap[tag];
|
|
6326
|
+
if (nativeCommand && nativeExec(nativeCommand)) return true;
|
|
6327
|
+
const range2 = getRange(editor);
|
|
6328
|
+
if (!range2) return false;
|
|
6329
|
+
const current = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest(tag);
|
|
6330
|
+
if (current && editor.contains(current)) {
|
|
6331
|
+
unwrap(current);
|
|
6332
|
+
return true;
|
|
6333
|
+
}
|
|
6334
|
+
return wrapSelection(editor, tag);
|
|
6335
|
+
}
|
|
6336
|
+
function formatBlock(editor, tag) {
|
|
6337
|
+
const normalized = tag.toLowerCase().replace(/[<>]/g, "");
|
|
6338
|
+
if (nativeExec("formatBlock", normalized)) return true;
|
|
6339
|
+
const blocks = getSelectedBlocks(editor);
|
|
6340
|
+
if (!blocks.length) {
|
|
6341
|
+
const block = getBlock(editor);
|
|
6342
|
+
if (block) blocks.push(block);
|
|
6343
|
+
}
|
|
6344
|
+
for (const block of blocks) {
|
|
6345
|
+
if (!editor.contains(block)) continue;
|
|
6346
|
+
const next = document.createElement(normalized);
|
|
6347
|
+
for (const attr of Array.from(block.attributes)) next.setAttribute(attr.name, attr.value);
|
|
6348
|
+
while (block.firstChild) next.appendChild(block.firstChild);
|
|
6349
|
+
block.replaceWith(next);
|
|
6350
|
+
}
|
|
6351
|
+
return blocks.length > 0;
|
|
6352
|
+
}
|
|
6353
|
+
function align(editor, value) {
|
|
6354
|
+
const nativeMap = {
|
|
6355
|
+
left: "justifyLeft",
|
|
6356
|
+
center: "justifyCenter",
|
|
6357
|
+
right: "justifyRight",
|
|
6358
|
+
justify: "justifyFull"
|
|
6359
|
+
};
|
|
6360
|
+
if (nativeExec(nativeMap[value])) return true;
|
|
6361
|
+
const blocks = getSelectedBlocks(editor);
|
|
6362
|
+
for (const block of blocks) block.style.textAlign = value;
|
|
6363
|
+
return blocks.length > 0;
|
|
6364
|
+
}
|
|
6365
|
+
function toggleList(editor, ordered) {
|
|
6366
|
+
if (nativeExec(ordered ? "insertOrderedList" : "insertUnorderedList")) return true;
|
|
6367
|
+
const blocks = getSelectedBlocks(editor);
|
|
6368
|
+
if (!blocks.length) return false;
|
|
6369
|
+
const list = document.createElement(ordered ? "ol" : "ul");
|
|
6370
|
+
const first = blocks[0];
|
|
6371
|
+
first.parentNode?.insertBefore(list, first);
|
|
6372
|
+
for (const block of blocks) {
|
|
6373
|
+
const li = document.createElement("li");
|
|
6374
|
+
while (block.firstChild) li.appendChild(block.firstChild);
|
|
6375
|
+
list.appendChild(li);
|
|
6376
|
+
block.remove();
|
|
6377
|
+
}
|
|
6378
|
+
const range2 = document.createRange();
|
|
6379
|
+
range2.selectNodeContents(list.lastElementChild ?? list);
|
|
6380
|
+
range2.collapse(false);
|
|
6381
|
+
setSelection(range2);
|
|
6382
|
+
return true;
|
|
6383
|
+
}
|
|
6384
|
+
function removeFormat(editor) {
|
|
6385
|
+
if (nativeExec("removeFormat")) return true;
|
|
6386
|
+
const range2 = getRange(editor);
|
|
6387
|
+
if (!range2) return false;
|
|
6388
|
+
const root = range2.commonAncestorContainer;
|
|
6389
|
+
const container = root.nodeType === Node.ELEMENT_NODE ? root : root.parentElement;
|
|
6390
|
+
if (!container) return false;
|
|
6391
|
+
const tags = ["strong", "b", "em", "i", "u", "s", "strike", "span", "mark", "code", "sub", "sup"];
|
|
6392
|
+
for (const el of Array.from(container.querySelectorAll(tags.join(",")))) {
|
|
6393
|
+
if (range2.intersectsNode(el)) unwrap(el);
|
|
6394
|
+
}
|
|
6395
|
+
return true;
|
|
6396
|
+
}
|
|
6397
|
+
const linkHtml = (href, label, target) => `<a href="${escapeAttr(href)}" target="${target}" rel="noopener noreferrer">${escapeText(label)}</a>`;
|
|
6398
|
+
function applyLink(editor, href, text, target = "_blank") {
|
|
6399
|
+
const label = text?.trim() || href;
|
|
6400
|
+
const range2 = getRange(editor);
|
|
6401
|
+
if (!range2) return insertHtml(editor, linkHtml(href, label, target));
|
|
6402
|
+
const anchor = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest("a");
|
|
6403
|
+
if (anchor && editor.contains(anchor)) {
|
|
6404
|
+
anchor.setAttribute("href", href);
|
|
6405
|
+
anchor.setAttribute("target", target);
|
|
6406
|
+
anchor.setAttribute("rel", "noopener noreferrer");
|
|
6407
|
+
if (text && text !== anchor.textContent) anchor.textContent = text;
|
|
6408
|
+
return true;
|
|
6409
|
+
}
|
|
6410
|
+
if (!range2.collapsed) {
|
|
6411
|
+
if (nativeExec("createLink", href)) {
|
|
6412
|
+
const created = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest("a");
|
|
6413
|
+
created?.setAttribute("target", target);
|
|
6414
|
+
created?.setAttribute("rel", "noopener noreferrer");
|
|
6415
|
+
return true;
|
|
6416
|
+
}
|
|
6417
|
+
const el = document.createElement("a");
|
|
6418
|
+
el.setAttribute("href", href);
|
|
6419
|
+
el.setAttribute("target", target);
|
|
6420
|
+
el.setAttribute("rel", "noopener noreferrer");
|
|
6421
|
+
try {
|
|
6422
|
+
range2.surroundContents(el);
|
|
6423
|
+
} catch {
|
|
6424
|
+
el.appendChild(range2.extractContents());
|
|
6425
|
+
range2.insertNode(el);
|
|
6426
|
+
}
|
|
6427
|
+
return true;
|
|
6428
|
+
}
|
|
6429
|
+
return insertHtml(editor, linkHtml(href, label, target));
|
|
6430
|
+
}
|
|
6431
|
+
function unlink(editor) {
|
|
6432
|
+
if (nativeExec("unlink")) return true;
|
|
6433
|
+
const range2 = getRange(editor);
|
|
6434
|
+
if (!range2) return false;
|
|
6435
|
+
const anchor = (range2.startContainer.nodeType === Node.ELEMENT_NODE ? range2.startContainer : range2.startContainer.parentElement)?.closest("a");
|
|
6436
|
+
if (anchor && editor.contains(anchor)) {
|
|
6437
|
+
unwrap(anchor);
|
|
6438
|
+
return true;
|
|
6439
|
+
}
|
|
6440
|
+
return false;
|
|
6441
|
+
}
|
|
6442
|
+
function indent(editor, delta) {
|
|
6443
|
+
if (delta > 0 && nativeExec("indent")) return true;
|
|
6444
|
+
if (delta < 0 && nativeExec("outdent")) return true;
|
|
6445
|
+
const blocks = getSelectedBlocks(editor);
|
|
6446
|
+
for (const block of blocks) {
|
|
6447
|
+
const current = Number.parseInt(block.style.marginLeft || "0", 10) || 0;
|
|
6448
|
+
const next = Math.max(0, current + delta);
|
|
6449
|
+
block.style.marginLeft = next ? `${next}px` : "";
|
|
6450
|
+
}
|
|
6451
|
+
return blocks.length > 0;
|
|
6452
|
+
}
|
|
6453
|
+
function escapeAttr(value) {
|
|
6454
|
+
return value.replace(/[&<>"']/g, (ch) => {
|
|
6455
|
+
switch (ch) {
|
|
6456
|
+
case "&":
|
|
6457
|
+
return "&";
|
|
6458
|
+
case "<":
|
|
6459
|
+
return "<";
|
|
6460
|
+
case ">":
|
|
6461
|
+
return ">";
|
|
6462
|
+
case '"':
|
|
6463
|
+
return """;
|
|
6464
|
+
default:
|
|
6465
|
+
return "'";
|
|
6466
|
+
}
|
|
6467
|
+
});
|
|
6468
|
+
}
|
|
6469
|
+
function escapeText(value) {
|
|
6470
|
+
return value.replace(/[&<>]/g, (ch) => ch === "&" ? "&" : ch === "<" ? "<" : ">");
|
|
6471
|
+
}
|
|
6472
|
+
function tableMatrix(table) {
|
|
6473
|
+
const rows = Array.from(table.rows);
|
|
6474
|
+
const matrix = rows.map(() => []);
|
|
6475
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
6476
|
+
const row = rows[rowIndex];
|
|
6477
|
+
for (const cell of Array.from(row.cells)) {
|
|
6478
|
+
let col = 0;
|
|
6479
|
+
while (matrix[rowIndex][col]) col++;
|
|
6480
|
+
const rowspan = Math.max(1, Math.min(cell.rowSpan || 1, rows.length - rowIndex));
|
|
6481
|
+
const colspan = Math.max(1, cell.colSpan || 1);
|
|
6482
|
+
for (let r = rowIndex; r < rowIndex + rowspan; r++) {
|
|
6483
|
+
if (!matrix[r]) matrix[r] = [];
|
|
6484
|
+
for (let c = col; c < col + colspan; c++) matrix[r][c] = cell;
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
const width = matrix.reduce((max, row) => Math.max(max, row.length), 0);
|
|
6489
|
+
for (const row of matrix) {
|
|
6490
|
+
while (row.length < width) row.push(null);
|
|
6491
|
+
}
|
|
6492
|
+
return { rows, matrix };
|
|
6493
|
+
}
|
|
6494
|
+
function cellPosition(table, cell) {
|
|
6495
|
+
const { matrix } = tableMatrix(table);
|
|
6496
|
+
for (let row = 0; row < matrix.length; row++) {
|
|
6497
|
+
for (let col = 0; col < matrix[row].length; col++) {
|
|
6498
|
+
if (matrix[row][col] === cell) return { row, col };
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
return null;
|
|
6502
|
+
}
|
|
6503
|
+
function selectedCells(table, range2) {
|
|
6504
|
+
return Array.from(table.querySelectorAll("td,th")).filter((cell) => {
|
|
6505
|
+
try {
|
|
6506
|
+
return range2.intersectsNode(cell);
|
|
6507
|
+
} catch {
|
|
6508
|
+
return false;
|
|
6509
|
+
}
|
|
6510
|
+
});
|
|
6511
|
+
}
|
|
6512
|
+
function copyCellAttributes(from, to) {
|
|
6513
|
+
for (const attr of Array.from(from.attributes)) {
|
|
6514
|
+
if (attr.name === "colspan" || attr.name === "rowspan") continue;
|
|
6515
|
+
to.setAttribute(attr.name, attr.value);
|
|
6516
|
+
}
|
|
6517
|
+
}
|
|
6518
|
+
function mergeSelectedCells(table, range2) {
|
|
6519
|
+
const cells = selectedCells(table, range2);
|
|
6520
|
+
if (cells.length < 2) return null;
|
|
6521
|
+
const { matrix } = tableMatrix(table);
|
|
6522
|
+
const positions = cells.map((cell) => ({ cell, position: cellPosition(table, cell) })).filter((item) => Boolean(item.position));
|
|
6523
|
+
if (positions.length !== cells.length) return null;
|
|
6524
|
+
const minRow = Math.min(...positions.map((item) => item.position.row));
|
|
6525
|
+
const maxRow = Math.max(...positions.map((item) => item.position.row));
|
|
6526
|
+
const minCol = Math.min(...positions.map((item) => item.position.col));
|
|
6527
|
+
const maxCol = Math.max(...positions.map((item) => item.position.col));
|
|
6528
|
+
for (const { cell, position } of positions) {
|
|
6529
|
+
if (position.row < minRow || position.col < minCol) return null;
|
|
6530
|
+
if (position.row + Math.max(1, cell.rowSpan || 1) - 1 > maxRow) return null;
|
|
6531
|
+
if (position.col + Math.max(1, cell.colSpan || 1) - 1 > maxCol) return null;
|
|
6532
|
+
}
|
|
6533
|
+
const selected = new Set(cells);
|
|
6534
|
+
for (let row = minRow; row <= maxRow; row++) {
|
|
6535
|
+
for (let col = minCol; col <= maxCol; col++) {
|
|
6536
|
+
const cell = matrix[row]?.[col];
|
|
6537
|
+
if (!cell || !selected.has(cell)) return null;
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
const anchor = matrix[minRow]?.[minCol];
|
|
6541
|
+
if (!anchor) return null;
|
|
6542
|
+
const tag = cells.every((cell) => cell.tagName.toLowerCase() === "th") ? "th" : "td";
|
|
6543
|
+
const merged = document.createElement(tag);
|
|
6544
|
+
copyCellAttributes(anchor, merged);
|
|
6545
|
+
if (tag === "td") merged.removeAttribute("scope");
|
|
6546
|
+
merged.colSpan = maxCol - minCol + 1;
|
|
6547
|
+
merged.rowSpan = maxRow - minRow + 1;
|
|
6548
|
+
if (merged.colSpan === 1) merged.removeAttribute("colspan");
|
|
6549
|
+
if (merged.rowSpan === 1) merged.removeAttribute("rowspan");
|
|
6550
|
+
for (const cell of cells) {
|
|
6551
|
+
while (cell.firstChild) merged.appendChild(cell.firstChild);
|
|
6552
|
+
}
|
|
6553
|
+
anchor.replaceWith(merged);
|
|
6554
|
+
for (const cell of cells) {
|
|
6555
|
+
if (cell !== anchor && cell.parentNode) cell.remove();
|
|
6556
|
+
}
|
|
6557
|
+
return merged;
|
|
6558
|
+
}
|
|
6559
|
+
function insertCellAtColumn(table, row, col, cell) {
|
|
6560
|
+
const rowIndex = Array.from(table.rows).indexOf(row);
|
|
6561
|
+
const { matrix } = tableMatrix(table);
|
|
6562
|
+
let before = null;
|
|
6563
|
+
for (const existing of Array.from(row.cells)) {
|
|
6564
|
+
let start = -1;
|
|
6565
|
+
for (let c = 0; c < (matrix[rowIndex]?.length ?? 0); c++) {
|
|
6566
|
+
if (matrix[rowIndex][c] === existing) {
|
|
6567
|
+
start = c;
|
|
6568
|
+
break;
|
|
6569
|
+
}
|
|
6570
|
+
}
|
|
6571
|
+
if (start >= col) {
|
|
6572
|
+
before = existing;
|
|
6573
|
+
break;
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
row.insertBefore(cell, before);
|
|
6577
|
+
}
|
|
6578
|
+
function splitCell(table, cell) {
|
|
6579
|
+
const colspan = Math.max(1, cell.colSpan || 1);
|
|
6580
|
+
const rowspan = Math.max(1, cell.rowSpan || 1);
|
|
6581
|
+
if (colspan === 1 && rowspan === 1) return [];
|
|
6582
|
+
const position = cellPosition(table, cell);
|
|
6583
|
+
if (!position) return [];
|
|
6584
|
+
const rows = Array.from(table.rows);
|
|
6585
|
+
const makeCell = () => {
|
|
6586
|
+
const next = document.createElement(cell.tagName.toLowerCase());
|
|
6587
|
+
if (next.tagName.toLowerCase() === "th") {
|
|
6588
|
+
next.setAttribute("scope", cell.getAttribute("scope") ?? "col");
|
|
6589
|
+
}
|
|
6590
|
+
next.innerHTML = "<p><br></p>";
|
|
6591
|
+
return next;
|
|
6592
|
+
};
|
|
6593
|
+
const created = [];
|
|
6594
|
+
let last = cell;
|
|
6595
|
+
for (let index = 1; index < colspan; index++) {
|
|
6596
|
+
const next = makeCell();
|
|
6597
|
+
last.after(next);
|
|
6598
|
+
last = next;
|
|
6599
|
+
created.push(next);
|
|
6600
|
+
}
|
|
6601
|
+
cell.removeAttribute("colspan");
|
|
6602
|
+
cell.removeAttribute("rowspan");
|
|
6603
|
+
for (let index = 1; index < rowspan; index++) {
|
|
6604
|
+
const row = rows[position.row + index];
|
|
6605
|
+
if (!row) break;
|
|
6606
|
+
for (let offset = 0; offset < colspan; offset++) {
|
|
6607
|
+
const next = makeCell();
|
|
6608
|
+
insertCellAtColumn(table, row, position.col + offset, next);
|
|
6609
|
+
created.push(next);
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
return created;
|
|
6613
|
+
}
|
|
6614
|
+
const MAX_PASTE_BYTES = 1024 * 1024;
|
|
6615
|
+
const MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
6616
|
+
const LARGE_DOC_CHARS = 2e4;
|
|
6617
|
+
const EMIT_DEBOUNCE_MS = 140;
|
|
6618
|
+
const HISTORY_LIMIT = 50;
|
|
6619
|
+
const QUICK_SVG = {
|
|
6620
|
+
left: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 10h10M4 14h16M4 18h10"/></svg>',
|
|
6621
|
+
center: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M7 10h10M4 14h16M7 18h10"/></svg>',
|
|
6622
|
+
right: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M10 10h10M4 14h16M10 18h10"/></svg>',
|
|
6623
|
+
delete: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M6 6l1 15h10l1-15M10 10v7M14 10v7"/></svg>'
|
|
6624
|
+
};
|
|
6625
|
+
const _FxEditor = class _FxEditor extends FxFormElement {
|
|
6626
|
+
constructor() {
|
|
6627
|
+
super(...arguments);
|
|
6628
|
+
__privateAdd(this, _FxEditor_instances);
|
|
6629
|
+
__privateAdd(this, _built, false);
|
|
6630
|
+
__privateAdd(this, _eventsBound, false);
|
|
6631
|
+
__privateAdd(this, _initialized, false);
|
|
6632
|
+
__privateAdd(this, _defaultValue, "");
|
|
6633
|
+
__privateAdd(this, _value, "");
|
|
6634
|
+
__privateAdd(this, _dirty, false);
|
|
6635
|
+
__privateAdd(this, _sourceMode, false);
|
|
6636
|
+
__privateAdd(this, _lastRange, null);
|
|
6637
|
+
__privateAdd(this, _rafId, 0);
|
|
6638
|
+
__privateAdd(this, _emitTimer, null);
|
|
6639
|
+
__privateAdd(this, _lastLimitError, 0);
|
|
6640
|
+
__privateAdd(this, _snapshotTimer, null);
|
|
6641
|
+
__privateAdd(this, _history, []);
|
|
6642
|
+
__privateAdd(this, _historyIndex, -1);
|
|
6643
|
+
__privateAdd(this, _imagePicker, null);
|
|
6644
|
+
__privateAdd(this, _mediaFigure, null);
|
|
6645
|
+
__privateAdd(this, _linkAnchor, null);
|
|
6646
|
+
__privateAdd(this, _resizeDispose, null);
|
|
6647
|
+
__privateAdd(this, _dialogReturnFocus, null);
|
|
6648
|
+
__privateAdd(this, _dialogResolve, null);
|
|
6649
|
+
__privateAdd(this, _disposers, []);
|
|
6650
|
+
__privateAdd(this, _selectionRaf, 0);
|
|
6651
|
+
__privateAdd(this, _bodyOverflow, "");
|
|
6652
|
+
}
|
|
6653
|
+
static get observedAttributes() {
|
|
6654
|
+
return [
|
|
6655
|
+
"toolbar",
|
|
6656
|
+
"features",
|
|
6657
|
+
"placeholder",
|
|
6658
|
+
"readonly",
|
|
6659
|
+
"disabled",
|
|
6660
|
+
"required",
|
|
6661
|
+
"full",
|
|
6662
|
+
"error",
|
|
6663
|
+
"invalid",
|
|
6664
|
+
"success",
|
|
6665
|
+
"valid",
|
|
6666
|
+
"show-count",
|
|
6667
|
+
"paste-mode",
|
|
6668
|
+
"min-height",
|
|
6669
|
+
"max-height",
|
|
6670
|
+
"fullscreen",
|
|
6671
|
+
"maxlength"
|
|
6672
|
+
];
|
|
6673
|
+
}
|
|
6674
|
+
get editor() {
|
|
6675
|
+
return this.root.querySelector(".content");
|
|
6676
|
+
}
|
|
6677
|
+
get source() {
|
|
6678
|
+
return this.root.querySelector(".source");
|
|
6679
|
+
}
|
|
6680
|
+
get toolbarEl() {
|
|
6681
|
+
return this.root.querySelector(".toolbar");
|
|
6682
|
+
}
|
|
6683
|
+
get value() {
|
|
6684
|
+
if (__privateGet(this, _sourceMode) && this.source) return __privateMethod(this, _FxEditor_instances, serialize_fn).call(this);
|
|
6685
|
+
if (__privateGet(this, _dirty)) __privateSet(this, _value, __privateMethod(this, _FxEditor_instances, serialize_fn).call(this));
|
|
6686
|
+
return __privateGet(this, _value);
|
|
6687
|
+
}
|
|
6688
|
+
set value(next) {
|
|
6689
|
+
const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, next ?? "");
|
|
6690
|
+
__privateSet(this, _value, clean);
|
|
6691
|
+
__privateSet(this, _dirty, false);
|
|
6692
|
+
if (!__privateGet(this, _defaultValue)) __privateSet(this, _defaultValue, clean);
|
|
6693
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, clean);
|
|
6694
|
+
this.updateFormValue();
|
|
6695
|
+
}
|
|
6696
|
+
get html() {
|
|
6697
|
+
return this.value;
|
|
6698
|
+
}
|
|
6699
|
+
set html(next) {
|
|
6700
|
+
this.value = next;
|
|
6701
|
+
}
|
|
6702
|
+
get text() {
|
|
6703
|
+
return (this.editor?.textContent ?? "").replace(/\u200b/g, "").trim();
|
|
6704
|
+
}
|
|
6705
|
+
get defaultValue() {
|
|
6706
|
+
return __privateGet(this, _defaultValue);
|
|
6707
|
+
}
|
|
6708
|
+
set defaultValue(next) {
|
|
6709
|
+
__privateSet(this, _defaultValue, __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, next ?? ""));
|
|
6710
|
+
if (!__privateGet(this, _dirty)) this.value = __privateGet(this, _defaultValue);
|
|
6711
|
+
}
|
|
6712
|
+
get disabled() {
|
|
6713
|
+
return this.hasAttr("disabled");
|
|
6714
|
+
}
|
|
6715
|
+
set disabled(value) {
|
|
6716
|
+
this.toggleAttr("disabled", Boolean(value));
|
|
6717
|
+
}
|
|
6718
|
+
get readonly() {
|
|
6719
|
+
return this.hasAttr("readonly");
|
|
6720
|
+
}
|
|
6721
|
+
set readonly(value) {
|
|
6722
|
+
this.toggleAttr("readonly", Boolean(value));
|
|
6723
|
+
}
|
|
6724
|
+
get required() {
|
|
6725
|
+
return this.hasAttr("required");
|
|
6726
|
+
}
|
|
6727
|
+
set required(value) {
|
|
6728
|
+
this.toggleAttr("required", Boolean(value));
|
|
6729
|
+
}
|
|
6730
|
+
get placeholder() {
|
|
6731
|
+
return this.getAttr("placeholder");
|
|
6732
|
+
}
|
|
6733
|
+
set placeholder(value) {
|
|
6734
|
+
this.setAttribute("placeholder", value);
|
|
6735
|
+
}
|
|
6736
|
+
get toolbar() {
|
|
6737
|
+
const value = this.getAttr("toolbar", "standard");
|
|
6738
|
+
return ["minimal", "basic", "standard", "full", "none"].includes(value) ? value : "standard";
|
|
6739
|
+
}
|
|
6740
|
+
set toolbar(value) {
|
|
6741
|
+
this.setAttribute("toolbar", value);
|
|
6742
|
+
}
|
|
6743
|
+
get pasteMode() {
|
|
6744
|
+
return this.getAttr("paste-mode", "clean") === "plain" ? "plain" : "clean";
|
|
6745
|
+
}
|
|
6746
|
+
set pasteMode(value) {
|
|
6747
|
+
this.setAttribute("paste-mode", value);
|
|
6748
|
+
}
|
|
6749
|
+
get fullscreen() {
|
|
6750
|
+
return this.hasAttr("fullscreen");
|
|
6751
|
+
}
|
|
6752
|
+
set fullscreen(value) {
|
|
6753
|
+
this.toggleAttr("fullscreen", Boolean(value));
|
|
6754
|
+
}
|
|
6755
|
+
/** Limite de caracteres de texto (0 desativa). Padrão: 30.000. */
|
|
6756
|
+
get maxlength() {
|
|
6757
|
+
const value = Number.parseInt(this.getAttr("maxlength", "30000"), 10);
|
|
6758
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
6759
|
+
}
|
|
6760
|
+
set maxlength(value) {
|
|
6761
|
+
if (Number.isFinite(value) && value >= 0) this.setAttribute("maxlength", String(Math.floor(value)));
|
|
6762
|
+
else this.removeAttribute("maxlength");
|
|
6763
|
+
}
|
|
6764
|
+
connectedCallback() {
|
|
6765
|
+
loadFenixIconsFont();
|
|
6766
|
+
super.connectedCallback();
|
|
6767
|
+
if (this.fullscreen) __privateMethod(this, _FxEditor_instances, syncBodyOverflow_fn).call(this);
|
|
6768
|
+
if (!__privateGet(this, _initialized)) {
|
|
6769
|
+
__privateSet(this, _initialized, true);
|
|
6770
|
+
const initial = __privateGet(this, _value) || this.getAttr("value") || this.innerHTML;
|
|
6771
|
+
this.replaceChildren();
|
|
6772
|
+
__privateSet(this, _defaultValue, __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, initial));
|
|
6773
|
+
__privateSet(this, _value, __privateGet(this, _defaultValue));
|
|
6774
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _defaultValue));
|
|
6775
|
+
this.updateFormValue();
|
|
6776
|
+
__privateMethod(this, _FxEditor_instances, pushHistory_fn).call(this, __privateGet(this, _value));
|
|
6777
|
+
this.dispatchEvent(new CustomEvent("ready", { bubbles: true, composed: true }));
|
|
6778
|
+
} else if (__privateGet(this, _built) && !__privateGet(this, _eventsBound)) {
|
|
6779
|
+
__privateMethod(this, _FxEditor_instances, bindEvents_fn).call(this);
|
|
6780
|
+
__privateSet(this, _eventsBound, true);
|
|
6781
|
+
}
|
|
6782
|
+
__privateMethod(this, _FxEditor_instances, bindGlobalSelection_fn).call(this);
|
|
6783
|
+
}
|
|
6784
|
+
disconnectedCallback() {
|
|
6785
|
+
for (const dispose of __privateGet(this, _disposers)) dispose();
|
|
6786
|
+
__privateSet(this, _disposers, []);
|
|
6787
|
+
__privateSet(this, _eventsBound, false);
|
|
6788
|
+
cancelAnimationFrame(__privateGet(this, _rafId));
|
|
6789
|
+
cancelAnimationFrame(__privateGet(this, _selectionRaf));
|
|
6790
|
+
if (__privateGet(this, _emitTimer)) clearTimeout(__privateGet(this, _emitTimer));
|
|
6791
|
+
if (__privateGet(this, _snapshotTimer)) clearTimeout(__privateGet(this, _snapshotTimer));
|
|
6792
|
+
__privateMethod(this, _FxEditor_instances, endResize_fn).call(this);
|
|
6793
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
6794
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
6795
|
+
if (this.hasAttr("fullscreen")) document.body.style.overflow = __privateGet(this, _bodyOverflow);
|
|
6796
|
+
__privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
|
|
6797
|
+
super.disconnectedCallback();
|
|
6798
|
+
}
|
|
6799
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
6800
|
+
if (oldValue === newValue || !__privateGet(this, _built)) return;
|
|
6801
|
+
if (name === "toolbar" || name === "features") __privateMethod(this, _FxEditor_instances, renderToolbar_fn).call(this);
|
|
6802
|
+
if (name === "fullscreen") __privateMethod(this, _FxEditor_instances, syncBodyOverflow_fn).call(this);
|
|
6803
|
+
__privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
|
|
6804
|
+
}
|
|
6805
|
+
formResetCallback() {
|
|
6806
|
+
__privateSet(this, _dirty, false);
|
|
6807
|
+
this.value = __privateGet(this, _defaultValue);
|
|
6808
|
+
}
|
|
6809
|
+
getFormValue() {
|
|
6810
|
+
return this.hasAttr("disabled") ? null : this.value;
|
|
6811
|
+
}
|
|
6812
|
+
render() {
|
|
6813
|
+
if (__privateGet(this, _built)) return;
|
|
6814
|
+
this.setTemplate(`
|
|
6815
|
+
<div class="editor" part="editor">
|
|
6816
|
+
<div class="toolbar" part="toolbar" role="toolbar" aria-label="Formatação" aria-orientation="horizontal"></div>
|
|
6817
|
+
<div class="body">
|
|
6818
|
+
<div class="content" part="content" contenteditable="true" role="textbox"
|
|
6819
|
+
aria-multiline="true" spellcheck="true"></div>
|
|
6820
|
+
<textarea class="source" part="source" spellcheck="false" hidden aria-label="HTML do conteúdo"></textarea>
|
|
6821
|
+
<div class="media-overlay" part="media-overlay" hidden>
|
|
6822
|
+
<span class="media-handle nw" data-handle="nw"></span>
|
|
6823
|
+
<span class="media-handle ne" data-handle="ne"></span>
|
|
6824
|
+
<span class="media-handle sw" data-handle="sw"></span>
|
|
6825
|
+
<span class="media-handle se" data-handle="se"></span>
|
|
6826
|
+
<span class="media-size"></span>
|
|
6827
|
+
<button type="button" class="media-delete" data-media-action="delete" aria-label="Excluir mídia" title="Excluir mídia">${fenixIconHtml("delete")}</button>
|
|
6828
|
+
</div>
|
|
6829
|
+
<div class="link-overlay" part="link-overlay" hidden>
|
|
6830
|
+
<button type="button" data-link-action="open">Abrir</button>
|
|
6831
|
+
<button type="button" data-link-action="edit">Editar</button>
|
|
6832
|
+
<button type="button" data-link-action="remove">Remover</button>
|
|
6833
|
+
</div>
|
|
6834
|
+
</div>
|
|
6835
|
+
<div class="statusbar" part="statusbar">
|
|
6836
|
+
<span class="count"></span>
|
|
6837
|
+
<span class="source-actions" hidden>
|
|
6838
|
+
<button type="button" class="btn source-apply">Aplicar</button>
|
|
6839
|
+
<button type="button" class="btn source-cancel">Cancelar</button>
|
|
6840
|
+
</span>
|
|
6841
|
+
<slot name="footer"></slot>
|
|
6842
|
+
</div>
|
|
6843
|
+
<input type="file" class="image-picker" accept="image/*" hidden>
|
|
6844
|
+
<div class="backdrop" part="dialog" hidden>
|
|
6845
|
+
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="fx-editor-dialog-title">
|
|
6846
|
+
<header>
|
|
6847
|
+
<h3 id="fx-editor-dialog-title"></h3>
|
|
6848
|
+
<button type="button" class="dialog-close" aria-label="Fechar">×</button>
|
|
6849
|
+
</header>
|
|
6850
|
+
<div class="dialog-body"></div>
|
|
6851
|
+
<footer>
|
|
6852
|
+
<button type="button" class="btn dialog-cancel">Cancelar</button>
|
|
6853
|
+
<button type="button" class="btn dialog-ok">Aplicar</button>
|
|
6854
|
+
</footer>
|
|
6855
|
+
</div>
|
|
6856
|
+
</div>
|
|
6857
|
+
</div>
|
|
6858
|
+
`);
|
|
6859
|
+
__privateSet(this, _built, true);
|
|
6860
|
+
__privateSet(this, _imagePicker, this.root.querySelector(".image-picker"));
|
|
6861
|
+
__privateMethod(this, _FxEditor_instances, bindEvents_fn).call(this);
|
|
6862
|
+
__privateSet(this, _eventsBound, true);
|
|
6863
|
+
__privateMethod(this, _FxEditor_instances, renderToolbar_fn).call(this);
|
|
6864
|
+
__privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
|
|
6865
|
+
}
|
|
6866
|
+
/* ---------------------------------------------------------------------
|
|
6867
|
+
* API pública
|
|
6868
|
+
* ------------------------------------------------------------------ */
|
|
6869
|
+
focus(options) {
|
|
6870
|
+
this.editor?.focus(options ?? { preventScroll: true });
|
|
6871
|
+
}
|
|
6872
|
+
blur() {
|
|
6873
|
+
this.editor?.blur();
|
|
6874
|
+
}
|
|
6875
|
+
execute(command, value) {
|
|
6876
|
+
if (value !== void 0) {
|
|
6877
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
6878
|
+
nativeExec("styleWithCSS", "true");
|
|
6879
|
+
nativeExec(command, value);
|
|
6880
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
6881
|
+
return;
|
|
6882
|
+
}
|
|
6883
|
+
__privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, command);
|
|
6884
|
+
}
|
|
6885
|
+
insertHTML(html) {
|
|
6886
|
+
const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html);
|
|
6887
|
+
const probe = document.createElement("div");
|
|
6888
|
+
probe.innerHTML = clean;
|
|
6889
|
+
const extra = probe.textContent?.length ?? 0;
|
|
6890
|
+
if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, extra)) {
|
|
6891
|
+
__privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
|
|
6892
|
+
return;
|
|
6893
|
+
}
|
|
6894
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
6895
|
+
insertHtml(this.editor, clean);
|
|
6896
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
6897
|
+
}
|
|
6898
|
+
/** Mescla as células tocadas pela seleção. */
|
|
6899
|
+
mergeCells() {
|
|
6900
|
+
const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
|
|
6901
|
+
const editor = this.editor;
|
|
6902
|
+
const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
|
|
6903
|
+
if (!table || !range2) return false;
|
|
6904
|
+
const merged = mergeSelectedCells(table, range2);
|
|
6905
|
+
if (!merged) return false;
|
|
6906
|
+
placeCaretAtEnd(merged);
|
|
6907
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
6908
|
+
return true;
|
|
6909
|
+
}
|
|
6910
|
+
/** Divide a célula mesclada atual. */
|
|
6911
|
+
splitCell() {
|
|
6912
|
+
const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
|
|
6913
|
+
const editor = this.editor;
|
|
6914
|
+
const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
|
|
6915
|
+
const cell = editor ? closestInEditor(editor, range2?.startContainer ?? null, "td,th") : null;
|
|
6916
|
+
if (!table || !cell) return false;
|
|
6917
|
+
const created = splitCell(table, cell);
|
|
6918
|
+
if (!created.length) return false;
|
|
6919
|
+
placeCaretAtEnd(cell);
|
|
6920
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
6921
|
+
return true;
|
|
6922
|
+
}
|
|
6923
|
+
getHtml() {
|
|
6924
|
+
return this.value;
|
|
6925
|
+
}
|
|
6926
|
+
setHtml(html) {
|
|
6927
|
+
this.value = html;
|
|
6928
|
+
}
|
|
6929
|
+
getText() {
|
|
6930
|
+
return this.text;
|
|
6931
|
+
}
|
|
6932
|
+
clear() {
|
|
6933
|
+
this.value = "";
|
|
6934
|
+
__privateSet(this, _dirty, false);
|
|
6935
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, "");
|
|
6936
|
+
this.updateFormValue();
|
|
6937
|
+
__privateMethod(this, _FxEditor_instances, emitChange_fn).call(this);
|
|
6938
|
+
this.focus();
|
|
6939
|
+
}
|
|
6940
|
+
undo() {
|
|
6941
|
+
this.execute("undo");
|
|
6942
|
+
}
|
|
6943
|
+
redo() {
|
|
6944
|
+
this.execute("redo");
|
|
6945
|
+
}
|
|
6946
|
+
saveSelection() {
|
|
6947
|
+
if (this.editor) __privateSet(this, _lastRange, saveRange(this.editor) ?? __privateGet(this, _lastRange));
|
|
6948
|
+
}
|
|
6949
|
+
restoreSelection() {
|
|
6950
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
6951
|
+
}
|
|
6952
|
+
openDialog(kind) {
|
|
6953
|
+
if (kind === "link") void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this);
|
|
6954
|
+
else if (kind === "image") void __privateMethod(this, _FxEditor_instances, openImageDialog_fn).call(this);
|
|
6955
|
+
else void __privateMethod(this, _FxEditor_instances, openVideoDialog_fn).call(this);
|
|
6956
|
+
}
|
|
6957
|
+
toggleSource() {
|
|
6958
|
+
__privateMethod(this, _FxEditor_instances, toggleSource_fn).call(this);
|
|
6959
|
+
}
|
|
6960
|
+
toggleFullscreen() {
|
|
6961
|
+
this.fullscreen = !this.fullscreen;
|
|
6962
|
+
}
|
|
6963
|
+
};
|
|
6964
|
+
_built = new WeakMap();
|
|
6965
|
+
_eventsBound = new WeakMap();
|
|
6966
|
+
_initialized = new WeakMap();
|
|
6967
|
+
_defaultValue = new WeakMap();
|
|
6968
|
+
_value = new WeakMap();
|
|
6969
|
+
_dirty = new WeakMap();
|
|
6970
|
+
_sourceMode = new WeakMap();
|
|
6971
|
+
_lastRange = new WeakMap();
|
|
6972
|
+
_rafId = new WeakMap();
|
|
6973
|
+
_emitTimer = new WeakMap();
|
|
6974
|
+
_lastLimitError = new WeakMap();
|
|
6975
|
+
_snapshotTimer = new WeakMap();
|
|
6976
|
+
_history = new WeakMap();
|
|
6977
|
+
_historyIndex = new WeakMap();
|
|
6978
|
+
_imagePicker = new WeakMap();
|
|
6979
|
+
_mediaFigure = new WeakMap();
|
|
6980
|
+
_linkAnchor = new WeakMap();
|
|
6981
|
+
_resizeDispose = new WeakMap();
|
|
6982
|
+
_dialogReturnFocus = new WeakMap();
|
|
6983
|
+
_dialogResolve = new WeakMap();
|
|
6984
|
+
_disposers = new WeakMap();
|
|
6985
|
+
_selectionRaf = new WeakMap();
|
|
6986
|
+
_bodyOverflow = new WeakMap();
|
|
6987
|
+
_FxEditor_instances = new WeakSet();
|
|
6988
|
+
on_fn = function(target, type, handler, options) {
|
|
6989
|
+
target.addEventListener(type, handler, options);
|
|
6990
|
+
__privateGet(this, _disposers).push(() => target.removeEventListener(type, handler, options));
|
|
6991
|
+
};
|
|
6992
|
+
bindEvents_fn = function() {
|
|
6993
|
+
const editor = this.editor;
|
|
6994
|
+
const toolbar = this.toolbarEl;
|
|
6995
|
+
if (!editor || !toolbar) return;
|
|
6996
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "mousedown", (event) => {
|
|
6997
|
+
const target = event.target;
|
|
6998
|
+
__privateSet(this, _lastRange, saveRange(editor) ?? __privateGet(this, _lastRange));
|
|
6999
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
7000
|
+
if (target.closest("button")) event.preventDefault();
|
|
7001
|
+
});
|
|
7002
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "click", (event) => {
|
|
7003
|
+
const target = event.target.closest("[data-cmd],[data-action]");
|
|
7004
|
+
if (!target || target.disabled) return;
|
|
7005
|
+
const cmd = target.dataset.cmd;
|
|
7006
|
+
const action = target.dataset.action;
|
|
7007
|
+
if (cmd) __privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, cmd);
|
|
7008
|
+
else if (action) __privateMethod(this, _FxEditor_instances, action_fn).call(this, action);
|
|
7009
|
+
});
|
|
7010
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "change", (event) => {
|
|
7011
|
+
const select = event.target.closest("[data-block]");
|
|
7012
|
+
if (!select) return;
|
|
7013
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7014
|
+
formatBlock(editor, select.value || "p");
|
|
7015
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7016
|
+
});
|
|
7017
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "input", (event) => {
|
|
7018
|
+
const input = event.target.closest("[data-color]");
|
|
7019
|
+
if (!input) return;
|
|
7020
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7021
|
+
nativeExec("styleWithCSS", "true");
|
|
7022
|
+
nativeExec(input.dataset.color ?? "foreColor", input.value);
|
|
7023
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7024
|
+
});
|
|
7025
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "keydown", (event) => __privateMethod(this, _FxEditor_instances, handleToolbarKeydown_fn).call(this, event));
|
|
7026
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, toolbar, "focusin", (event) => {
|
|
7027
|
+
const button = event.target.closest(".tb-btn");
|
|
7028
|
+
if (button) __privateMethod(this, _FxEditor_instances, moveToolbarTabStop_fn).call(this, button);
|
|
7029
|
+
});
|
|
7030
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "input", (event) => {
|
|
7031
|
+
event.stopPropagation();
|
|
7032
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
7033
|
+
__privateSet(this, _dirty, true);
|
|
7034
|
+
__privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
|
|
7035
|
+
__privateMethod(this, _FxEditor_instances, scheduleEmit_fn).call(this);
|
|
7036
|
+
__privateMethod(this, _FxEditor_instances, scheduleSnapshot_fn).call(this);
|
|
7037
|
+
});
|
|
7038
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "focus", () => {
|
|
7039
|
+
this.dispatchEvent(new CustomEvent("focus", { bubbles: true, composed: true }));
|
|
7040
|
+
});
|
|
7041
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "blur", () => {
|
|
7042
|
+
__privateSet(this, _lastRange, saveRange(editor) ?? __privateGet(this, _lastRange));
|
|
7043
|
+
__privateMethod(this, _FxEditor_instances, emitNow_fn).call(this);
|
|
7044
|
+
__privateMethod(this, _FxEditor_instances, emitChange_fn).call(this);
|
|
7045
|
+
this.dispatchEvent(new CustomEvent("blur", { bubbles: true, composed: true }));
|
|
7046
|
+
});
|
|
7047
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "keydown", (event) => __privateMethod(this, _FxEditor_instances, handleKeydown_fn).call(this, event));
|
|
7048
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "beforeinput", (event) => __privateMethod(this, _FxEditor_instances, handleBeforeInput_fn).call(this, event));
|
|
7049
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "paste", (event) => __privateMethod(this, _FxEditor_instances, handlePaste_fn).call(this, event));
|
|
7050
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "dragover", (event) => event.preventDefault());
|
|
7051
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "drop", (event) => __privateMethod(this, _FxEditor_instances, handleDrop_fn).call(this, event));
|
|
7052
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "dblclick", (event) => __privateMethod(this, _FxEditor_instances, handleDoubleClick_fn).call(this, event));
|
|
7053
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "click", (event) => __privateMethod(this, _FxEditor_instances, handleEditorClick_fn).call(this, event));
|
|
7054
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, editor, "scroll", () => {
|
|
7055
|
+
__privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
|
|
7056
|
+
__privateMethod(this, _FxEditor_instances, positionLinkOverlay_fn).call(this);
|
|
7057
|
+
}, { passive: true });
|
|
7058
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, window, "resize", () => {
|
|
7059
|
+
__privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
|
|
7060
|
+
__privateMethod(this, _FxEditor_instances, positionLinkOverlay_fn).call(this);
|
|
7061
|
+
});
|
|
7062
|
+
const overlay = this.root.querySelector(".media-overlay");
|
|
7063
|
+
if (overlay) {
|
|
7064
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, overlay, "pointerdown", (event) => __privateMethod(this, _FxEditor_instances, startResize_fn).call(this, event));
|
|
7065
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, overlay, "click", (event) => {
|
|
7066
|
+
const action = event.target.closest("[data-media-action]");
|
|
7067
|
+
if (action?.dataset.mediaAction === "delete") __privateMethod(this, _FxEditor_instances, deleteMediaFigure_fn).call(this);
|
|
7068
|
+
});
|
|
7069
|
+
}
|
|
7070
|
+
const linkOverlay = this.root.querySelector(".link-overlay");
|
|
7071
|
+
if (linkOverlay) {
|
|
7072
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, linkOverlay, "click", (event) => __privateMethod(this, _FxEditor_instances, handleLinkOverlayClick_fn).call(this, event));
|
|
7073
|
+
}
|
|
7074
|
+
if (this.source) {
|
|
7075
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, this.source, "input", () => {
|
|
7076
|
+
__privateSet(this, _dirty, true);
|
|
7077
|
+
__privateMethod(this, _FxEditor_instances, scheduleEmit_fn).call(this);
|
|
7078
|
+
});
|
|
7079
|
+
}
|
|
7080
|
+
if (__privateGet(this, _imagePicker)) {
|
|
7081
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, __privateGet(this, _imagePicker), "change", () => {
|
|
7082
|
+
const file = __privateGet(this, _imagePicker)?.files?.[0];
|
|
7083
|
+
if (file) void __privateMethod(this, _FxEditor_instances, handleImageFile_fn).call(this, file);
|
|
7084
|
+
if (__privateGet(this, _imagePicker)) __privateGet(this, _imagePicker).value = "";
|
|
7085
|
+
});
|
|
7086
|
+
}
|
|
7087
|
+
const backdrop = this.root.querySelector(".backdrop");
|
|
7088
|
+
if (backdrop) {
|
|
7089
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, backdrop, "click", (event) => {
|
|
7090
|
+
if (event.target === backdrop) __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
|
|
7091
|
+
});
|
|
7092
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, backdrop, "keydown", (event) => __privateMethod(this, _FxEditor_instances, trapDialogFocus_fn).call(this, event));
|
|
7093
|
+
}
|
|
7094
|
+
const dialogBody = this.root.querySelector(".dialog-body");
|
|
7095
|
+
if (dialogBody) {
|
|
7096
|
+
__privateMethod(this, _FxEditor_instances, on_fn).call(this, dialogBody, "keydown", (event) => {
|
|
7097
|
+
const target = event.target;
|
|
7098
|
+
if (event.key === "Enter" && target.tagName !== "BUTTON") {
|
|
7099
|
+
event.preventDefault();
|
|
7100
|
+
__privateMethod(this, _FxEditor_instances, submitDialog_fn).call(this);
|
|
7101
|
+
}
|
|
7102
|
+
});
|
|
7103
|
+
}
|
|
7104
|
+
for (const selector of [".dialog-close", ".dialog-cancel"]) {
|
|
7105
|
+
const btn = this.root.querySelector(selector);
|
|
7106
|
+
if (btn) __privateMethod(this, _FxEditor_instances, on_fn).call(this, btn, "click", () => __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null));
|
|
7107
|
+
}
|
|
7108
|
+
const ok = this.root.querySelector(".dialog-ok");
|
|
7109
|
+
if (ok) __privateMethod(this, _FxEditor_instances, on_fn).call(this, ok, "click", () => __privateMethod(this, _FxEditor_instances, submitDialog_fn).call(this));
|
|
7110
|
+
const applySource = this.root.querySelector(".source-apply");
|
|
7111
|
+
if (applySource) __privateMethod(this, _FxEditor_instances, on_fn).call(this, applySource, "click", () => __privateMethod(this, _FxEditor_instances, applySource_fn).call(this));
|
|
7112
|
+
const cancelSource = this.root.querySelector(".source-cancel");
|
|
7113
|
+
if (cancelSource) __privateMethod(this, _FxEditor_instances, on_fn).call(this, cancelSource, "click", () => __privateMethod(this, _FxEditor_instances, cancelSource_fn).call(this));
|
|
7114
|
+
};
|
|
7115
|
+
syncChrome_fn = function() {
|
|
7116
|
+
const editor = this.editor;
|
|
7117
|
+
if (!editor) return;
|
|
7118
|
+
const locked = this.disabled || this.readonly;
|
|
7119
|
+
editor.setAttribute("contenteditable", locked ? "false" : "true");
|
|
7120
|
+
editor.setAttribute("aria-readonly", String(this.readonly));
|
|
7121
|
+
editor.setAttribute("aria-disabled", String(this.disabled));
|
|
7122
|
+
if (this.placeholder) editor.setAttribute("data-placeholder", this.placeholder);
|
|
7123
|
+
else editor.removeAttribute("data-placeholder");
|
|
7124
|
+
const toolbar = this.toolbarEl;
|
|
7125
|
+
toolbar?.toggleAttribute("hidden", this.toolbar === "none" || this.readonly);
|
|
7126
|
+
for (const control of Array.from(this.root.querySelectorAll(
|
|
7127
|
+
".tb-btn, .tb-select, .tb-color input"
|
|
7128
|
+
))) {
|
|
7129
|
+
const isSourceToggle = control.closest('[data-action="source"]') !== null;
|
|
7130
|
+
const isFullscreenToggle = control.closest('[data-action="fullscreen"]') !== null;
|
|
7131
|
+
control.disabled = this.readonly || this.disabled || __privateGet(this, _sourceMode) && !isSourceToggle && !isFullscreenToggle;
|
|
7132
|
+
}
|
|
7133
|
+
__privateMethod(this, _FxEditor_instances, applyCssVars_fn).call(this);
|
|
7134
|
+
__privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
|
|
7135
|
+
__privateMethod(this, _FxEditor_instances, updateCount_fn).call(this);
|
|
7136
|
+
__privateMethod(this, _FxEditor_instances, validate_fn).call(this);
|
|
7137
|
+
__privateMethod(this, _FxEditor_instances, updateSourceUi_fn).call(this);
|
|
7138
|
+
const fullscreenButton = this.root.querySelector('[data-action="fullscreen"]');
|
|
7139
|
+
fullscreenButton?.classList.toggle("active", this.fullscreen);
|
|
7140
|
+
fullscreenButton?.setAttribute("aria-pressed", String(this.fullscreen));
|
|
7141
|
+
if (locked || __privateGet(this, _sourceMode)) __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
7142
|
+
};
|
|
7143
|
+
applyCssVars_fn = function() {
|
|
7144
|
+
const min = this.getAttr("min-height");
|
|
7145
|
+
const max = this.getAttr("max-height");
|
|
7146
|
+
if (min) this.style.setProperty("--fx-editor-min-height", min);
|
|
7147
|
+
else this.style.removeProperty("--fx-editor-min-height");
|
|
7148
|
+
if (max) this.style.setProperty("--fx-editor-max-height", max);
|
|
7149
|
+
else this.style.removeProperty("--fx-editor-max-height");
|
|
7150
|
+
};
|
|
7151
|
+
updateEmpty_fn = function() {
|
|
7152
|
+
const editor = this.editor;
|
|
7153
|
+
if (!editor) return;
|
|
7154
|
+
const empty = !editor.textContent?.trim() && !editor.querySelector("img,video,iframe,table,hr");
|
|
7155
|
+
editor.classList.toggle("is-empty", empty);
|
|
7156
|
+
};
|
|
7157
|
+
updateCount_fn = function() {
|
|
7158
|
+
const count = this.root.querySelector(".count");
|
|
7159
|
+
if (!count) return;
|
|
7160
|
+
const statusbar = this.root.querySelector(".statusbar");
|
|
7161
|
+
const hasFooter = this.querySelector('[slot="footer"]') !== null;
|
|
7162
|
+
if (statusbar) statusbar.hidden = !__privateGet(this, _sourceMode) && !this.hasAttr("show-count") && !hasFooter;
|
|
7163
|
+
if (!this.hasAttr("show-count")) {
|
|
7164
|
+
count.textContent = "";
|
|
7165
|
+
return;
|
|
7166
|
+
}
|
|
7167
|
+
const text = this.text;
|
|
7168
|
+
const words = text ? text.split(/\s+/).filter(Boolean).length : 0;
|
|
7169
|
+
count.textContent = `${words} palavra${words === 1 ? "" : "s"} · ${text.length} caractere${text.length === 1 ? "" : "s"}`;
|
|
7170
|
+
};
|
|
7171
|
+
fits_fn = function(extra) {
|
|
7172
|
+
const max = this.maxlength;
|
|
7173
|
+
if (!max) return true;
|
|
7174
|
+
const editor = this.editor;
|
|
7175
|
+
const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
|
|
7176
|
+
const selected = range2 && !range2.collapsed ? range2.toString().length : 0;
|
|
7177
|
+
return this.text.length - selected + extra <= max;
|
|
7178
|
+
};
|
|
7179
|
+
limitError_fn = function() {
|
|
7180
|
+
const now = Date.now();
|
|
7181
|
+
if (now - __privateGet(this, _lastLimitError) < 1200) return;
|
|
7182
|
+
__privateSet(this, _lastLimitError, now);
|
|
7183
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, `Limite de ${this.maxlength.toLocaleString("pt-BR")} caracteres atingido.`);
|
|
7184
|
+
};
|
|
7185
|
+
validate_fn = function() {
|
|
7186
|
+
if (!this.internals || typeof this.internals.setValidity !== "function") return;
|
|
7187
|
+
try {
|
|
7188
|
+
if (this.required && !this.text) {
|
|
7189
|
+
this.internals.setValidity(
|
|
7190
|
+
{ valueMissing: true },
|
|
7191
|
+
"Preencha este campo.",
|
|
7192
|
+
this.editor ?? void 0
|
|
7193
|
+
);
|
|
7194
|
+
} else {
|
|
7195
|
+
this.internals.setValidity({});
|
|
7196
|
+
}
|
|
7197
|
+
} catch {
|
|
7198
|
+
}
|
|
7199
|
+
};
|
|
7200
|
+
/** Sanitiza + normaliza em uma única parseada do HTML. */
|
|
7201
|
+
cleanHtml_fn = function(html, forEditor = false) {
|
|
7202
|
+
const body = sanitizeBody(html);
|
|
7203
|
+
if (!body) return forEditor ? "<p><br></p>" : "";
|
|
7204
|
+
return normalizeElement(body, { forEditor });
|
|
7205
|
+
};
|
|
7206
|
+
serialize_fn = function() {
|
|
7207
|
+
if (__privateGet(this, _sourceMode) && this.source) {
|
|
7208
|
+
return __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, this.source.value);
|
|
7209
|
+
}
|
|
7210
|
+
const editor = this.editor;
|
|
7211
|
+
if (!editor) return "";
|
|
7212
|
+
if (!editor.textContent?.trim() && !editor.querySelector("img,video,iframe,table,hr")) return "";
|
|
7213
|
+
let clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, editor.innerHTML);
|
|
7214
|
+
if (this.sanitizeHtml) {
|
|
7215
|
+
clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, this.sanitizeHtml(clean));
|
|
7216
|
+
}
|
|
7217
|
+
return clean;
|
|
7218
|
+
};
|
|
7219
|
+
applyValue_fn = function(html) {
|
|
7220
|
+
const editor = this.editor;
|
|
7221
|
+
if (!editor) return;
|
|
7222
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
7223
|
+
const normalized = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html, true);
|
|
7224
|
+
if (editor.innerHTML !== normalized) editor.innerHTML = normalized;
|
|
7225
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
7226
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
7227
|
+
__privateMethod(this, _FxEditor_instances, decorateMediaFigures_fn).call(this);
|
|
7228
|
+
__privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
|
|
7229
|
+
__privateMethod(this, _FxEditor_instances, updateCount_fn).call(this);
|
|
7230
|
+
};
|
|
7231
|
+
scheduleEmit_fn = function() {
|
|
7232
|
+
cancelAnimationFrame(__privateGet(this, _rafId));
|
|
7233
|
+
if (__privateGet(this, _emitTimer)) {
|
|
7234
|
+
clearTimeout(__privateGet(this, _emitTimer));
|
|
7235
|
+
__privateSet(this, _emitTimer, null);
|
|
7236
|
+
}
|
|
7237
|
+
const size = this.editor?.textContent?.length ?? 0;
|
|
7238
|
+
if (size < LARGE_DOC_CHARS) {
|
|
7239
|
+
__privateSet(this, _rafId, requestAnimationFrame(() => __privateMethod(this, _FxEditor_instances, emitNow_fn).call(this)));
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
__privateSet(this, _emitTimer, setTimeout(() => {
|
|
7243
|
+
__privateSet(this, _emitTimer, null);
|
|
7244
|
+
__privateMethod(this, _FxEditor_instances, emitNow_fn).call(this);
|
|
7245
|
+
}, EMIT_DEBOUNCE_MS));
|
|
7246
|
+
};
|
|
7247
|
+
emitNow_fn = function() {
|
|
7248
|
+
cancelAnimationFrame(__privateGet(this, _rafId));
|
|
7249
|
+
if (__privateGet(this, _emitTimer)) {
|
|
7250
|
+
clearTimeout(__privateGet(this, _emitTimer));
|
|
7251
|
+
__privateSet(this, _emitTimer, null);
|
|
7252
|
+
}
|
|
7253
|
+
const value = __privateMethod(this, _FxEditor_instances, serialize_fn).call(this);
|
|
7254
|
+
__privateSet(this, _value, value);
|
|
7255
|
+
__privateSet(this, _dirty, false);
|
|
7256
|
+
const detail = { value, html: value, text: this.text };
|
|
7257
|
+
for (const type of ["input", "valueChange"]) {
|
|
7258
|
+
this.dispatchEvent(new CustomEvent(type, { bubbles: true, composed: true, detail }));
|
|
7259
|
+
}
|
|
7260
|
+
__privateMethod(this, _FxEditor_instances, updateCount_fn).call(this);
|
|
7261
|
+
__privateMethod(this, _FxEditor_instances, validate_fn).call(this);
|
|
7262
|
+
};
|
|
7263
|
+
emitChange_fn = function() {
|
|
7264
|
+
const value = this.value;
|
|
7265
|
+
const detail = { value, html: value, text: this.text };
|
|
7266
|
+
this.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail }));
|
|
7267
|
+
};
|
|
7268
|
+
pushHistory_fn = function(html) {
|
|
7269
|
+
if (__privateGet(this, _history)[__privateGet(this, _historyIndex)] === html) return;
|
|
7270
|
+
__privateSet(this, _history, __privateGet(this, _history).slice(0, __privateGet(this, _historyIndex) + 1));
|
|
7271
|
+
__privateGet(this, _history).push(html);
|
|
7272
|
+
if (__privateGet(this, _history).length > HISTORY_LIMIT) __privateGet(this, _history).shift();
|
|
7273
|
+
__privateSet(this, _historyIndex, __privateGet(this, _history).length - 1);
|
|
7274
|
+
};
|
|
7275
|
+
scheduleSnapshot_fn = function() {
|
|
7276
|
+
if (__privateGet(this, _snapshotTimer)) clearTimeout(__privateGet(this, _snapshotTimer));
|
|
7277
|
+
__privateSet(this, _snapshotTimer, setTimeout(() => {
|
|
7278
|
+
__privateMethod(this, _FxEditor_instances, pushHistory_fn).call(this, __privateGet(this, _dirty) ? __privateMethod(this, _FxEditor_instances, serialize_fn).call(this) : __privateGet(this, _value));
|
|
7279
|
+
}, 600));
|
|
7280
|
+
};
|
|
7281
|
+
historyUndo_fn = function() {
|
|
7282
|
+
if (__privateGet(this, _historyIndex) <= 0) return;
|
|
7283
|
+
__privateWrapper(this, _historyIndex)._--;
|
|
7284
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _history)[__privateGet(this, _historyIndex)]);
|
|
7285
|
+
if (this.editor) placeCaretAtEnd(this.editor);
|
|
7286
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7287
|
+
};
|
|
7288
|
+
historyRedo_fn = function() {
|
|
7289
|
+
if (__privateGet(this, _historyIndex) >= __privateGet(this, _history).length - 1) return;
|
|
7290
|
+
__privateWrapper(this, _historyIndex)._++;
|
|
7291
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _history)[__privateGet(this, _historyIndex)]);
|
|
7292
|
+
if (this.editor) placeCaretAtEnd(this.editor);
|
|
7293
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7294
|
+
};
|
|
7295
|
+
bindGlobalSelection_fn = function() {
|
|
7296
|
+
const handler = () => {
|
|
7297
|
+
const editor = this.editor;
|
|
7298
|
+
if (!editor) return;
|
|
7299
|
+
const range2 = getRange(editor, __privateGet(this, _lastRange));
|
|
7300
|
+
if (!range2) return;
|
|
7301
|
+
__privateSet(this, _lastRange, range2.cloneRange());
|
|
7302
|
+
cancelAnimationFrame(__privateGet(this, _selectionRaf));
|
|
7303
|
+
__privateSet(this, _selectionRaf, requestAnimationFrame(() => __privateMethod(this, _FxEditor_instances, updateToolbar_fn).call(this)));
|
|
7304
|
+
};
|
|
7305
|
+
document.addEventListener("selectionchange", handler);
|
|
7306
|
+
__privateGet(this, _disposers).push(() => document.removeEventListener("selectionchange", handler));
|
|
7307
|
+
};
|
|
7308
|
+
restoreSelection_fn = function() {
|
|
7309
|
+
const editor = this.editor;
|
|
7310
|
+
if (!editor) return;
|
|
7311
|
+
if (__privateGet(this, _lastRange)) restoreRange(editor, __privateGet(this, _lastRange));
|
|
7312
|
+
else editor.focus({ preventScroll: true });
|
|
7313
|
+
};
|
|
7314
|
+
syncBodyOverflow_fn = function() {
|
|
7315
|
+
if (this.fullscreen) {
|
|
7316
|
+
__privateSet(this, _bodyOverflow, document.body.style.overflow);
|
|
7317
|
+
document.body.style.overflow = "hidden";
|
|
7318
|
+
} else {
|
|
7319
|
+
document.body.style.overflow = __privateGet(this, _bodyOverflow);
|
|
7320
|
+
}
|
|
7321
|
+
};
|
|
7322
|
+
updateSourceUi_fn = function() {
|
|
7323
|
+
const source = this.source;
|
|
7324
|
+
const content = this.editor;
|
|
7325
|
+
const actions = this.root.querySelector(".source-actions");
|
|
7326
|
+
if (actions) actions.toggleAttribute("hidden", !__privateGet(this, _sourceMode));
|
|
7327
|
+
if (source) source.hidden = !__privateGet(this, _sourceMode);
|
|
7328
|
+
if (content) content.hidden = __privateGet(this, _sourceMode);
|
|
7329
|
+
const toggle = this.root.querySelector('[data-action="source"]');
|
|
7330
|
+
toggle?.classList.toggle("active", __privateGet(this, _sourceMode));
|
|
7331
|
+
toggle?.setAttribute("aria-pressed", String(__privateGet(this, _sourceMode)));
|
|
7332
|
+
};
|
|
7333
|
+
releaseDialog_fn = function(result) {
|
|
7334
|
+
const backdrop = this.root.querySelector(".backdrop");
|
|
7335
|
+
backdrop?.setAttribute("hidden", "");
|
|
7336
|
+
const resolve = __privateGet(this, _dialogResolve);
|
|
7337
|
+
__privateSet(this, _dialogResolve, null);
|
|
7338
|
+
const returnFocus = __privateGet(this, _dialogReturnFocus);
|
|
7339
|
+
__privateSet(this, _dialogReturnFocus, null);
|
|
7340
|
+
resolve?.(result);
|
|
7341
|
+
if (returnFocus && returnFocus.isConnected) returnFocus.focus({ preventScroll: true });
|
|
7342
|
+
else __privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7343
|
+
};
|
|
7344
|
+
renderToolbar_fn = function() {
|
|
7345
|
+
const toolbar = this.toolbarEl;
|
|
7346
|
+
if (!toolbar) return;
|
|
7347
|
+
const preset = this.toolbar;
|
|
7348
|
+
if (preset === "none") {
|
|
7349
|
+
toolbar.innerHTML = "";
|
|
7350
|
+
toolbar.hidden = true;
|
|
7351
|
+
return;
|
|
7352
|
+
}
|
|
7353
|
+
toolbar.hidden = false;
|
|
7354
|
+
const features = new Set(
|
|
7355
|
+
this.getAttr("features").split(",").map((item) => item.trim()).filter(Boolean)
|
|
7356
|
+
);
|
|
7357
|
+
const has = (feature) => features.size === 0 || features.has(feature);
|
|
7358
|
+
const button = (cmd, icon, label, action = "") => `<button type="button" class="tb-btn" tabindex="-1" ${action ? `data-action="${action}"` : `data-cmd="${cmd}"`} title="${label}" aria-label="${label}" aria-pressed="false">${fenixIconHtml(icon)}</button>`;
|
|
7359
|
+
const select = `<select class="tb-select" data-block aria-label="Formato do bloco">
|
|
7360
|
+
<option value="p">Parágrafo</option>
|
|
7361
|
+
<option value="h1">Título 1</option>
|
|
7362
|
+
<option value="h2">Título 2</option>
|
|
7363
|
+
<option value="h3">Título 3</option>
|
|
7364
|
+
<option value="h4">Título 4</option>
|
|
7365
|
+
<option value="blockquote">Citação</option>
|
|
7366
|
+
<option value="pre">Código</option>
|
|
7367
|
+
</select>`;
|
|
7368
|
+
const history = button("undo", "undo", "Desfazer") + button("redo", "redo", "Refazer");
|
|
7369
|
+
const inline = [
|
|
7370
|
+
button("bold", "format_bold", "Negrito"),
|
|
7371
|
+
button("italic", "format_italic", "Itálico"),
|
|
7372
|
+
button("underline", "format_underlined", "Sublinhado"),
|
|
7373
|
+
button("strikeThrough", "format_strikethrough", "Riscado"),
|
|
7374
|
+
button("code", "code", "Código inline"),
|
|
7375
|
+
button("removeFormat", "format_clear", "Limpar formatação")
|
|
7376
|
+
].join("");
|
|
7377
|
+
const color = [
|
|
7378
|
+
`<label class="tb-color" title="Cor do texto"><input type="color" data-color="foreColor" value="#111827" aria-label="Cor do texto"></label>`,
|
|
7379
|
+
`<label class="tb-color" title="Realçar texto"><input type="color" data-color="hiliteColor" value="#fef08a" aria-label="Realçar texto"></label>`
|
|
7380
|
+
].join("");
|
|
7381
|
+
const alignGroup = [
|
|
7382
|
+
button("justifyLeft", "format_align_left", "Alinhar à esquerda"),
|
|
7383
|
+
button("justifyCenter", "format_align_center", "Centralizar"),
|
|
7384
|
+
button("justifyRight", "format_align_right", "Alinhar à direita"),
|
|
7385
|
+
button("justifyFull", "format_align_justify", "Justificar")
|
|
7386
|
+
].join("");
|
|
7387
|
+
const indentation = [
|
|
7388
|
+
button("outdent", "format_indent_decrease", "Diminuir recuo", "outdent"),
|
|
7389
|
+
button("indent", "format_indent_increase", "Aumentar recuo", "indent")
|
|
7390
|
+
].join("");
|
|
7391
|
+
const lists = [
|
|
7392
|
+
button("insertUnorderedList", "format_list_bulleted", "Lista com marcadores"),
|
|
7393
|
+
button("insertOrderedList", "format_list_numbered", "Lista numerada")
|
|
7394
|
+
].join("");
|
|
7395
|
+
const blocks = button("", "format_quote", "Citação", "blockquote") + button("", "code_blocks", "Bloco de código", "codeblock");
|
|
7396
|
+
const insert = [];
|
|
7397
|
+
if (has("link")) {
|
|
7398
|
+
insert.push(button("", "link", "Link (Ctrl+K)", "link"));
|
|
7399
|
+
insert.push(button("", "link_off", "Remover link", "unlink"));
|
|
7400
|
+
}
|
|
7401
|
+
if (preset !== "minimal") {
|
|
7402
|
+
if (has("image")) insert.push(button("", "image", "Imagem", "image"));
|
|
7403
|
+
if (has("video")) insert.push(button("", "videocam", "Vídeo", "video"));
|
|
7404
|
+
if (has("table")) insert.push(button("", "table", "Tabela", "table"));
|
|
7405
|
+
if (has("columns")) insert.push(button("", "view_column", "Colunas", "columns"));
|
|
7406
|
+
insert.push(button("insertHorizontalRule", "horizontal_rule", "Linha horizontal"));
|
|
7407
|
+
}
|
|
7408
|
+
const tools = [];
|
|
7409
|
+
if (preset === "standard" || preset === "full") {
|
|
7410
|
+
if (has("source")) tools.push(button("", "code", "Ver HTML", "source"));
|
|
7411
|
+
if (has("fullscreen")) tools.push(button("", "fullscreen", "Tela cheia", "fullscreen"));
|
|
7412
|
+
}
|
|
7413
|
+
const groups = [history, select + inline];
|
|
7414
|
+
if (preset !== "minimal") groups.push(lists);
|
|
7415
|
+
if (preset !== "minimal" && preset !== "basic") {
|
|
7416
|
+
groups.push(color, alignGroup, indentation, blocks);
|
|
7417
|
+
}
|
|
7418
|
+
if (insert.length) groups.push(insert.join(""));
|
|
7419
|
+
if (tools.length) groups.push(tools.join(""));
|
|
7420
|
+
toolbar.innerHTML = groups.map((group) => `<div class="tb-group">${group}</div>`).join("") + `<div class="panel" data-panel hidden></div>`;
|
|
7421
|
+
toolbar.querySelector(".tb-btn")?.setAttribute("tabindex", "0");
|
|
7422
|
+
};
|
|
7423
|
+
updateToolbar_fn = function() {
|
|
7424
|
+
const editor = this.editor;
|
|
7425
|
+
if (!editor) return;
|
|
7426
|
+
const range2 = getRange(editor, __privateGet(this, _lastRange));
|
|
7427
|
+
if (!range2) return;
|
|
7428
|
+
const setActive = (cmd, active) => {
|
|
7429
|
+
const button = this.root.querySelector(`[data-cmd="${cmd}"]`);
|
|
7430
|
+
if (!button) return;
|
|
7431
|
+
button.classList.toggle("active", active);
|
|
7432
|
+
button.setAttribute("aria-pressed", String(active));
|
|
7433
|
+
};
|
|
7434
|
+
const ancestor = (selector) => {
|
|
7435
|
+
const node = range2.startContainer;
|
|
7436
|
+
const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
7437
|
+
const match = el?.closest(selector);
|
|
7438
|
+
return Boolean(match && editor.contains(match));
|
|
7439
|
+
};
|
|
7440
|
+
setActive("bold", ancestor("strong,b"));
|
|
7441
|
+
setActive("italic", ancestor("em,i"));
|
|
7442
|
+
setActive("underline", ancestor("u"));
|
|
7443
|
+
setActive("strikeThrough", ancestor("s,del,strike"));
|
|
7444
|
+
setActive("code", ancestor("code"));
|
|
7445
|
+
setActive("insertUnorderedList", ancestor("ul"));
|
|
7446
|
+
setActive("insertOrderedList", ancestor("ol"));
|
|
7447
|
+
const block = getBlock(editor, range2);
|
|
7448
|
+
const blockTag = block?.tagName.toLowerCase() ?? "p";
|
|
7449
|
+
const blockSelect = this.root.querySelector("[data-block]");
|
|
7450
|
+
if (blockSelect) {
|
|
7451
|
+
const value = Array.from(blockSelect.options).some((option) => option.value === blockTag) ? blockTag : "p";
|
|
7452
|
+
blockSelect.value = value;
|
|
7453
|
+
}
|
|
7454
|
+
const alignment = block ? block.style.textAlign || "left" : "left";
|
|
7455
|
+
setActive("justifyLeft", alignment === "left" || alignment === "start" || alignment === "");
|
|
7456
|
+
setActive("justifyCenter", alignment === "center");
|
|
7457
|
+
setActive("justifyRight", alignment === "right" || alignment === "end");
|
|
7458
|
+
setActive("justifyFull", alignment === "justify");
|
|
7459
|
+
};
|
|
7460
|
+
handleToolbarKeydown_fn = function(event) {
|
|
7461
|
+
const active = this.root.activeElement;
|
|
7462
|
+
if (!active || !active.classList.contains("tb-btn")) return;
|
|
7463
|
+
if (!["ArrowRight", "ArrowLeft", "Home", "End"].includes(event.key)) return;
|
|
7464
|
+
const controls = Array.from(
|
|
7465
|
+
this.toolbarEl?.querySelectorAll(
|
|
7466
|
+
".tb-btn:not(:disabled), .tb-select:not(:disabled), .tb-color input:not(:disabled)"
|
|
7467
|
+
) ?? []
|
|
7468
|
+
);
|
|
7469
|
+
if (!controls.length) return;
|
|
7470
|
+
event.preventDefault();
|
|
7471
|
+
let index = controls.indexOf(active);
|
|
7472
|
+
if (event.key === "Home") index = 0;
|
|
7473
|
+
else if (event.key === "End") index = controls.length - 1;
|
|
7474
|
+
else index = (index + (event.key === "ArrowRight" ? 1 : -1) + controls.length) % controls.length;
|
|
7475
|
+
const next = controls[index];
|
|
7476
|
+
if (!next) return;
|
|
7477
|
+
__privateMethod(this, _FxEditor_instances, moveToolbarTabStop_fn).call(this, next);
|
|
7478
|
+
next.focus();
|
|
7479
|
+
};
|
|
7480
|
+
moveToolbarTabStop_fn = function(active) {
|
|
7481
|
+
this.toolbarEl?.querySelectorAll(".tb-btn").forEach((button) => {
|
|
7482
|
+
button.setAttribute("tabindex", button === active ? "0" : "-1");
|
|
7483
|
+
});
|
|
7484
|
+
};
|
|
7485
|
+
trapDialogFocus_fn = function(event) {
|
|
7486
|
+
if (!__privateGet(this, _dialogResolve) || event.key !== "Tab") return;
|
|
7487
|
+
const dialog = this.root.querySelector(".dialog");
|
|
7488
|
+
if (!dialog) return;
|
|
7489
|
+
const focusables = Array.from(
|
|
7490
|
+
dialog.querySelectorAll(
|
|
7491
|
+
'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'
|
|
7492
|
+
)
|
|
7493
|
+
);
|
|
7494
|
+
if (!focusables.length) return;
|
|
7495
|
+
const first = focusables[0];
|
|
7496
|
+
const last = focusables[focusables.length - 1];
|
|
7497
|
+
const active = this.root.activeElement;
|
|
7498
|
+
if (event.shiftKey && (active === first || !active || !dialog.contains(active))) {
|
|
7499
|
+
event.preventDefault();
|
|
7500
|
+
last.focus();
|
|
7501
|
+
} else if (!event.shiftKey && (active === last || !active || !dialog.contains(active))) {
|
|
7502
|
+
event.preventDefault();
|
|
7503
|
+
first.focus();
|
|
7504
|
+
}
|
|
7505
|
+
};
|
|
7506
|
+
execCommand_fn = function(cmd) {
|
|
7507
|
+
const editor = this.editor;
|
|
7508
|
+
if (!editor) return;
|
|
7509
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7510
|
+
switch (cmd) {
|
|
7511
|
+
case "undo":
|
|
7512
|
+
if (!nativeExec("undo")) __privateMethod(this, _FxEditor_instances, historyUndo_fn).call(this);
|
|
7513
|
+
break;
|
|
7514
|
+
case "redo":
|
|
7515
|
+
if (!nativeExec("redo")) __privateMethod(this, _FxEditor_instances, historyRedo_fn).call(this);
|
|
7516
|
+
break;
|
|
7517
|
+
case "bold":
|
|
7518
|
+
toggleInline(editor, "strong");
|
|
7519
|
+
break;
|
|
7520
|
+
case "italic":
|
|
7521
|
+
toggleInline(editor, "em");
|
|
7522
|
+
break;
|
|
7523
|
+
case "underline":
|
|
7524
|
+
toggleInline(editor, "u");
|
|
7525
|
+
break;
|
|
7526
|
+
case "strikeThrough":
|
|
7527
|
+
toggleInline(editor, "s");
|
|
7528
|
+
break;
|
|
7529
|
+
case "code":
|
|
7530
|
+
toggleInline(editor, "code");
|
|
7531
|
+
break;
|
|
7532
|
+
case "justifyLeft":
|
|
7533
|
+
align(editor, "left");
|
|
7534
|
+
break;
|
|
7535
|
+
case "justifyCenter":
|
|
7536
|
+
align(editor, "center");
|
|
7537
|
+
break;
|
|
7538
|
+
case "justifyRight":
|
|
7539
|
+
align(editor, "right");
|
|
7540
|
+
break;
|
|
7541
|
+
case "justifyFull":
|
|
7542
|
+
align(editor, "justify");
|
|
7543
|
+
break;
|
|
7544
|
+
case "insertUnorderedList":
|
|
7545
|
+
toggleList(editor, false);
|
|
7546
|
+
break;
|
|
7547
|
+
case "insertOrderedList":
|
|
7548
|
+
toggleList(editor, true);
|
|
7549
|
+
break;
|
|
7550
|
+
case "removeFormat":
|
|
7551
|
+
removeFormat(editor);
|
|
7552
|
+
break;
|
|
7553
|
+
case "insertHorizontalRule":
|
|
7554
|
+
insertHtml(editor, "<hr><p><br></p>");
|
|
7555
|
+
break;
|
|
7556
|
+
case "foreColor":
|
|
7557
|
+
case "hiliteColor":
|
|
7558
|
+
nativeExec("styleWithCSS", "true");
|
|
7559
|
+
nativeExec(cmd, this.root.querySelector(`[data-color="${cmd}"]`)?.value);
|
|
7560
|
+
break;
|
|
7561
|
+
default:
|
|
7562
|
+
nativeExec(cmd);
|
|
7563
|
+
}
|
|
7564
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7565
|
+
};
|
|
7566
|
+
afterCommand_fn = function() {
|
|
7567
|
+
__privateSet(this, _dirty, true);
|
|
7568
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
7569
|
+
__privateMethod(this, _FxEditor_instances, decorateMediaFigures_fn).call(this);
|
|
7570
|
+
__privateMethod(this, _FxEditor_instances, updateEmpty_fn).call(this);
|
|
7571
|
+
__privateMethod(this, _FxEditor_instances, scheduleEmit_fn).call(this);
|
|
7572
|
+
__privateMethod(this, _FxEditor_instances, scheduleSnapshot_fn).call(this);
|
|
7573
|
+
__privateMethod(this, _FxEditor_instances, updateToolbar_fn).call(this);
|
|
7574
|
+
};
|
|
7575
|
+
action_fn = function(action) {
|
|
7576
|
+
const editor = this.editor;
|
|
7577
|
+
if (!editor) return;
|
|
7578
|
+
switch (action) {
|
|
7579
|
+
case "link":
|
|
7580
|
+
void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this);
|
|
7581
|
+
break;
|
|
7582
|
+
case "image":
|
|
7583
|
+
void __privateMethod(this, _FxEditor_instances, openImageDialog_fn).call(this);
|
|
7584
|
+
break;
|
|
7585
|
+
case "video":
|
|
7586
|
+
void __privateMethod(this, _FxEditor_instances, openVideoDialog_fn).call(this);
|
|
7587
|
+
break;
|
|
7588
|
+
case "table":
|
|
7589
|
+
__privateMethod(this, _FxEditor_instances, showTablePanel_fn).call(this);
|
|
7590
|
+
break;
|
|
7591
|
+
case "columns":
|
|
7592
|
+
__privateMethod(this, _FxEditor_instances, showColumnsPanel_fn).call(this);
|
|
7593
|
+
break;
|
|
7594
|
+
case "source":
|
|
7595
|
+
__privateMethod(this, _FxEditor_instances, toggleSource_fn).call(this);
|
|
7596
|
+
break;
|
|
7597
|
+
case "fullscreen":
|
|
7598
|
+
this.fullscreen = !this.fullscreen;
|
|
7599
|
+
break;
|
|
7600
|
+
case "blockquote":
|
|
7601
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7602
|
+
formatBlock(editor, "blockquote");
|
|
7603
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7604
|
+
break;
|
|
7605
|
+
case "codeblock":
|
|
7606
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7607
|
+
formatBlock(editor, "pre");
|
|
7608
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7609
|
+
break;
|
|
7610
|
+
case "indent":
|
|
7611
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7612
|
+
indent(editor, 24);
|
|
7613
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7614
|
+
break;
|
|
7615
|
+
case "outdent":
|
|
7616
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7617
|
+
indent(editor, -24);
|
|
7618
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7619
|
+
break;
|
|
7620
|
+
case "clear":
|
|
7621
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7622
|
+
removeFormat(editor);
|
|
7623
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7624
|
+
break;
|
|
7625
|
+
case "unlink":
|
|
7626
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7627
|
+
unlink(editor);
|
|
7628
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7629
|
+
break;
|
|
7630
|
+
}
|
|
7631
|
+
};
|
|
7632
|
+
handleKeydown_fn = function(event) {
|
|
7633
|
+
if (event.isComposing) return;
|
|
7634
|
+
const active = this.root.activeElement;
|
|
7635
|
+
const overlay = this.root.querySelector(".media-overlay");
|
|
7636
|
+
if (__privateGet(this, _mediaFigure) && (event.key === "Delete" || event.key === "Backspace") && (active === __privateGet(this, _mediaFigure) || __privateGet(this, _mediaFigure).contains(active) || Boolean(overlay && active && overlay.contains(active)))) {
|
|
7637
|
+
event.preventDefault();
|
|
7638
|
+
__privateMethod(this, _FxEditor_instances, deleteMediaFigure_fn).call(this);
|
|
7639
|
+
return;
|
|
7640
|
+
}
|
|
7641
|
+
const mod = event.ctrlKey || event.metaKey;
|
|
7642
|
+
const key2 = event.key.toLowerCase();
|
|
7643
|
+
if (mod && !event.altKey) {
|
|
7644
|
+
if (key2 === "b") {
|
|
7645
|
+
event.preventDefault();
|
|
7646
|
+
__privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "bold");
|
|
7647
|
+
return;
|
|
7648
|
+
}
|
|
7649
|
+
if (key2 === "i") {
|
|
7650
|
+
event.preventDefault();
|
|
7651
|
+
__privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "italic");
|
|
7652
|
+
return;
|
|
7653
|
+
}
|
|
7654
|
+
if (key2 === "u") {
|
|
7655
|
+
event.preventDefault();
|
|
7656
|
+
__privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "underline");
|
|
7657
|
+
return;
|
|
7658
|
+
}
|
|
7659
|
+
if (key2 === "k") {
|
|
7660
|
+
event.preventDefault();
|
|
7661
|
+
void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this);
|
|
7662
|
+
return;
|
|
7663
|
+
}
|
|
7664
|
+
if (key2 === "z" && !event.shiftKey) {
|
|
7665
|
+
event.preventDefault();
|
|
7666
|
+
__privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "undo");
|
|
7667
|
+
return;
|
|
7668
|
+
}
|
|
7669
|
+
if (key2 === "y" || key2 === "z" && event.shiftKey) {
|
|
7670
|
+
event.preventDefault();
|
|
7671
|
+
__privateMethod(this, _FxEditor_instances, execCommand_fn).call(this, "redo");
|
|
7672
|
+
return;
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
if (event.key === "Escape") {
|
|
7676
|
+
if (__privateGet(this, _dialogResolve)) __privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
|
|
7677
|
+
else if (__privateGet(this, _sourceMode)) __privateMethod(this, _FxEditor_instances, cancelSource_fn).call(this);
|
|
7678
|
+
else if (this.fullscreen) this.fullscreen = false;
|
|
7679
|
+
else if (__privateGet(this, _linkAnchor)) __privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
7680
|
+
else if (__privateGet(this, _mediaFigure)) __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
7681
|
+
else __privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
|
|
7682
|
+
return;
|
|
7683
|
+
}
|
|
7684
|
+
if (event.key === "Tab" && __privateMethod(this, _FxEditor_instances, tabInTable_fn).call(this, event.shiftKey)) {
|
|
7685
|
+
event.preventDefault();
|
|
7686
|
+
}
|
|
7687
|
+
};
|
|
7688
|
+
tabInTable_fn = function(backwards) {
|
|
7689
|
+
const editor = this.editor;
|
|
7690
|
+
if (!editor) return false;
|
|
7691
|
+
const range2 = getRange(editor, __privateGet(this, _lastRange));
|
|
7692
|
+
if (!range2) return false;
|
|
7693
|
+
const cell = closestInEditor(editor, range2.startContainer, "td,th");
|
|
7694
|
+
const table = cell?.closest("table");
|
|
7695
|
+
if (!cell || !table) return false;
|
|
7696
|
+
const cells = Array.from(table.querySelectorAll("td,th"));
|
|
7697
|
+
let index = cells.indexOf(cell);
|
|
7698
|
+
if (index < 0) return false;
|
|
7699
|
+
if (backwards) {
|
|
7700
|
+
index = Math.max(0, index - 1);
|
|
7701
|
+
} else {
|
|
7702
|
+
index++;
|
|
7703
|
+
if (index >= cells.length) {
|
|
7704
|
+
const row = cell.parentElement;
|
|
7705
|
+
if (!row) return false;
|
|
7706
|
+
const clone = row.cloneNode(true);
|
|
7707
|
+
for (const cloned of Array.from(clone.querySelectorAll("td,th"))) {
|
|
7708
|
+
cloned.innerHTML = "<p><br></p>";
|
|
7709
|
+
}
|
|
7710
|
+
row.after(clone);
|
|
7711
|
+
cells.push(...Array.from(clone.querySelectorAll("td,th")));
|
|
7712
|
+
index = cells.indexOf(clone.querySelector("td,th"));
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
const target = cells[index];
|
|
7716
|
+
if (!target) return false;
|
|
7717
|
+
const next = document.createRange();
|
|
7718
|
+
next.selectNodeContents(target);
|
|
7719
|
+
next.collapse(true);
|
|
7720
|
+
const selection = document.getSelection();
|
|
7721
|
+
selection?.removeAllRanges();
|
|
7722
|
+
selection?.addRange(next);
|
|
7723
|
+
return true;
|
|
7724
|
+
};
|
|
7725
|
+
handleBeforeInput_fn = function(event) {
|
|
7726
|
+
if (this.disabled || this.readonly || !this.maxlength) return;
|
|
7727
|
+
const type = event.inputType;
|
|
7728
|
+
if (type === "insertFromPaste" || type === "insertFromDrop" || type.startsWith("history") || type.startsWith("format")) {
|
|
7729
|
+
return;
|
|
7730
|
+
}
|
|
7731
|
+
const extra = event.data?.length ?? (type === "insertParagraph" || type === "insertLineBreak" ? 1 : 0);
|
|
7732
|
+
if (!extra) return;
|
|
7733
|
+
if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, extra)) {
|
|
7734
|
+
event.preventDefault();
|
|
7735
|
+
__privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
|
|
7736
|
+
}
|
|
7737
|
+
};
|
|
7738
|
+
handlePaste_fn = function(event) {
|
|
7739
|
+
const clipboard = event.clipboardData;
|
|
7740
|
+
if (!clipboard) return;
|
|
7741
|
+
const file = Array.from(clipboard.files ?? []).find((item) => item.type.startsWith("image/"));
|
|
7742
|
+
if (file) {
|
|
7743
|
+
event.preventDefault();
|
|
7744
|
+
void __privateMethod(this, _FxEditor_instances, handleImageFile_fn).call(this, file);
|
|
7745
|
+
return;
|
|
7746
|
+
}
|
|
7747
|
+
const html = clipboard.getData("text/html");
|
|
7748
|
+
const text = clipboard.getData("text/plain");
|
|
7749
|
+
if (html.length > MAX_PASTE_BYTES || text.length > MAX_PASTE_BYTES) return;
|
|
7750
|
+
event.preventDefault();
|
|
7751
|
+
if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, text.length)) {
|
|
7752
|
+
__privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
|
|
7753
|
+
return;
|
|
7754
|
+
}
|
|
7755
|
+
if (this.pasteMode === "plain" || !html) {
|
|
7756
|
+
__privateMethod(this, _FxEditor_instances, insertPlainText_fn).call(this, text);
|
|
7757
|
+
return;
|
|
7758
|
+
}
|
|
7759
|
+
const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html);
|
|
7760
|
+
if (!clean) {
|
|
7761
|
+
__privateMethod(this, _FxEditor_instances, insertPlainText_fn).call(this, text);
|
|
7762
|
+
return;
|
|
7763
|
+
}
|
|
7764
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7765
|
+
insertHtml(this.editor, clean);
|
|
7766
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7767
|
+
};
|
|
7768
|
+
handleDrop_fn = function(event) {
|
|
7769
|
+
const transfer = event.dataTransfer;
|
|
7770
|
+
if (!transfer) return;
|
|
7771
|
+
event.preventDefault();
|
|
7772
|
+
const file = Array.from(transfer.files ?? []).find((item) => item.type.startsWith("image/"));
|
|
7773
|
+
if (file) {
|
|
7774
|
+
void __privateMethod(this, _FxEditor_instances, handleImageFile_fn).call(this, file);
|
|
7775
|
+
return;
|
|
7776
|
+
}
|
|
7777
|
+
const html = transfer.getData("text/html");
|
|
7778
|
+
const text = transfer.getData("text/plain");
|
|
7779
|
+
if (!__privateMethod(this, _FxEditor_instances, fits_fn).call(this, text.length)) {
|
|
7780
|
+
__privateMethod(this, _FxEditor_instances, limitError_fn).call(this);
|
|
7781
|
+
return;
|
|
7782
|
+
}
|
|
7783
|
+
if (html) {
|
|
7784
|
+
insertHtml(this.editor, __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, html));
|
|
7785
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7786
|
+
} else if (text) {
|
|
7787
|
+
__privateMethod(this, _FxEditor_instances, insertPlainText_fn).call(this, text);
|
|
7788
|
+
}
|
|
7789
|
+
};
|
|
7790
|
+
insertPlainText_fn = function(text) {
|
|
7791
|
+
const paragraphs = text.replace(/\r\n/g, "\n").split(/\n{2,}/).map((block) => `<p>${escapeText(block).replace(/\n/g, "<br>")}</p>`).join("");
|
|
7792
|
+
insertHtml(this.editor, paragraphs || "<p><br></p>");
|
|
7793
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7794
|
+
};
|
|
7795
|
+
toggleSource_fn = function() {
|
|
7796
|
+
if (__privateGet(this, _sourceMode)) {
|
|
7797
|
+
__privateMethod(this, _FxEditor_instances, applySource_fn).call(this);
|
|
7798
|
+
return;
|
|
7799
|
+
}
|
|
7800
|
+
const source = this.source;
|
|
7801
|
+
if (!source) return;
|
|
7802
|
+
source.value = __privateMethod(this, _FxEditor_instances, serialize_fn).call(this);
|
|
7803
|
+
__privateSet(this, _sourceMode, true);
|
|
7804
|
+
__privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
|
|
7805
|
+
source.focus();
|
|
7806
|
+
};
|
|
7807
|
+
applySource_fn = function() {
|
|
7808
|
+
const source = this.source;
|
|
7809
|
+
if (!source) return;
|
|
7810
|
+
const clean = __privateMethod(this, _FxEditor_instances, cleanHtml_fn).call(this, source.value);
|
|
7811
|
+
__privateSet(this, _sourceMode, false);
|
|
7812
|
+
__privateSet(this, _value, clean);
|
|
7813
|
+
__privateSet(this, _dirty, false);
|
|
7814
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, clean);
|
|
7815
|
+
__privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
|
|
7816
|
+
this.updateFormValue();
|
|
7817
|
+
__privateMethod(this, _FxEditor_instances, emitChange_fn).call(this);
|
|
7818
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7819
|
+
};
|
|
7820
|
+
cancelSource_fn = function() {
|
|
7821
|
+
__privateSet(this, _sourceMode, false);
|
|
7822
|
+
__privateMethod(this, _FxEditor_instances, applyValue_fn).call(this, __privateGet(this, _value));
|
|
7823
|
+
__privateMethod(this, _FxEditor_instances, syncChrome_fn).call(this);
|
|
7824
|
+
};
|
|
7825
|
+
openDialog_fn = function(title, fields) {
|
|
7826
|
+
const backdrop = this.root.querySelector(".backdrop");
|
|
7827
|
+
const titleEl = this.root.querySelector(".dialog h3");
|
|
7828
|
+
const body = this.root.querySelector(".dialog-body");
|
|
7829
|
+
if (!backdrop || !titleEl || !body) return Promise.resolve(null);
|
|
7830
|
+
titleEl.textContent = title;
|
|
7831
|
+
body.innerHTML = fields.map((field) => __privateMethod(this, _FxEditor_instances, fieldHtml_fn).call(this, field)).join("");
|
|
7832
|
+
__privateSet(this, _dialogReturnFocus, this.root.activeElement ?? this.editor);
|
|
7833
|
+
backdrop.removeAttribute("hidden");
|
|
7834
|
+
const first = body.querySelector("input,select");
|
|
7835
|
+
first?.focus();
|
|
7836
|
+
return new Promise((resolve) => {
|
|
7837
|
+
__privateSet(this, _dialogResolve, resolve);
|
|
7838
|
+
});
|
|
7839
|
+
};
|
|
7840
|
+
fieldHtml_fn = function(field) {
|
|
7841
|
+
const label = esc(field.label);
|
|
7842
|
+
if (field.type === "select") {
|
|
7843
|
+
const options = (field.options ?? []).map((option) => `<option value="${esc(option.value)}" ${option.value === field.value ? "selected" : ""}>${esc(option.label)}</option>`).join("");
|
|
7844
|
+
return `<label class="field"><span>${label}</span><select data-field="${esc(field.name)}">${options}</select></label>`;
|
|
7845
|
+
}
|
|
7846
|
+
const type = field.type === "url" ? "url" : field.type === "number" ? "number" : "text";
|
|
7847
|
+
return `<label class="field"><span>${label}</span><input data-field="${esc(field.name)}" type="${type}" value="${esc(field.value ?? "")}" placeholder="${esc(field.placeholder ?? "")}"></label>`;
|
|
7848
|
+
};
|
|
7849
|
+
submitDialog_fn = function() {
|
|
7850
|
+
const body = this.root.querySelector(".dialog-body");
|
|
7851
|
+
if (!body) return;
|
|
7852
|
+
const result = {};
|
|
7853
|
+
for (const field of Array.from(body.querySelectorAll("[data-field]"))) {
|
|
7854
|
+
result[field.dataset.field ?? ""] = field.value.trim();
|
|
7855
|
+
}
|
|
7856
|
+
__privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, result);
|
|
7857
|
+
};
|
|
7858
|
+
openLinkDialog_fn = async function(existing) {
|
|
7859
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
7860
|
+
const editor = this.editor;
|
|
7861
|
+
if (!editor) return;
|
|
7862
|
+
if (existing) __privateMethod(this, _FxEditor_instances, selectAnchor_fn).call(this, existing);
|
|
7863
|
+
let range2 = getRange(editor, __privateGet(this, _lastRange));
|
|
7864
|
+
if (!range2) {
|
|
7865
|
+
placeCaretAtEnd(editor);
|
|
7866
|
+
range2 = getRange(editor);
|
|
7867
|
+
__privateSet(this, _lastRange, range2?.cloneRange() ?? null);
|
|
7868
|
+
}
|
|
7869
|
+
const anchor = existing ?? closestInEditor(editor, range2?.startContainer ?? null, "a");
|
|
7870
|
+
const selected = range2 && !range2.collapsed ? range2.toString() : anchor?.textContent ?? "";
|
|
7871
|
+
const result = await __privateMethod(this, _FxEditor_instances, openDialog_fn).call(this, "Link", [
|
|
7872
|
+
{ name: "url", label: "URL", type: "url", value: anchor?.getAttribute("href") ?? "", placeholder: "https://..." },
|
|
7873
|
+
{ name: "text", label: "Texto (quando não há seleção)", value: selected, placeholder: "Texto do link" },
|
|
7874
|
+
{
|
|
7875
|
+
name: "target",
|
|
7876
|
+
label: "Abrir em",
|
|
7877
|
+
type: "select",
|
|
7878
|
+
value: anchor?.getAttribute("target") ?? "_blank",
|
|
7879
|
+
options: [
|
|
7880
|
+
{ value: "_blank", label: "Nova aba" },
|
|
7881
|
+
{ value: "_self", label: "Mesma aba" }
|
|
7882
|
+
]
|
|
7883
|
+
}
|
|
7884
|
+
]);
|
|
7885
|
+
if (!result) return;
|
|
7886
|
+
const href = sanitizeLinkUrl(result.url ?? "");
|
|
7887
|
+
if (!href) {
|
|
7888
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "URL de link inválida.");
|
|
7889
|
+
return;
|
|
7890
|
+
}
|
|
7891
|
+
const target = result.target === "_self" ? "_self" : "_blank";
|
|
7892
|
+
const label = (result.text ?? "").trim() || (anchor ? void 0 : selected || href);
|
|
7893
|
+
if (anchor) {
|
|
7894
|
+
anchor.setAttribute("href", href);
|
|
7895
|
+
anchor.setAttribute("target", target);
|
|
7896
|
+
anchor.setAttribute("rel", "noopener noreferrer");
|
|
7897
|
+
if (label) anchor.textContent = label;
|
|
7898
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7899
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7900
|
+
return;
|
|
7901
|
+
}
|
|
7902
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7903
|
+
applyLink(editor, href, label ?? (selected || href), target);
|
|
7904
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7905
|
+
};
|
|
7906
|
+
openImageDialog_fn = async function(existing) {
|
|
7907
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
7908
|
+
const figure = existing?.closest("figure");
|
|
7909
|
+
const caption = figure?.querySelector("figcaption")?.textContent ?? "";
|
|
7910
|
+
const alignValue = figure?.getAttribute("data-align") ?? "center";
|
|
7911
|
+
const widthValue = figure?.style.width?.replace("%", "") || "100";
|
|
7912
|
+
const dialog = __privateMethod(this, _FxEditor_instances, openDialog_fn).call(this, existing ? "Editar imagem" : "Inserir imagem", [
|
|
7913
|
+
{ name: "src", label: "URL da imagem", type: "url", value: existing?.getAttribute("src") ?? "", placeholder: "https://..." },
|
|
7914
|
+
{ name: "alt", label: "Texto alternativo", value: existing?.getAttribute("alt") ?? "" },
|
|
7915
|
+
{ name: "caption", label: "Legenda", value: caption },
|
|
7916
|
+
{
|
|
7917
|
+
name: "width",
|
|
7918
|
+
label: "Largura",
|
|
7919
|
+
type: "select",
|
|
7920
|
+
value: widthValue,
|
|
7921
|
+
options: [
|
|
7922
|
+
{ value: "25", label: "25%" },
|
|
7923
|
+
{ value: "50", label: "50%" },
|
|
7924
|
+
{ value: "75", label: "75%" },
|
|
7925
|
+
{ value: "100", label: "100%" }
|
|
7926
|
+
]
|
|
7927
|
+
},
|
|
7928
|
+
{
|
|
7929
|
+
name: "align",
|
|
7930
|
+
label: "Alinhamento",
|
|
7931
|
+
type: "select",
|
|
7932
|
+
value: alignValue,
|
|
7933
|
+
options: [
|
|
7934
|
+
{ value: "left", label: "Esquerda" },
|
|
7935
|
+
{ value: "center", label: "Centro" },
|
|
7936
|
+
{ value: "right", label: "Direita" }
|
|
7937
|
+
]
|
|
7938
|
+
}
|
|
7939
|
+
]);
|
|
7940
|
+
const body = this.root.querySelector(".dialog-body");
|
|
7941
|
+
if (body) {
|
|
7942
|
+
const upload = document.createElement("button");
|
|
7943
|
+
upload.type = "button";
|
|
7944
|
+
upload.className = "btn";
|
|
7945
|
+
upload.textContent = "Escolher arquivo…";
|
|
7946
|
+
upload.addEventListener("click", () => {
|
|
7947
|
+
__privateMethod(this, _FxEditor_instances, releaseDialog_fn).call(this, null);
|
|
7948
|
+
__privateGet(this, _imagePicker)?.click();
|
|
7949
|
+
});
|
|
7950
|
+
body.appendChild(upload);
|
|
7951
|
+
}
|
|
7952
|
+
const result = await dialog;
|
|
7953
|
+
if (!result) return;
|
|
7954
|
+
const src = sanitizeMediaUrl(result.src ?? "", "img");
|
|
7955
|
+
if (!src) {
|
|
7956
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "URL de imagem inválida.");
|
|
7957
|
+
return;
|
|
7958
|
+
}
|
|
7959
|
+
if (existing && figure) {
|
|
7960
|
+
existing.setAttribute("src", src);
|
|
7961
|
+
existing.setAttribute("alt", result.alt ?? "");
|
|
7962
|
+
figure.setAttribute("data-align", result.align || "center");
|
|
7963
|
+
figure.style.width = `${result.width || 100}%`;
|
|
7964
|
+
const cap = figure.querySelector("figcaption");
|
|
7965
|
+
if (result.caption) {
|
|
7966
|
+
if (cap) cap.textContent = result.caption;
|
|
7967
|
+
else figure.insertAdjacentHTML("beforeend", `<figcaption>${escapeText(result.caption)}</figcaption>`);
|
|
7968
|
+
} else {
|
|
7969
|
+
cap?.remove();
|
|
7970
|
+
}
|
|
7971
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
7972
|
+
return;
|
|
7973
|
+
}
|
|
7974
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
7975
|
+
__privateMethod(this, _FxEditor_instances, insertImage_fn).call(this, src, result.alt ?? "", result.caption ?? "", result.width || "100", result.align || "center");
|
|
7976
|
+
};
|
|
7977
|
+
toEmbedUrl_fn = function(value) {
|
|
7978
|
+
try {
|
|
7979
|
+
const url = new URL(value);
|
|
7980
|
+
const host = url.hostname.replace(/^www\./, "");
|
|
7981
|
+
if (host === "youtu.be") {
|
|
7982
|
+
return `https://www.youtube-nocookie.com/embed/${url.pathname.slice(1)}`;
|
|
7983
|
+
}
|
|
7984
|
+
if (host === "youtube.com" || host === "m.youtube.com") {
|
|
7985
|
+
const id = url.searchParams.get("v");
|
|
7986
|
+
if (id) return `https://www.youtube-nocookie.com/embed/${id}`;
|
|
7987
|
+
if (url.pathname.startsWith("/shorts/")) {
|
|
7988
|
+
return `https://www.youtube-nocookie.com/embed/${url.pathname.split("/")[2] ?? ""}`;
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
if (host === "vimeo.com") {
|
|
7992
|
+
const id = url.pathname.split("/").filter(Boolean)[0];
|
|
7993
|
+
if (id) return `https://player.vimeo.com/video/${id}`;
|
|
7994
|
+
}
|
|
7995
|
+
return value;
|
|
7996
|
+
} catch {
|
|
7997
|
+
return value;
|
|
7998
|
+
}
|
|
7999
|
+
};
|
|
8000
|
+
openVideoDialog_fn = async function() {
|
|
8001
|
+
const result = await __privateMethod(this, _FxEditor_instances, openDialog_fn).call(this, "Inserir vídeo", [
|
|
8002
|
+
{ name: "src", label: "URL do vídeo ou embed", type: "url", placeholder: "https://.../video.mp4 ou YouTube/Vimeo" },
|
|
8003
|
+
{ name: "caption", label: "Legenda", value: "" },
|
|
8004
|
+
{
|
|
8005
|
+
name: "width",
|
|
8006
|
+
label: "Largura",
|
|
8007
|
+
type: "select",
|
|
8008
|
+
value: "100",
|
|
8009
|
+
options: [
|
|
8010
|
+
{ value: "50", label: "50%" },
|
|
8011
|
+
{ value: "75", label: "75%" },
|
|
8012
|
+
{ value: "100", label: "100%" }
|
|
8013
|
+
]
|
|
8014
|
+
},
|
|
8015
|
+
{
|
|
8016
|
+
name: "align",
|
|
8017
|
+
label: "Alinhamento",
|
|
8018
|
+
type: "select",
|
|
8019
|
+
value: "center",
|
|
8020
|
+
options: [
|
|
8021
|
+
{ value: "left", label: "Esquerda" },
|
|
8022
|
+
{ value: "center", label: "Centro" },
|
|
8023
|
+
{ value: "right", label: "Direita" }
|
|
8024
|
+
]
|
|
8025
|
+
}
|
|
8026
|
+
]);
|
|
8027
|
+
if (!result) return;
|
|
8028
|
+
const src = result.src ?? "";
|
|
8029
|
+
const embed = sanitizeEmbedUrl(__privateMethod(this, _FxEditor_instances, toEmbedUrl_fn).call(this, src));
|
|
8030
|
+
if (embed) {
|
|
8031
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
8032
|
+
__privateMethod(this, _FxEditor_instances, insertEmbed_fn).call(this, embed, result.caption ?? "", result.width || "100", result.align || "center");
|
|
8033
|
+
return;
|
|
8034
|
+
}
|
|
8035
|
+
const media = sanitizeMediaUrl(src, "video");
|
|
8036
|
+
if (!media) {
|
|
8037
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "URL de vídeo inválida.");
|
|
8038
|
+
return;
|
|
8039
|
+
}
|
|
8040
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
8041
|
+
__privateMethod(this, _FxEditor_instances, insertVideo_fn).call(this, media, result.caption ?? "", result.width || "100", result.align || "center");
|
|
8042
|
+
};
|
|
8043
|
+
decorateMediaFigures_fn = function() {
|
|
8044
|
+
const editor = this.editor;
|
|
8045
|
+
if (!editor || __privateGet(this, _sourceMode)) return;
|
|
8046
|
+
for (const figure of Array.from(
|
|
8047
|
+
editor.querySelectorAll("figure.fx-image, figure.fx-video, figure.fx-embed")
|
|
8048
|
+
)) {
|
|
8049
|
+
if (figure.querySelector(":scope > .fx-media-quick")) {
|
|
8050
|
+
__privateMethod(this, _FxEditor_instances, syncQuickActions_fn).call(this, figure);
|
|
8051
|
+
continue;
|
|
8052
|
+
}
|
|
8053
|
+
for (const kind of ["left", "center", "right", "delete"]) {
|
|
8054
|
+
const label = kind === "delete" ? "Excluir mídia" : kind === "left" ? "Alinhar à esquerda" : kind === "center" ? "Centralizar" : "Alinhar à direita";
|
|
8055
|
+
const button = document.createElement("button");
|
|
8056
|
+
button.type = "button";
|
|
8057
|
+
button.className = `fx-media-quick quick-${kind}`;
|
|
8058
|
+
button.title = label;
|
|
8059
|
+
button.setAttribute("aria-label", label);
|
|
8060
|
+
if (kind === "delete") button.dataset.mediaDelete = "";
|
|
8061
|
+
else button.dataset.mediaAlign = kind;
|
|
8062
|
+
button.innerHTML = QUICK_SVG[kind];
|
|
8063
|
+
button.addEventListener("click", (event) => {
|
|
8064
|
+
event.preventDefault();
|
|
8065
|
+
event.stopPropagation();
|
|
8066
|
+
if (kind === "delete") {
|
|
8067
|
+
figure.remove();
|
|
8068
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
8069
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8070
|
+
} else {
|
|
8071
|
+
figure.setAttribute("data-align", kind);
|
|
8072
|
+
__privateMethod(this, _FxEditor_instances, syncQuickActions_fn).call(this, figure);
|
|
8073
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8074
|
+
}
|
|
8075
|
+
});
|
|
8076
|
+
figure.appendChild(button);
|
|
8077
|
+
}
|
|
8078
|
+
__privateMethod(this, _FxEditor_instances, syncQuickActions_fn).call(this, figure);
|
|
8079
|
+
}
|
|
8080
|
+
};
|
|
8081
|
+
syncQuickActions_fn = function(figure) {
|
|
8082
|
+
const align2 = figure.getAttribute("data-align") || "center";
|
|
8083
|
+
for (const button of Array.from(
|
|
8084
|
+
figure.querySelectorAll(":scope > .fx-media-quick[data-media-align]")
|
|
8085
|
+
)) {
|
|
8086
|
+
button.classList.toggle("active", button.dataset.mediaAlign === align2);
|
|
8087
|
+
}
|
|
8088
|
+
};
|
|
8089
|
+
selectAnchor_fn = function(anchor) {
|
|
8090
|
+
const editor = this.editor;
|
|
8091
|
+
if (!editor) return;
|
|
8092
|
+
editor.focus({ preventScroll: true });
|
|
8093
|
+
const range2 = document.createRange();
|
|
8094
|
+
range2.selectNodeContents(anchor);
|
|
8095
|
+
const selection = document.getSelection();
|
|
8096
|
+
if (!selection) return;
|
|
8097
|
+
selection.removeAllRanges();
|
|
8098
|
+
selection.addRange(range2);
|
|
8099
|
+
__privateSet(this, _lastRange, range2.cloneRange());
|
|
8100
|
+
};
|
|
8101
|
+
handleLinkOverlayClick_fn = function(event) {
|
|
8102
|
+
const action = event.target.closest("[data-link-action]");
|
|
8103
|
+
const anchor = __privateGet(this, _linkAnchor);
|
|
8104
|
+
if (!action || !anchor) return;
|
|
8105
|
+
event.preventDefault();
|
|
8106
|
+
event.stopPropagation();
|
|
8107
|
+
const kind = action.dataset.linkAction;
|
|
8108
|
+
if (kind === "open") __privateMethod(this, _FxEditor_instances, openLink_fn).call(this, anchor);
|
|
8109
|
+
else if (kind === "edit") void __privateMethod(this, _FxEditor_instances, openLinkDialog_fn).call(this, anchor);
|
|
8110
|
+
else if (kind === "remove") __privateMethod(this, _FxEditor_instances, removeLink_fn).call(this, anchor);
|
|
8111
|
+
};
|
|
8112
|
+
openLink_fn = function(anchor) {
|
|
8113
|
+
const href = anchor.getAttribute("href") ?? "";
|
|
8114
|
+
if (!href) return;
|
|
8115
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
8116
|
+
const target = anchor.getAttribute("target") === "_self" ? "_self" : "_blank";
|
|
8117
|
+
window.open(href, target, target === "_blank" ? "noopener,noreferrer" : void 0);
|
|
8118
|
+
};
|
|
8119
|
+
removeLink_fn = function(anchor) {
|
|
8120
|
+
const parent = anchor.parentNode;
|
|
8121
|
+
if (!parent) return;
|
|
8122
|
+
while (anchor.firstChild) parent.insertBefore(anchor.firstChild, anchor);
|
|
8123
|
+
anchor.remove();
|
|
8124
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
8125
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8126
|
+
this.editor?.focus({ preventScroll: true });
|
|
8127
|
+
};
|
|
8128
|
+
showLinkOverlay_fn = function(anchor) {
|
|
8129
|
+
__privateSet(this, _linkAnchor, anchor);
|
|
8130
|
+
const overlay = this.root.querySelector(".link-overlay");
|
|
8131
|
+
if (!overlay) return;
|
|
8132
|
+
const target = anchor.getAttribute("target") === "_self" ? "mesma janela" : "nova aba";
|
|
8133
|
+
const open = overlay.querySelector('[data-link-action="open"]');
|
|
8134
|
+
if (open) open.title = `Abrir na ${target}`;
|
|
8135
|
+
overlay.hidden = false;
|
|
8136
|
+
__privateMethod(this, _FxEditor_instances, positionLinkOverlay_fn).call(this);
|
|
8137
|
+
};
|
|
8138
|
+
hideLinkOverlay_fn = function() {
|
|
8139
|
+
__privateSet(this, _linkAnchor, null);
|
|
8140
|
+
const overlay = this.root.querySelector(".link-overlay");
|
|
8141
|
+
if (overlay) overlay.hidden = true;
|
|
8142
|
+
};
|
|
8143
|
+
positionLinkOverlay_fn = function() {
|
|
8144
|
+
const overlay = this.root.querySelector(".link-overlay");
|
|
8145
|
+
const anchor = __privateGet(this, _linkAnchor);
|
|
8146
|
+
const editor = this.editor;
|
|
8147
|
+
if (!overlay) return;
|
|
8148
|
+
if (!anchor || !editor || __privateGet(this, _sourceMode) || !anchor.isConnected || !editor.contains(anchor)) {
|
|
8149
|
+
overlay.hidden = true;
|
|
8150
|
+
return;
|
|
8151
|
+
}
|
|
8152
|
+
const body = overlay.parentElement;
|
|
8153
|
+
if (!body) return;
|
|
8154
|
+
const bodyRect = body.getBoundingClientRect();
|
|
8155
|
+
const rect = anchor.getBoundingClientRect();
|
|
8156
|
+
if (!rect.width || !rect.height || rect.bottom < bodyRect.top || rect.top > bodyRect.bottom) {
|
|
8157
|
+
overlay.hidden = true;
|
|
8158
|
+
return;
|
|
8159
|
+
}
|
|
8160
|
+
overlay.hidden = false;
|
|
8161
|
+
const width = overlay.offsetWidth || 180;
|
|
8162
|
+
const height = overlay.offsetHeight || 30;
|
|
8163
|
+
const left = Math.max(4, Math.min(rect.left - bodyRect.left, bodyRect.width - width - 4));
|
|
8164
|
+
let top = rect.bottom - bodyRect.top + 6;
|
|
8165
|
+
if (top + height > bodyRect.height) top = rect.top - bodyRect.top - height - 6;
|
|
8166
|
+
overlay.style.left = `${left}px`;
|
|
8167
|
+
overlay.style.top = `${Math.max(4, top)}px`;
|
|
8168
|
+
};
|
|
8169
|
+
handleEditorClick_fn = function(event) {
|
|
8170
|
+
const target = event.target;
|
|
8171
|
+
const anchor = target.closest("a[href]");
|
|
8172
|
+
if (anchor && this.editor?.contains(anchor)) {
|
|
8173
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
8174
|
+
__privateMethod(this, _FxEditor_instances, showLinkOverlay_fn).call(this, anchor);
|
|
8175
|
+
if (event.ctrlKey || event.metaKey) {
|
|
8176
|
+
event.preventDefault();
|
|
8177
|
+
__privateMethod(this, _FxEditor_instances, openLink_fn).call(this, anchor);
|
|
8178
|
+
}
|
|
8179
|
+
return;
|
|
8180
|
+
}
|
|
8181
|
+
__privateMethod(this, _FxEditor_instances, hideLinkOverlay_fn).call(this);
|
|
8182
|
+
const figure = target.closest("figure.fx-image, figure.fx-video, figure.fx-embed");
|
|
8183
|
+
if (figure) __privateMethod(this, _FxEditor_instances, showMediaOverlay_fn).call(this, figure);
|
|
8184
|
+
else __privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
8185
|
+
};
|
|
8186
|
+
showMediaOverlay_fn = function(figure) {
|
|
8187
|
+
__privateSet(this, _mediaFigure, figure);
|
|
8188
|
+
figure.setAttribute("tabindex", "0");
|
|
8189
|
+
if (figure.classList.contains("fx-image")) figure.focus({ preventScroll: true });
|
|
8190
|
+
const overlay = this.root.querySelector(".media-overlay");
|
|
8191
|
+
if (!overlay) return;
|
|
8192
|
+
overlay.hidden = false;
|
|
8193
|
+
__privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
|
|
8194
|
+
};
|
|
8195
|
+
hideMediaOverlay_fn = function() {
|
|
8196
|
+
__privateGet(this, _mediaFigure)?.removeAttribute("tabindex");
|
|
8197
|
+
__privateSet(this, _mediaFigure, null);
|
|
8198
|
+
const overlay = this.root.querySelector(".media-overlay");
|
|
8199
|
+
if (!overlay) return;
|
|
8200
|
+
overlay.hidden = true;
|
|
8201
|
+
const size = overlay.querySelector(".media-size");
|
|
8202
|
+
if (size) size.textContent = "";
|
|
8203
|
+
};
|
|
8204
|
+
deleteMediaFigure_fn = function() {
|
|
8205
|
+
const figure = __privateGet(this, _mediaFigure);
|
|
8206
|
+
if (!figure) return;
|
|
8207
|
+
figure.remove();
|
|
8208
|
+
__privateMethod(this, _FxEditor_instances, hideMediaOverlay_fn).call(this);
|
|
8209
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8210
|
+
this.editor?.focus({ preventScroll: true });
|
|
8211
|
+
};
|
|
8212
|
+
positionMediaOverlay_fn = function() {
|
|
8213
|
+
const overlay = this.root.querySelector(".media-overlay");
|
|
8214
|
+
const figure = __privateGet(this, _mediaFigure);
|
|
8215
|
+
const editor = this.editor;
|
|
8216
|
+
if (!overlay) return;
|
|
8217
|
+
if (!figure || !editor || __privateGet(this, _sourceMode) || !figure.isConnected || !editor.contains(figure)) {
|
|
8218
|
+
overlay.hidden = true;
|
|
8219
|
+
return;
|
|
8220
|
+
}
|
|
8221
|
+
const body = overlay.parentElement;
|
|
8222
|
+
if (!body) return;
|
|
8223
|
+
const bodyRect = body.getBoundingClientRect();
|
|
8224
|
+
const rect = figure.getBoundingClientRect();
|
|
8225
|
+
if (!rect.width || !rect.height || rect.bottom < bodyRect.top || rect.top > bodyRect.bottom) {
|
|
8226
|
+
overlay.hidden = true;
|
|
8227
|
+
return;
|
|
8228
|
+
}
|
|
8229
|
+
overlay.hidden = false;
|
|
8230
|
+
overlay.style.left = `${rect.left - bodyRect.left}px`;
|
|
8231
|
+
overlay.style.top = `${rect.top - bodyRect.top}px`;
|
|
8232
|
+
overlay.style.width = `${rect.width}px`;
|
|
8233
|
+
overlay.style.height = `${rect.height}px`;
|
|
8234
|
+
const size = overlay.querySelector(".media-size");
|
|
8235
|
+
if (size) size.textContent = `${Math.round(rect.width)} × ${Math.round(rect.height)}`;
|
|
8236
|
+
};
|
|
8237
|
+
startResize_fn = function(event) {
|
|
8238
|
+
const handle = event.target.closest("[data-handle]");
|
|
8239
|
+
const figure = __privateGet(this, _mediaFigure);
|
|
8240
|
+
const editor = this.editor;
|
|
8241
|
+
if (!handle || !figure || !editor) return;
|
|
8242
|
+
event.preventDefault();
|
|
8243
|
+
event.stopPropagation();
|
|
8244
|
+
__privateMethod(this, _FxEditor_instances, endResize_fn).call(this);
|
|
8245
|
+
const parent = figure.parentElement;
|
|
8246
|
+
if (!parent) return;
|
|
8247
|
+
const parentWidth = parent.getBoundingClientRect().width || editor.clientWidth;
|
|
8248
|
+
if (!parentWidth) return;
|
|
8249
|
+
const rect = figure.getBoundingClientRect();
|
|
8250
|
+
const startWidth = rect.width ? rect.width / parentWidth * 100 : Number.parseFloat(figure.style.width) || 100;
|
|
8251
|
+
const startX = event.clientX;
|
|
8252
|
+
const direction = handle.dataset.handle?.includes("w") ? -1 : 1;
|
|
8253
|
+
const move = (moveEvent) => {
|
|
8254
|
+
const deltaPct = (moveEvent.clientX - startX) * direction * 100 / parentWidth;
|
|
8255
|
+
const width = Math.min(100, Math.max(15, startWidth + deltaPct));
|
|
8256
|
+
figure.style.width = `${width.toFixed(1).replace(/\.0$/, "")}%`;
|
|
8257
|
+
__privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
|
|
8258
|
+
};
|
|
8259
|
+
const up = () => {
|
|
8260
|
+
__privateMethod(this, _FxEditor_instances, endResize_fn).call(this);
|
|
8261
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8262
|
+
__privateMethod(this, _FxEditor_instances, positionMediaOverlay_fn).call(this);
|
|
8263
|
+
};
|
|
8264
|
+
window.addEventListener("pointermove", move);
|
|
8265
|
+
window.addEventListener("pointerup", up, { once: true });
|
|
8266
|
+
window.addEventListener("pointercancel", up, { once: true });
|
|
8267
|
+
__privateSet(this, _resizeDispose, () => {
|
|
8268
|
+
window.removeEventListener("pointermove", move);
|
|
8269
|
+
window.removeEventListener("pointerup", up);
|
|
8270
|
+
window.removeEventListener("pointercancel", up);
|
|
8271
|
+
});
|
|
8272
|
+
};
|
|
8273
|
+
endResize_fn = function() {
|
|
8274
|
+
var _a;
|
|
8275
|
+
(_a = __privateGet(this, _resizeDispose)) == null ? void 0 : _a.call(this);
|
|
8276
|
+
__privateSet(this, _resizeDispose, null);
|
|
8277
|
+
};
|
|
8278
|
+
handleDoubleClick_fn = function(event) {
|
|
8279
|
+
const target = event.target;
|
|
8280
|
+
const image = target.closest("img");
|
|
8281
|
+
if (image) {
|
|
8282
|
+
event.preventDefault();
|
|
8283
|
+
void __privateMethod(this, _FxEditor_instances, openImageDialog_fn).call(this, image);
|
|
8284
|
+
}
|
|
8285
|
+
};
|
|
8286
|
+
insertImage_fn = function(src, alt, caption, width, align2) {
|
|
8287
|
+
const html = `<figure class="fx-image" contenteditable="false" data-align="${escapeAttr(align2)}" style="width:${escapeAttr(width)}%"><img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}" loading="lazy" decoding="async">${caption ? `<figcaption>${escapeText(caption)}</figcaption>` : ""}</figure><p><br></p>`;
|
|
8288
|
+
insertHtml(this.editor, html);
|
|
8289
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8290
|
+
};
|
|
8291
|
+
insertVideo_fn = function(src, caption, width, align2 = "center") {
|
|
8292
|
+
const html = `<figure class="fx-video" contenteditable="false" data-align="${escapeAttr(align2)}" style="width:${escapeAttr(width)}%"><video controls preload="metadata" src="${escapeAttr(src)}" style="width:100%"></video>${caption ? `<figcaption>${escapeText(caption)}</figcaption>` : ""}</figure><p><br></p>`;
|
|
8293
|
+
insertHtml(this.editor, html);
|
|
8294
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8295
|
+
};
|
|
8296
|
+
insertEmbed_fn = function(src, caption, width, align2 = "center") {
|
|
8297
|
+
const html = `<figure class="fx-embed" contenteditable="false" data-align="${escapeAttr(align2)}" style="width:${escapeAttr(width)}%"><iframe src="${escapeAttr(src)}" title="${escapeAttr(caption || "Vídeo")}" loading="lazy" allowfullscreen sandbox="allow-scripts allow-same-origin allow-presentation" referrerpolicy="strict-origin-when-cross-origin" style="width:100%;aspect-ratio:16/9;border:0"></iframe>${caption ? `<figcaption>${escapeText(caption)}</figcaption>` : ""}</figure><p><br></p>`;
|
|
8298
|
+
insertHtml(this.editor, html);
|
|
8299
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8300
|
+
};
|
|
8301
|
+
insertTable_fn = function(rows, cols) {
|
|
8302
|
+
const rowCount = Math.min(20, Math.max(1, rows));
|
|
8303
|
+
const colCount = Math.min(12, Math.max(1, cols));
|
|
8304
|
+
const width = (100 / colCount).toFixed(4).replace(/\.?0+$/, "");
|
|
8305
|
+
const colsHtml = `<colgroup>${Array.from({ length: colCount }, () => `<col style="width:${width}%">`).join("")}</colgroup>`;
|
|
8306
|
+
const head = `<thead><tr>${Array.from({ length: colCount }, () => '<th scope="col"><p><br></p></th>').join("")}</tr></thead>`;
|
|
8307
|
+
const bodyRows = Math.max(0, rowCount - 1);
|
|
8308
|
+
const body = bodyRows ? `<tbody>${Array.from(
|
|
8309
|
+
{ length: bodyRows },
|
|
8310
|
+
() => `<tr>${Array.from({ length: colCount }, () => "<td><p><br></p></td>").join("")}</tr>`
|
|
8311
|
+
).join("")}</tbody>` : "";
|
|
8312
|
+
const html = `<figure class="fx-table"><table>${colsHtml}${head}${body}</table></figure><p><br></p>`;
|
|
8313
|
+
insertHtml(this.editor, html);
|
|
8314
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8315
|
+
};
|
|
8316
|
+
insertColumns_fn = function(cols) {
|
|
8317
|
+
const count = Math.min(4, Math.max(1, cols));
|
|
8318
|
+
const width = (100 / count).toFixed(4).replace(/\.?0+$/, "");
|
|
8319
|
+
const cells = Array.from(
|
|
8320
|
+
{ length: count },
|
|
8321
|
+
() => `<td style="width:${width}%;vertical-align:top;padding:8px"><p><br></p></td>`
|
|
8322
|
+
).join("");
|
|
8323
|
+
const html = `<table class="fx-grid" role="presentation" data-cols="${count}" data-stack="sm" style="width:100%;border-collapse:collapse"><tbody><tr>${cells}</tr></tbody></table><p><br></p>`;
|
|
8324
|
+
insertHtml(this.editor, html);
|
|
8325
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8326
|
+
};
|
|
8327
|
+
showTablePanel_fn = function() {
|
|
8328
|
+
const panel = this.root.querySelector("[data-panel]");
|
|
8329
|
+
if (!panel) return;
|
|
8330
|
+
const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
|
|
8331
|
+
const grid = Array.from({ length: 64 }, (_, index) => {
|
|
8332
|
+
const rows = Math.floor(index / 8) + 1;
|
|
8333
|
+
const cols = index % 8 + 1;
|
|
8334
|
+
return `<button type="button" data-grid="${rows},${cols}" aria-label="${rows} por ${cols}"></button>`;
|
|
8335
|
+
}).join("");
|
|
8336
|
+
panel.innerHTML = `<p class="panel-title">Inserir tabela</p><div class="grid-picker">${grid}</div>` + (table ? `<p class="panel-title" style="margin-top:8px">Tabela atual</p><div class="panel-actions"><button type="button" class="btn" data-table-action="add-row">+ Linha</button><button type="button" class="btn" data-table-action="add-col">+ Coluna</button><button type="button" class="btn" data-table-action="del-row">− Linha</button><button type="button" class="btn" data-table-action="del-col">− Coluna</button><button type="button" class="btn" data-table-action="toggle-header">Cabeçalho</button><button type="button" class="btn" data-table-action="merge-cells">Mesclar</button><button type="button" class="btn" data-table-action="split-cell">Dividir</button><button type="button" class="btn" data-table-action="del-table">Excluir tabela</button></div>` : "");
|
|
8337
|
+
panel.removeAttribute("hidden");
|
|
8338
|
+
panel.querySelectorAll("[data-grid]").forEach((button) => {
|
|
8339
|
+
button.addEventListener("click", () => {
|
|
8340
|
+
const [rows, cols] = (button.dataset.grid ?? "").split(",").map(Number);
|
|
8341
|
+
__privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
|
|
8342
|
+
__privateMethod(this, _FxEditor_instances, insertTable_fn).call(this, rows, cols);
|
|
8343
|
+
});
|
|
8344
|
+
});
|
|
8345
|
+
panel.querySelectorAll("[data-table-action]").forEach((button) => {
|
|
8346
|
+
button.addEventListener("click", () => {
|
|
8347
|
+
__privateMethod(this, _FxEditor_instances, tableAction_fn).call(this, button.dataset.tableAction ?? "");
|
|
8348
|
+
__privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
|
|
8349
|
+
});
|
|
8350
|
+
});
|
|
8351
|
+
};
|
|
8352
|
+
showColumnsPanel_fn = function() {
|
|
8353
|
+
const panel = this.root.querySelector("[data-panel]");
|
|
8354
|
+
if (!panel) return;
|
|
8355
|
+
panel.innerHTML = `<p class="panel-title">Inserir colunas</p><div class="panel-actions">` + [1, 2, 3, 4].map((count) => `<button type="button" class="btn" data-columns="${count}">${count}</button>`).join("") + `</div>`;
|
|
8356
|
+
panel.removeAttribute("hidden");
|
|
8357
|
+
panel.querySelectorAll("[data-columns]").forEach((button) => {
|
|
8358
|
+
button.addEventListener("click", () => {
|
|
8359
|
+
__privateMethod(this, _FxEditor_instances, hidePanel_fn).call(this);
|
|
8360
|
+
__privateMethod(this, _FxEditor_instances, insertColumns_fn).call(this, Number(button.dataset.columns ?? 1));
|
|
8361
|
+
});
|
|
8362
|
+
});
|
|
8363
|
+
};
|
|
8364
|
+
hidePanel_fn = function() {
|
|
8365
|
+
this.root.querySelector("[data-panel]")?.setAttribute("hidden", "");
|
|
8366
|
+
};
|
|
8367
|
+
currentTable_fn = function() {
|
|
8368
|
+
const editor = this.editor;
|
|
8369
|
+
if (!editor) return null;
|
|
8370
|
+
const range2 = getRange(editor, __privateGet(this, _lastRange));
|
|
8371
|
+
return closestInEditor(editor, range2?.startContainer ?? null, "table");
|
|
8372
|
+
};
|
|
8373
|
+
tableAction_fn = function(action) {
|
|
8374
|
+
const table = __privateMethod(this, _FxEditor_instances, currentTable_fn).call(this);
|
|
8375
|
+
if (!table) return;
|
|
8376
|
+
const editor = this.editor;
|
|
8377
|
+
const range2 = editor ? getRange(editor, __privateGet(this, _lastRange)) : null;
|
|
8378
|
+
const cell = editor ? closestInEditor(editor, range2?.startContainer ?? null, "td,th") : null;
|
|
8379
|
+
const row = cell?.parentElement;
|
|
8380
|
+
if (action === "merge-cells") {
|
|
8381
|
+
if (!range2) return;
|
|
8382
|
+
const merged = mergeSelectedCells(table, range2);
|
|
8383
|
+
if (!merged) return;
|
|
8384
|
+
placeCaretAtEnd(merged);
|
|
8385
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8386
|
+
return;
|
|
8387
|
+
}
|
|
8388
|
+
if (action === "split-cell") {
|
|
8389
|
+
if (!cell) return;
|
|
8390
|
+
const created = splitCell(table, cell);
|
|
8391
|
+
if (!created.length) return;
|
|
8392
|
+
placeCaretAtEnd(cell);
|
|
8393
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8394
|
+
return;
|
|
8395
|
+
}
|
|
8396
|
+
if (action === "add-row" && row) {
|
|
8397
|
+
const clone = row.cloneNode(true);
|
|
8398
|
+
for (const cloneCell of Array.from(clone.querySelectorAll("td,th"))) {
|
|
8399
|
+
cloneCell.innerHTML = "<p><br></p>";
|
|
8400
|
+
}
|
|
8401
|
+
row.after(clone);
|
|
8402
|
+
} else if (action === "add-col" && cell) {
|
|
8403
|
+
const index = Array.from(row?.children ?? []).indexOf(cell);
|
|
8404
|
+
for (const currentRow of Array.from(table.rows)) {
|
|
8405
|
+
const target = currentRow.cells[Math.max(0, index)];
|
|
8406
|
+
const clone = (target ?? currentRow.cells[currentRow.cells.length - 1])?.cloneNode(false);
|
|
8407
|
+
if (!clone) continue;
|
|
8408
|
+
clone.innerHTML = "<p><br></p>";
|
|
8409
|
+
currentRow.insertBefore(clone, target?.nextSibling ?? null);
|
|
8410
|
+
}
|
|
8411
|
+
} else if (action === "del-row" && row && table.rows.length > 1) {
|
|
8412
|
+
row.remove();
|
|
8413
|
+
} else if (action === "del-col" && cell && table.rows[0]?.cells.length > 1) {
|
|
8414
|
+
const index = Array.from(row?.children ?? []).indexOf(cell);
|
|
8415
|
+
for (const currentRow of Array.from(table.rows)) {
|
|
8416
|
+
currentRow.cells[index]?.remove();
|
|
8417
|
+
}
|
|
8418
|
+
} else if (action === "toggle-header") {
|
|
8419
|
+
const thead = table.tHead;
|
|
8420
|
+
if (thead && thead.rows.length) {
|
|
8421
|
+
const headerRow = thead.rows[0];
|
|
8422
|
+
for (const headerCell of Array.from(headerRow.cells)) {
|
|
8423
|
+
const td = document.createElement("td");
|
|
8424
|
+
td.innerHTML = headerCell.innerHTML;
|
|
8425
|
+
headerCell.replaceWith(td);
|
|
8426
|
+
}
|
|
8427
|
+
const tbody = table.tBodies[0] ?? table.createTBody();
|
|
8428
|
+
tbody.insertBefore(headerRow, tbody.firstChild);
|
|
8429
|
+
thead.remove();
|
|
8430
|
+
} else {
|
|
8431
|
+
const firstRow = table.rows[0];
|
|
8432
|
+
if (firstRow) {
|
|
8433
|
+
for (const firstCell of Array.from(firstRow.cells)) {
|
|
8434
|
+
const th = document.createElement("th");
|
|
8435
|
+
th.setAttribute("scope", "col");
|
|
8436
|
+
th.innerHTML = firstCell.innerHTML;
|
|
8437
|
+
firstCell.replaceWith(th);
|
|
8438
|
+
}
|
|
8439
|
+
table.createTHead().appendChild(firstRow);
|
|
8440
|
+
}
|
|
8441
|
+
}
|
|
8442
|
+
} else if (action === "del-table") {
|
|
8443
|
+
const figure = table.closest("figure.fx-table");
|
|
8444
|
+
(figure ?? table).remove();
|
|
8445
|
+
}
|
|
8446
|
+
__privateMethod(this, _FxEditor_instances, afterCommand_fn).call(this);
|
|
8447
|
+
};
|
|
8448
|
+
handleImageFile_fn = async function(file) {
|
|
8449
|
+
if (!file.type.startsWith("image/")) {
|
|
8450
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "O arquivo selecionado não é uma imagem.");
|
|
8451
|
+
return;
|
|
8452
|
+
}
|
|
8453
|
+
if (file.type === "image/svg+xml") {
|
|
8454
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "SVG não é permitido por segurança. Use PNG, JPEG, WebP ou GIF.");
|
|
8455
|
+
return;
|
|
8456
|
+
}
|
|
8457
|
+
if (file.size > MAX_IMAGE_BYTES) {
|
|
8458
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "Imagem muito grande (máximo 8 MB).");
|
|
8459
|
+
return;
|
|
8460
|
+
}
|
|
8461
|
+
let src = "";
|
|
8462
|
+
let alt = file.name.replace(/\.[a-z0-9]+$/i, "");
|
|
8463
|
+
try {
|
|
8464
|
+
if (this.uploadImage) {
|
|
8465
|
+
const uploaded = await this.uploadImage(file);
|
|
8466
|
+
if (typeof uploaded === "string") src = uploaded;
|
|
8467
|
+
else {
|
|
8468
|
+
src = uploaded.url;
|
|
8469
|
+
if (uploaded.alt) alt = uploaded.alt;
|
|
8470
|
+
}
|
|
8471
|
+
} else {
|
|
8472
|
+
src = await __privateMethod(this, _FxEditor_instances, fileToDataUrl_fn).call(this, file);
|
|
8473
|
+
}
|
|
8474
|
+
} catch {
|
|
8475
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "Não foi possível processar a imagem.");
|
|
8476
|
+
return;
|
|
8477
|
+
}
|
|
8478
|
+
if (!src || !sanitizeMediaUrl(src, "img")) {
|
|
8479
|
+
__privateMethod(this, _FxEditor_instances, error_fn).call(this, "A imagem resultante não tem uma URL segura.");
|
|
8480
|
+
return;
|
|
8481
|
+
}
|
|
8482
|
+
__privateMethod(this, _FxEditor_instances, restoreSelection_fn).call(this);
|
|
8483
|
+
__privateMethod(this, _FxEditor_instances, insertImage_fn).call(this, src, alt, "", "100", "center");
|
|
8484
|
+
};
|
|
8485
|
+
fileToDataUrl_fn = async function(file) {
|
|
8486
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
8487
|
+
const reader = new FileReader();
|
|
8488
|
+
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
8489
|
+
reader.onerror = () => reject(new Error("read-error"));
|
|
8490
|
+
reader.readAsDataURL(file);
|
|
8491
|
+
});
|
|
8492
|
+
if (file.type === "image/gif") return dataUrl;
|
|
8493
|
+
return new Promise((resolve) => {
|
|
8494
|
+
const image = new Image();
|
|
8495
|
+
image.onload = () => {
|
|
8496
|
+
const maxWidth = 1600;
|
|
8497
|
+
const scale = Math.min(1, maxWidth / (image.naturalWidth || maxWidth));
|
|
8498
|
+
if (scale >= 1) {
|
|
8499
|
+
resolve(dataUrl);
|
|
8500
|
+
return;
|
|
8501
|
+
}
|
|
8502
|
+
const canvas = document.createElement("canvas");
|
|
8503
|
+
canvas.width = Math.round((image.naturalWidth || maxWidth) * scale);
|
|
8504
|
+
canvas.height = Math.round((image.naturalHeight || maxWidth) * scale);
|
|
8505
|
+
const context = canvas.getContext("2d");
|
|
8506
|
+
if (!context) {
|
|
8507
|
+
resolve(dataUrl);
|
|
8508
|
+
return;
|
|
8509
|
+
}
|
|
8510
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
8511
|
+
resolve(canvas.toDataURL("image/webp", 0.85));
|
|
8512
|
+
};
|
|
8513
|
+
image.onerror = () => resolve(dataUrl);
|
|
8514
|
+
image.src = dataUrl;
|
|
8515
|
+
});
|
|
8516
|
+
};
|
|
8517
|
+
error_fn = function(message) {
|
|
8518
|
+
this.dispatchEvent(
|
|
8519
|
+
new CustomEvent("editor-error", {
|
|
8520
|
+
bubbles: true,
|
|
8521
|
+
composed: true,
|
|
8522
|
+
detail: { message }
|
|
8523
|
+
})
|
|
8524
|
+
);
|
|
8525
|
+
};
|
|
8526
|
+
_FxEditor.styles = `${FENIX_ICON_BASE_CSS}
|
|
8527
|
+
${EDITOR_STYLES}`;
|
|
8528
|
+
let FxEditor = _FxEditor;
|
|
8529
|
+
function defineFxEditor() {
|
|
8530
|
+
return defineElement("fx-editor", FxEditor);
|
|
8531
|
+
}
|
|
8532
|
+
defineFxEditor();
|
|
8533
|
+
const _FxDialog = class _FxDialog extends FxElement {
|
|
8534
|
+
constructor() {
|
|
8535
|
+
super(...arguments);
|
|
8536
|
+
this._previouslyFocused = null;
|
|
8537
|
+
}
|
|
8538
|
+
static get observedAttributes() {
|
|
8539
|
+
return ["open", "size", "heading"];
|
|
8540
|
+
}
|
|
8541
|
+
get open() {
|
|
8542
|
+
return this.hasAttr("open");
|
|
8543
|
+
}
|
|
8544
|
+
set open(value) {
|
|
8545
|
+
this.toggleAttr("open", value);
|
|
8546
|
+
}
|
|
8547
|
+
render() {
|
|
8548
|
+
const isOpen = this.open;
|
|
8549
|
+
const heading = this.getAttr("heading");
|
|
8550
|
+
this.setTemplate(`
|
|
8551
|
+
<div class="overlay" part="overlay" ${isOpen ? "" : "hidden"}>
|
|
8552
|
+
<div class="dialog" role="dialog" aria-modal="true" aria-label="${esc(heading)}" tabindex="-1">
|
|
8553
|
+
<header>
|
|
8554
|
+
<h2>${esc(heading)}</h2>
|
|
8555
|
+
<button type="button" class="close" part="close" aria-label="Fechar">×</button>
|
|
8556
|
+
</header>
|
|
8557
|
+
<div class="body"><slot></slot></div>
|
|
8558
|
+
<footer part="footer"><slot name="footer"></slot></footer>
|
|
8559
|
+
</div>
|
|
8560
|
+
</div>
|
|
8561
|
+
`);
|
|
8562
|
+
if (!isOpen) return;
|
|
8563
|
+
this._cleanup?.();
|
|
8564
|
+
this._cleanup = void 0;
|
|
8565
|
+
const overlay = this.root.querySelector(".overlay");
|
|
8566
|
+
const closeBtn = this.root.querySelector(".close");
|
|
8567
|
+
const close = () => {
|
|
8568
|
+
this.open = false;
|
|
8569
|
+
this._cleanup?.();
|
|
8570
|
+
this._cleanup = void 0;
|
|
8571
|
+
this._restoreFocus();
|
|
8572
|
+
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
8573
|
+
};
|
|
8574
|
+
overlay?.addEventListener("click", (e) => {
|
|
8575
|
+
if (e.target === overlay) close();
|
|
8576
|
+
});
|
|
8577
|
+
closeBtn?.addEventListener("click", close);
|
|
8578
|
+
const onKey = (e) => {
|
|
8579
|
+
if (e.key === "Escape") {
|
|
8580
|
+
e.stopPropagation();
|
|
8581
|
+
close();
|
|
8582
|
+
return;
|
|
8583
|
+
}
|
|
8584
|
+
if (e.key === "Tab") this._trapFocus(e);
|
|
8585
|
+
};
|
|
8586
|
+
document.addEventListener("keydown", onKey);
|
|
8587
|
+
this._cleanup = () => document.removeEventListener("keydown", onKey);
|
|
8588
|
+
requestAnimationFrame(() => {
|
|
8589
|
+
this._previouslyFocused = document.activeElement;
|
|
8590
|
+
this.root.querySelector(".dialog")?.focus();
|
|
8591
|
+
});
|
|
8592
|
+
this.dispatchEvent(new CustomEvent("open", { bubbles: true, composed: true }));
|
|
8593
|
+
}
|
|
8594
|
+
/** Mantém o foco dentro do modal (WCAG 2.4.3 / 2.1.2). */
|
|
8595
|
+
_trapFocus(e) {
|
|
8596
|
+
const dialog = this.root.querySelector(".dialog");
|
|
8597
|
+
if (!dialog) return;
|
|
8598
|
+
const focusables = dialog.querySelectorAll(
|
|
8599
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
8600
|
+
);
|
|
8601
|
+
if (!focusables.length) return;
|
|
8602
|
+
const first = focusables[0];
|
|
8603
|
+
const last = focusables[focusables.length - 1];
|
|
8604
|
+
const active = this.root.activeElement;
|
|
8605
|
+
if (e.shiftKey && (active === first || active === dialog)) {
|
|
8606
|
+
e.preventDefault();
|
|
8607
|
+
last.focus();
|
|
8608
|
+
} else if (!e.shiftKey && active === last) {
|
|
8609
|
+
e.preventDefault();
|
|
8610
|
+
first.focus();
|
|
8611
|
+
}
|
|
8612
|
+
}
|
|
8613
|
+
_restoreFocus() {
|
|
8614
|
+
if (this._previouslyFocused?.isConnected) this._previouslyFocused.focus();
|
|
8615
|
+
this._previouslyFocused = null;
|
|
8616
|
+
}
|
|
8617
|
+
disconnectedCallback() {
|
|
8618
|
+
super.disconnectedCallback();
|
|
8619
|
+
this._cleanup?.();
|
|
8620
|
+
this._cleanup = void 0;
|
|
8621
|
+
this._restoreFocus();
|
|
8622
|
+
}
|
|
8623
|
+
};
|
|
8624
|
+
_FxDialog.styles = css`
|
|
8625
|
+
:host { display: contents; }
|
|
8626
|
+
.overlay {
|
|
8627
|
+
position: fixed;
|
|
8628
|
+
inset: 0;
|
|
8629
|
+
background: color-mix(in srgb, var(--fx-text-default, #000) 45%, transparent);
|
|
8630
|
+
backdrop-filter: blur(3px);
|
|
8631
|
+
-webkit-backdrop-filter: blur(3px);
|
|
8632
|
+
display: flex;
|
|
8633
|
+
align-items: center;
|
|
8634
|
+
justify-content: center;
|
|
8635
|
+
z-index: var(--fx-z-modal, 1100);
|
|
8636
|
+
animation: fade var(--fx-motion-duration-fast) var(--fx-motion-easing);
|
|
8637
|
+
}
|
|
8638
|
+
.overlay[hidden] { display: none; }
|
|
8639
|
+
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
|
|
8640
|
+
.dialog {
|
|
8641
|
+
background: var(--fx-surface-background);
|
|
8642
|
+
border: 1px solid var(--fx-border-default);
|
|
8643
|
+
border-radius: var(--fx-radius-lg);
|
|
8644
|
+
box-shadow: var(--fx-shadow-xl);
|
|
8645
|
+
/* Largura customizável por instância: defina --fx-dialog-width no elemento. */
|
|
8646
|
+
width: min(var(--fx-dialog-width, 560px), calc(100vw - 32px));
|
|
8647
|
+
max-height: calc(100vh - 64px);
|
|
8648
|
+
display: flex;
|
|
8649
|
+
flex-direction: column;
|
|
8650
|
+
font-family: var(--fx-font-family);
|
|
8651
|
+
animation: pop var(--fx-motion-duration-normal) var(--fx-motion-easing);
|
|
8652
|
+
}
|
|
8653
|
+
@keyframes pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
|
|
8654
|
+
:host([size='sm']) .dialog { width: min(var(--fx-dialog-width, 400px), calc(100vw - 32px)); }
|
|
8655
|
+
:host([size='lg']) .dialog { width: min(var(--fx-dialog-width, 800px), calc(100vw - 32px)); }
|
|
8656
|
+
/* Mobile: dialog ocupa praticamente toda a largura da tela. */
|
|
8657
|
+
@media (max-width: 640px) {
|
|
8658
|
+
.dialog, :host([size='sm']) .dialog, :host([size='lg']) .dialog {
|
|
8659
|
+
width: calc(100vw - 16px);
|
|
8660
|
+
max-width: calc(100vw - 16px);
|
|
8661
|
+
}
|
|
8662
|
+
}
|
|
8663
|
+
header {
|
|
8664
|
+
display: flex;
|
|
8665
|
+
align-items: center;
|
|
8666
|
+
justify-content: space-between;
|
|
8667
|
+
padding: var(--fx-space-lg) var(--fx-space-xl);
|
|
8668
|
+
border-bottom: 1px solid var(--fx-border-default);
|
|
8669
|
+
}
|
|
8670
|
+
h2 {
|
|
8671
|
+
margin: 0;
|
|
8672
|
+
font-size: calc(var(--fx-font-size) + 2px);
|
|
8673
|
+
font-weight: 600;
|
|
8674
|
+
color: var(--fx-text-default);
|
|
8675
|
+
}
|
|
8676
|
+
.close {
|
|
8677
|
+
border: none;
|
|
8678
|
+
background: transparent;
|
|
8679
|
+
color: var(--fx-text-muted);
|
|
8680
|
+
font-size: calc(var(--fx-font-size) + 6px);
|
|
8681
|
+
cursor: pointer;
|
|
8682
|
+
line-height: 1;
|
|
8683
|
+
padding: 0 var(--fx-space-xs);
|
|
8684
|
+
border-radius: var(--fx-radius-full);
|
|
8685
|
+
}
|
|
8686
|
+
.close:hover { color: var(--fx-color-danger); }
|
|
8687
|
+
.body {
|
|
8688
|
+
padding: var(--fx-space-lg) var(--fx-space-xl);
|
|
8689
|
+
overflow-y: auto;
|
|
8690
|
+
color: var(--fx-text-default);
|
|
8691
|
+
font-size: var(--fx-font-size);
|
|
8692
|
+
}
|
|
8693
|
+
footer {
|
|
8694
|
+
padding: var(--fx-space-md) var(--fx-space-xl);
|
|
8695
|
+
border-top: 1px solid var(--fx-border-default);
|
|
8696
|
+
display: flex;
|
|
8697
|
+
justify-content: flex-end;
|
|
8698
|
+
gap: var(--fx-space-sm);
|
|
8699
|
+
}
|
|
8700
|
+
footer[hidden] { display: none; }
|
|
5056
8701
|
`;
|
|
5057
8702
|
let FxDialog = _FxDialog;
|
|
5058
8703
|
function defineFxDialog() {
|
|
@@ -7854,12 +11499,13 @@ const CARD_CSS = `
|
|
|
7854
11499
|
border-left: 4px solid var(--kind);
|
|
7855
11500
|
border-radius: var(--fx-radius-md, 8px);
|
|
7856
11501
|
box-shadow: var(--fx-shadow-lg, 0 10px 30px rgba(0,0,0,.14));
|
|
7857
|
-
padding: var(--fx-space-
|
|
11502
|
+
padding: var(--fx-space-xs, 8px) var(--fx-space-md, 12px);
|
|
7858
11503
|
font-family: inherit;
|
|
7859
11504
|
animation: fx-toast-in .25s ease;
|
|
7860
11505
|
}
|
|
7861
11506
|
@keyframes fx-toast-in { from { opacity: 0; transform: translateY(-6px); } }
|
|
7862
11507
|
.card.leaving { opacity: 0; transition: opacity .2s ease; }
|
|
11508
|
+
.card.no-title { min-height: 48px; }
|
|
7863
11509
|
.icon {
|
|
7864
11510
|
width: 22px; height: 22px; border-radius: 50%;
|
|
7865
11511
|
background: var(--kind);
|
|
@@ -7966,7 +11612,16 @@ class FxToast extends HTMLElement {
|
|
|
7966
11612
|
this.attachShadow({ mode: "open" });
|
|
7967
11613
|
}
|
|
7968
11614
|
static get observedAttributes() {
|
|
7969
|
-
return [
|
|
11615
|
+
return [
|
|
11616
|
+
"kind",
|
|
11617
|
+
"title",
|
|
11618
|
+
"message",
|
|
11619
|
+
"duration",
|
|
11620
|
+
"icon",
|
|
11621
|
+
"mode",
|
|
11622
|
+
"progress",
|
|
11623
|
+
"progress-origin"
|
|
11624
|
+
];
|
|
7970
11625
|
}
|
|
7971
11626
|
/**
|
|
7972
11627
|
* Exibe a barra de contagem regressiva na base do card.
|
|
@@ -8043,7 +11698,9 @@ class FxToast extends HTMLElement {
|
|
|
8043
11698
|
this.disposed = true;
|
|
8044
11699
|
if (this.timer) clearTimeout(this.timer);
|
|
8045
11700
|
this.timer = null;
|
|
8046
|
-
this.dispatchEvent(
|
|
11701
|
+
this.dispatchEvent(
|
|
11702
|
+
new CustomEvent(REMOVED_EVENT, { bubbles: false, composed: false })
|
|
11703
|
+
);
|
|
8047
11704
|
}
|
|
8048
11705
|
attributeChangedCallback(name) {
|
|
8049
11706
|
if (name === "duration") {
|
|
@@ -8053,7 +11710,8 @@ class FxToast extends HTMLElement {
|
|
|
8053
11710
|
}
|
|
8054
11711
|
const safe = this.safeDuration;
|
|
8055
11712
|
this.counting = safe > 0;
|
|
8056
|
-
if (safe > 0 && this.isConnected && !this.disposed)
|
|
11713
|
+
if (safe > 0 && this.isConnected && !this.disposed)
|
|
11714
|
+
__privateMethod(this, _FxToast_instances, startTimer_fn).call(this, safe);
|
|
8057
11715
|
}
|
|
8058
11716
|
if (name === "duration" || name === "progress" || name === "progress-origin" || name === "kind") {
|
|
8059
11717
|
__privateMethod(this, _FxToast_instances, syncProgress_fn).call(this);
|
|
@@ -8105,7 +11763,10 @@ pauseTimer_fn = function() {
|
|
|
8105
11763
|
if (!this.timer) return;
|
|
8106
11764
|
clearTimeout(this.timer);
|
|
8107
11765
|
this.timer = null;
|
|
8108
|
-
this.remaining = Math.max(
|
|
11766
|
+
this.remaining = Math.max(
|
|
11767
|
+
0,
|
|
11768
|
+
this.remaining - (Date.now() - this.startedAt)
|
|
11769
|
+
);
|
|
8109
11770
|
};
|
|
8110
11771
|
resumeTimer_fn = function() {
|
|
8111
11772
|
if (!this.counting || this.timer || this.disposed) return;
|
|
@@ -8135,15 +11796,23 @@ syncProgress_fn = function() {
|
|
|
8135
11796
|
return;
|
|
8136
11797
|
}
|
|
8137
11798
|
if (!bar) {
|
|
8138
|
-
this.shadowRoot.querySelector(".card").insertAdjacentHTML(
|
|
11799
|
+
this.shadowRoot.querySelector(".card").insertAdjacentHTML(
|
|
11800
|
+
"beforeend",
|
|
11801
|
+
'<span class="progress" part="progress" aria-hidden="true"></span>'
|
|
11802
|
+
);
|
|
8139
11803
|
}
|
|
8140
11804
|
const el = this.shadowRoot.querySelector(".progress");
|
|
8141
11805
|
el.style.setProperty("--toast-duration", `${dur}ms`);
|
|
8142
|
-
el.style.setProperty(
|
|
11806
|
+
el.style.setProperty(
|
|
11807
|
+
"--toast-progress-origin",
|
|
11808
|
+
`${this.progressOrigin} center`
|
|
11809
|
+
);
|
|
8143
11810
|
el.classList.remove("run");
|
|
8144
11811
|
if (mode !== "paused") el.classList.add("run");
|
|
8145
11812
|
card.classList.add("entering");
|
|
8146
|
-
requestAnimationFrame(
|
|
11813
|
+
requestAnimationFrame(
|
|
11814
|
+
() => requestAnimationFrame(() => card.classList.remove("entering"))
|
|
11815
|
+
);
|
|
8147
11816
|
};
|
|
8148
11817
|
render_fn = function() {
|
|
8149
11818
|
const kind = this.getAttribute("kind") || "info";
|
|
@@ -8156,7 +11825,7 @@ render_fn = function() {
|
|
|
8156
11825
|
const iconContent = isFxIcon ? iconName : KIND_ICON[kind] ?? KIND_ICON.info;
|
|
8157
11826
|
this.shadowRoot.innerHTML = `
|
|
8158
11827
|
<style>${CARD_CSS}</style>
|
|
8159
|
-
<div class="card" style="--kind:${color}" role="status" part="card">
|
|
11828
|
+
<div class="card${title ? "" : " no-title"}" style="--kind:${color}" role="status" part="card">
|
|
8160
11829
|
<span class="icon${isFxIcon ? " fx-icon" : ""}">${iconContent}</span>
|
|
8161
11830
|
<div class="body">
|
|
8162
11831
|
${title ? '<div class="title" part="title"></div>' : ""}
|
|
@@ -8196,7 +11865,10 @@ render_fn = function() {
|
|
|
8196
11865
|
btn.title = "Copiar mensagem";
|
|
8197
11866
|
}, 1200);
|
|
8198
11867
|
});
|
|
8199
|
-
this.shadowRoot.querySelector(".close").addEventListener(
|
|
11868
|
+
this.shadowRoot.querySelector(".close").addEventListener(
|
|
11869
|
+
"click",
|
|
11870
|
+
() => this.dismiss()
|
|
11871
|
+
);
|
|
8200
11872
|
};
|
|
8201
11873
|
/**
|
|
8202
11874
|
* Mostra "Ver mais" somente quando a mensagem passou de 2 linhas.
|
|
@@ -8214,7 +11886,9 @@ syncMore_fn = function() {
|
|
|
8214
11886
|
more.hidden = !(msg.scrollHeight - msg.clientHeight > 1);
|
|
8215
11887
|
};
|
|
8216
11888
|
function regionFor(position) {
|
|
8217
|
-
let region = document.querySelector(
|
|
11889
|
+
let region = document.querySelector(
|
|
11890
|
+
`[data-fx-toast-region="${position}"]`
|
|
11891
|
+
);
|
|
8218
11892
|
if (!region) {
|
|
8219
11893
|
region = document.createElement("div");
|
|
8220
11894
|
region.setAttribute("data-fx-toast-region", position);
|
|
@@ -8249,7 +11923,10 @@ class ToastApi {
|
|
|
8249
11923
|
push(kind, title, messageOrOpts, opts) {
|
|
8250
11924
|
const id = ++this.seq;
|
|
8251
11925
|
const message = typeof messageOrOpts === "string" ? messageOrOpts : messageOrOpts?.message;
|
|
8252
|
-
const o = {
|
|
11926
|
+
const o = {
|
|
11927
|
+
...typeof messageOrOpts === "object" && messageOrOpts ? messageOrOpts : void 0,
|
|
11928
|
+
...opts
|
|
11929
|
+
};
|
|
8253
11930
|
const el = document.createElement("fx-toast");
|
|
8254
11931
|
el.setAttribute("kind", kind);
|
|
8255
11932
|
el.setAttribute("title", title);
|
|
@@ -8274,7 +11951,8 @@ class ToastApi {
|
|
|
8274
11951
|
const FenixToast = new ToastApi();
|
|
8275
11952
|
globalThis.FenixToast = FenixToast;
|
|
8276
11953
|
function defineFxToast() {
|
|
8277
|
-
if (!customElements.get("fx-toast"))
|
|
11954
|
+
if (!customElements.get("fx-toast"))
|
|
11955
|
+
customElements.define("fx-toast", FxToast);
|
|
8278
11956
|
}
|
|
8279
11957
|
defineFxToast();
|
|
8280
11958
|
const _FxTooltip = class _FxTooltip extends FxElement {
|
|
@@ -9476,17 +13154,16 @@ const _FxPagination = class _FxPagination extends FxElement {
|
|
|
9476
13154
|
const pages = this.pages;
|
|
9477
13155
|
const current = Math.min(Math.max(1, this.page), pages);
|
|
9478
13156
|
const opts = (this.getAttr("rows-options", "5,10,20,50") || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
9479
|
-
const
|
|
9480
|
-
|
|
9481
|
-
|
|
13157
|
+
const pageButtons = pageRange(current, pages).map(
|
|
13158
|
+
(item) => item === "ellipsis" ? `<span class="ellipsis" part="ellipsis" aria-hidden="true">…</span>` : `<button type="button" class="nav ${item === current ? "active" : ""}" part="page" data-go="${item}"${item === current ? ' aria-current="page"' : ""}>${item}</button>`
|
|
13159
|
+
).join("");
|
|
13160
|
+
const from = (current - 1) * this.rows + 1;
|
|
9482
13161
|
const to = Math.min(current * this.rows, this.total);
|
|
9483
13162
|
this.setTemplate(`
|
|
9484
13163
|
<span class="info">${this.total ? `${from}-${to} de ${this.total}` : "0 itens"}</span>
|
|
9485
13164
|
<button type="button" class="nav" part="first" aria-label="Primeira página" data-go="1" ${current <= 1 ? "disabled" : ""}>${fenixIconHtml("first_page")}</button>
|
|
9486
13165
|
<button type="button" class="nav" part="prev" aria-label="Página anterior" data-go="${current - 1}" ${current <= 1 ? "disabled" : ""}>${fenixIconHtml("navigate_before")}</button>
|
|
9487
|
-
${
|
|
9488
|
-
${window_.map((p) => `<button type="button" class="nav ${p === current ? "active" : ""}" data-go="${p}">${p}</button>`).join("")}
|
|
9489
|
-
${window_[window_.length - 1] !== pages && pages > 5 ? `<button type="button" class="nav" data-go="${pages}">${pages}</button>` : ""}
|
|
13166
|
+
${pageButtons}
|
|
9490
13167
|
<button type="button" class="nav" part="next" aria-label="Próxima página" data-go="${current + 1}" ${current >= pages ? "disabled" : ""}>${fenixIconHtml("navigate_next")}</button>
|
|
9491
13168
|
<button type="button" class="nav" part="last" aria-label="Última página" data-go="${pages}" ${current >= pages ? "disabled" : ""}>${fenixIconHtml("last_page")}</button>
|
|
9492
13169
|
${opts.length ? `
|
|
@@ -9494,9 +13171,6 @@ const _FxPagination = class _FxPagination extends FxElement {
|
|
|
9494
13171
|
${opts.map((o) => `<option value="${esc(o)}">${esc(o)}</option>`).join("")}
|
|
9495
13172
|
</fx-select>` : ""}
|
|
9496
13173
|
`);
|
|
9497
|
-
function total0(page, rows) {
|
|
9498
|
-
return (page - 1) * rows + 1;
|
|
9499
|
-
}
|
|
9500
13174
|
this.root.querySelectorAll(".nav[data-go]").forEach((btn) => {
|
|
9501
13175
|
btn.addEventListener("click", () => {
|
|
9502
13176
|
const target = Number(btn.dataset.go);
|
|
@@ -9528,6 +13202,7 @@ _FxPagination.styles = css`
|
|
|
9528
13202
|
:host {
|
|
9529
13203
|
display: flex;
|
|
9530
13204
|
align-items: center;
|
|
13205
|
+
flex-wrap: wrap;
|
|
9531
13206
|
gap: var(--fx-space-sm);
|
|
9532
13207
|
font-family: var(--fx-font-family);
|
|
9533
13208
|
font-size: var(--fx-font-size);
|
|
@@ -9565,6 +13240,18 @@ _FxPagination.styles = css`
|
|
|
9565
13240
|
color: #fff;
|
|
9566
13241
|
font-weight: 600;
|
|
9567
13242
|
}
|
|
13243
|
+
/* Elipse entre a janela de páginas e as bordas: ocupa o mesmo espaço de
|
|
13244
|
+
um botão (para o rodapé não "pular" ao navegar) mas não é clicável. */
|
|
13245
|
+
.ellipsis {
|
|
13246
|
+
display: inline-flex;
|
|
13247
|
+
align-items: center;
|
|
13248
|
+
justify-content: center;
|
|
13249
|
+
min-width: var(--fx-size-sm);
|
|
13250
|
+
height: var(--fx-size-sm);
|
|
13251
|
+
color: var(--fx-text-muted);
|
|
13252
|
+
font-size: calc(var(--fx-font-size) - 2px);
|
|
13253
|
+
user-select: none;
|
|
13254
|
+
}
|
|
9568
13255
|
.info { color: var(--fx-text-muted); font-size: calc(var(--fx-font-size) - 2px); }
|
|
9569
13256
|
fx-select { vertical-align: middle; }
|
|
9570
13257
|
fx-select::part(trigger) {
|
|
@@ -9900,8 +13587,8 @@ const _FxKnob = class _FxKnob extends FxFormElement {
|
|
|
9900
13587
|
const min = this.min;
|
|
9901
13588
|
const max = this.max;
|
|
9902
13589
|
const value = this.clampValue(this.value);
|
|
9903
|
-
const
|
|
9904
|
-
const pct =
|
|
13590
|
+
const range2 = max - min;
|
|
13591
|
+
const pct = range2 > 0 ? (value - min) / range2 : 0;
|
|
9905
13592
|
const sizeNum = this.getSizeNum();
|
|
9906
13593
|
const strokeW = this.strokeWidth;
|
|
9907
13594
|
const radius = (sizeNum - strokeW) / 2;
|
|
@@ -11792,6 +15479,7 @@ defineFxAccordion();
|
|
|
11792
15479
|
defineFxAccordionPanel();
|
|
11793
15480
|
export {
|
|
11794
15481
|
DEFAULT_PRESET,
|
|
15482
|
+
FX_EDITOR_CONTENT_CSS,
|
|
11795
15483
|
FX_JSX_TYPES,
|
|
11796
15484
|
FenixToast,
|
|
11797
15485
|
FenixUI,
|
|
@@ -11814,6 +15502,7 @@ export {
|
|
|
11814
15502
|
FxDrawer,
|
|
11815
15503
|
FxDropdown,
|
|
11816
15504
|
FxDropdownItem,
|
|
15505
|
+
FxEditor,
|
|
11817
15506
|
FxElement,
|
|
11818
15507
|
FxEmptyState,
|
|
11819
15508
|
FxFileUpload,
|
|
@@ -11863,8 +15552,15 @@ export {
|
|
|
11863
15552
|
kebabToCamel,
|
|
11864
15553
|
lightTokens,
|
|
11865
15554
|
listPresets,
|
|
15555
|
+
normalizeForEditor,
|
|
15556
|
+
normalizeForStorage,
|
|
15557
|
+
normalizeHtml,
|
|
11866
15558
|
resetTheme,
|
|
11867
15559
|
h as sanitizeData,
|
|
15560
|
+
sanitizeEmbedUrl,
|
|
15561
|
+
sanitizeHtml,
|
|
15562
|
+
sanitizeLinkUrl,
|
|
15563
|
+
sanitizeMediaUrl,
|
|
11868
15564
|
setTokens,
|
|
11869
15565
|
j as stripTags,
|
|
11870
15566
|
theme,
|