@threadlabs/looma 0.31.0 → 0.31.2
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/components/shared/icons.js +1 -0
- package/components/shared/tooltip.js +7 -0
- package/components/ui-editor-footnote-editor/ui-editor-footnote-editor.html +57 -0
- package/components/ui-editor-footnote-editor/ui-editor-footnote-editor.js +38 -0
- package/components/ui-icon/ui-icon.html +1 -0
- package/components/ui-tooltip/ui-tooltip.js +4 -1
- package/dist/index.js +37 -36
- package/editor/chunks/extensions.js +363 -20
- package/editor/expressmark.js +1 -1
- package/editor/extensions/footnote.d.ts +40 -7
- package/editor/extensions/index.d.ts +1 -1
- package/editor/extensions/index.js +2 -2
- package/editor/footnote-editing.d.ts +46 -0
- package/editor/icons.d.ts +1 -0
- package/editor/index.js +4 -3
- package/editor/table-ui.d.ts +2 -11
- package/editor/toolbar-tooltip.d.ts +1 -1
- package/package.json +1 -1
- package/styles/ui-editor-footnote-editor.css +1 -0
- package/vanilla/UiEditorFootnoteEditor.d.ts +30 -0
- package/vanilla/UiEditorFootnoteEditor.js +101 -0
- package/vanilla/UiIcon.js +1 -1
- package/vanilla/index.js +1 -0
- package/vue/UiEditorFootnoteEditor.d.ts +57 -0
- package/vue/UiEditorFootnoteEditor.js +2 -0
- package/vue/UiEditorFootnoteEditor.vue +304 -0
- package/vue/UiIcon.vue +4 -0
- package/vue/chunks/UiEditorFootnoteEditor.js +306 -0
- package/vue/chunks/UiEditorImageToolbar.js +1 -1
- package/vue/chunks/UiEditorLinkForm.js +1 -1
- package/vue/chunks/UiIcon.js +15 -1
- package/vue/components.css +590 -575
- package/vue/editor/index.js +643 -45
- package/vue/editor/primitives.d.ts +3 -1
- package/vue/index.d.ts +1 -0
- package/vue/index.js +5 -4
package/vue/editor/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, shallowRef, useId, watch } from "vue";
|
|
1
|
+
import { Teleport, computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, shallowRef, useId, watch } from "vue";
|
|
2
2
|
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeTableOfContentsConfig } from "@threadlabs/looma/editor";
|
|
3
|
-
import { Button, Card, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Popover, Stack, StatusMessage, Text, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
-
import { Editor, Extension, Node as Node$1, getText, getTextSerializersFromSchema, isNodeSelection, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
3
|
+
import { Button, Card, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorFootnoteEditor, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Popover, Stack, StatusMessage, Text, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
+
import { Editor, Extension, InputRule, Node as Node$1, PasteRule, getText, getTextSerializersFromSchema, isNodeSelection, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
5
5
|
import { Editor as Editor$1 } from "@tiptap/vue-3";
|
|
6
|
-
import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
6
|
+
import { AllSelection, NodeSelection, Plugin, PluginKey, Selection, TextSelection } from "@tiptap/pm/state";
|
|
7
7
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
8
8
|
import { createLowlight } from "lowlight";
|
|
9
9
|
import { RequestEvent, componentRegistry } from "@nextwebwg/html-next/runtime";
|
|
10
10
|
import "@tiptap/pm/tables";
|
|
11
|
+
import { DOMParser, DOMSerializer, Fragment as Fragment$1, Slice } from "@tiptap/pm/model";
|
|
11
12
|
import "@tiptap/pm/transform";
|
|
12
13
|
import { closeHistory } from "@tiptap/pm/history";
|
|
13
|
-
import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
|
|
14
14
|
import { IndexedList, NONCONFORMING, READY_INPUT, UNRESOLVED_INPUT, acceptProps, activateModule, armElement, attachGeneratedController, awaitInput, buildTemplate, checkBoolean, checkConstrained, checkFormat, checkInteger, checkNumber, checkObject, checkSelected, checkString, clearRegion, computedCycle, defineComponent as defineComponent$1, detailCheck, dispatchDeclared, dispose, evaluateInput, fillSlot, formatOf, generatedInputs, hostState, inputRead, keywordFormat, manageProps, notifyPropertySet, project, readIndex, readMember, readonlyView, readsChanged, rec, replaceRoot, textCall, toAttribute, toText, trackContainer, truthy, writeAttribute, writeUrlAttribute } from "@nextwebwg/html-next/generated-runtime";
|
|
15
15
|
export * from "@threadlabs/looma/editor";
|
|
16
16
|
//#region \0rolldown/runtime.js
|
|
@@ -43,7 +43,8 @@ var EDITOR_ADAPTER_COMPONENT_TAG_MAP = {
|
|
|
43
43
|
EditorLinkForm: "ui-editor-link-form",
|
|
44
44
|
EditorBlockActions: "ui-editor-block-actions",
|
|
45
45
|
EditorCodeLanguage: "ui-editor-code-language",
|
|
46
|
-
EditorChipEditor: "ui-editor-chip-editor"
|
|
46
|
+
EditorChipEditor: "ui-editor-chip-editor",
|
|
47
|
+
EditorFootnoteEditor: "ui-editor-footnote-editor"
|
|
47
48
|
};
|
|
48
49
|
//#endregion
|
|
49
50
|
//#region src/components/shared/overlay.js
|
|
@@ -1678,6 +1679,528 @@ function createChipEditing(options) {
|
|
|
1678
1679
|
};
|
|
1679
1680
|
}
|
|
1680
1681
|
//#endregion
|
|
1682
|
+
//#region src/editor/extensions/footnote.ts
|
|
1683
|
+
var labelFrom = (href, prefix) => href?.startsWith(`#${prefix}`) ? href.slice(prefix.length + 1) : null;
|
|
1684
|
+
/** A footnote label as Looma Markdown reads it in `[^label]`: no whitespace and no `]`. */
|
|
1685
|
+
var LOOMA_FOOTNOTE_LABEL = /^[^\s\]]+$/;
|
|
1686
|
+
var REFERENCE = "loomaFootnoteReference";
|
|
1687
|
+
var NOTES = "loomaFootnotes";
|
|
1688
|
+
var NOTE = "loomaFootnote";
|
|
1689
|
+
var isLabel = (label) => LOOMA_FOOTNOTE_LABEL.test(label);
|
|
1690
|
+
/** Every label a reference or note in `doc` uses. */
|
|
1691
|
+
function usedLabels(doc) {
|
|
1692
|
+
const labels = /* @__PURE__ */ new Set();
|
|
1693
|
+
doc.descendants((node) => {
|
|
1694
|
+
if (node.type.name === REFERENCE || node.type.name === NOTE) labels.add(String(node.attrs.label));
|
|
1695
|
+
});
|
|
1696
|
+
return labels;
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* The first note for `label` in `doc` and its position.
|
|
1700
|
+
* @failure Returns null when no note has that label.
|
|
1701
|
+
*/
|
|
1702
|
+
function findLoomaFootnote(doc, label) {
|
|
1703
|
+
let found = null;
|
|
1704
|
+
doc.descendants((node, position) => {
|
|
1705
|
+
if (found) return false;
|
|
1706
|
+
if (node.type.name === NOTE && node.attrs.label === label) {
|
|
1707
|
+
found = {
|
|
1708
|
+
node,
|
|
1709
|
+
position
|
|
1710
|
+
};
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
return found;
|
|
1715
|
+
}
|
|
1716
|
+
function mapReferences(node, change) {
|
|
1717
|
+
if (node.type.name === REFERENCE) return change(node);
|
|
1718
|
+
if (node.isLeaf) return node;
|
|
1719
|
+
const children = [];
|
|
1720
|
+
node.content.forEach((child) => children.push(mapReferences(child, change)));
|
|
1721
|
+
return node.copy(Fragment$1.from(children));
|
|
1722
|
+
}
|
|
1723
|
+
function noteAt(doc, position) {
|
|
1724
|
+
const $position = doc.resolve(position);
|
|
1725
|
+
for (let depth = $position.depth; depth > 0; depth--) {
|
|
1726
|
+
const node = $position.node(depth);
|
|
1727
|
+
if (node.type.name === NOTE) return {
|
|
1728
|
+
node,
|
|
1729
|
+
start: $position.before(depth)
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Keeps footnotes on Looma Markdown's rule, so the saved HTML is the HTML Looma Markdown renders for
|
|
1736
|
+
* the same document. Notes are numbered by first reference, in the text and then in the notes, and
|
|
1737
|
+
* every reference carries its note's number and its place among that note's references. The notes
|
|
1738
|
+
* live in one `loomaFootnotes` section at the end of the document, in number order. A note no
|
|
1739
|
+
* reference points to leaves the document; a reference with no note gets one back, the note it last
|
|
1740
|
+
* had in this editor (so cutting a reference and pasting it keeps its note) or an empty one.
|
|
1741
|
+
* @invariant Changing nothing but text inside a note never rebuilds the section.
|
|
1742
|
+
*/
|
|
1743
|
+
function footnoteTransaction(state, known) {
|
|
1744
|
+
const { doc, schema } = state;
|
|
1745
|
+
const sections = [];
|
|
1746
|
+
const notes = /* @__PURE__ */ new Map();
|
|
1747
|
+
const body = [];
|
|
1748
|
+
doc.descendants((node, position) => {
|
|
1749
|
+
if (node.type.name === NOTES) {
|
|
1750
|
+
sections.push({
|
|
1751
|
+
node,
|
|
1752
|
+
position
|
|
1753
|
+
});
|
|
1754
|
+
node.forEach((note) => {
|
|
1755
|
+
if (!notes.has(note.attrs.label)) notes.set(note.attrs.label, note);
|
|
1756
|
+
});
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
if (node.type.name === REFERENCE) body.push({
|
|
1760
|
+
node,
|
|
1761
|
+
position
|
|
1762
|
+
});
|
|
1763
|
+
});
|
|
1764
|
+
if (!sections.length && !body.length) return null;
|
|
1765
|
+
const order = [];
|
|
1766
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1767
|
+
const numbered = (reference) => {
|
|
1768
|
+
const label = String(reference.attrs.label);
|
|
1769
|
+
if (!counts.has(label)) order.push(label);
|
|
1770
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
1771
|
+
const attrs = {
|
|
1772
|
+
...reference.attrs,
|
|
1773
|
+
number: order.indexOf(label) + 1,
|
|
1774
|
+
occurrence: counts.get(label)
|
|
1775
|
+
};
|
|
1776
|
+
return attrs.number === reference.attrs.number && attrs.occurrence === reference.attrs.occurrence ? reference : reference.type.create(attrs, null, reference.marks);
|
|
1777
|
+
};
|
|
1778
|
+
const transaction = state.tr;
|
|
1779
|
+
for (const { node, position } of body) {
|
|
1780
|
+
const next = numbered(node);
|
|
1781
|
+
if (next !== node) transaction.setNodeMarkup(position, void 0, next.attrs);
|
|
1782
|
+
}
|
|
1783
|
+
const built = [];
|
|
1784
|
+
for (let index = 0; index < order.length; index++) {
|
|
1785
|
+
const label = order[index];
|
|
1786
|
+
const note = notes.get(label) ?? known.get(label) ?? schema.nodes[NOTE].create({ label }, schema.nodes.paragraph.create());
|
|
1787
|
+
built.push(mapReferences(note, numbered));
|
|
1788
|
+
}
|
|
1789
|
+
const wanted = built.map((note) => note.attrs.count === counts.get(note.attrs.label) ? note : note.type.create({
|
|
1790
|
+
...note.attrs,
|
|
1791
|
+
count: counts.get(note.attrs.label)
|
|
1792
|
+
}, note.content));
|
|
1793
|
+
for (const note of [...notes.values(), ...wanted]) known.set(note.attrs.label, note);
|
|
1794
|
+
const section = wanted.length ? schema.nodes[NOTES].create(null, wanted) : null;
|
|
1795
|
+
const last = doc.lastChild?.type.name === NOTES ? {
|
|
1796
|
+
node: doc.lastChild,
|
|
1797
|
+
position: doc.content.size - doc.lastChild.nodeSize
|
|
1798
|
+
} : null;
|
|
1799
|
+
if (!(section && last && sections.length === 1 && last.node.eq(section)) && (sections.length || section)) {
|
|
1800
|
+
const { selection } = state;
|
|
1801
|
+
const inside = noteAt(doc, selection.from);
|
|
1802
|
+
const keep = inside && selection.to <= inside.start + inside.node.nodeSize ? {
|
|
1803
|
+
label: inside.node.attrs.label,
|
|
1804
|
+
start: inside.start
|
|
1805
|
+
} : null;
|
|
1806
|
+
for (const { node, position } of [...sections].reverse()) if (node !== last?.node) transaction.delete(position, position + node.nodeSize);
|
|
1807
|
+
if (last) {
|
|
1808
|
+
const position = transaction.mapping.map(last.position);
|
|
1809
|
+
if (section) transaction.replaceWith(position, position + last.node.nodeSize, section);
|
|
1810
|
+
else transaction.delete(position, position + last.node.nodeSize);
|
|
1811
|
+
} else if (section) transaction.insert(transaction.doc.content.size, section);
|
|
1812
|
+
const moved = keep && findLoomaFootnote(transaction.doc, keep.label);
|
|
1813
|
+
if (keep && moved) {
|
|
1814
|
+
const json = selection.toJSON();
|
|
1815
|
+
json.anchor += moved.position - keep.start;
|
|
1816
|
+
if (json.head !== void 0) json.head += moved.position - keep.start;
|
|
1817
|
+
try {
|
|
1818
|
+
transaction.setSelection(Selection.fromJSON(transaction.doc, json));
|
|
1819
|
+
} catch {}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
return transaction.docChanged ? transaction : null;
|
|
1823
|
+
}
|
|
1824
|
+
function appendBackLinks(item, label, number, count) {
|
|
1825
|
+
const document = item.ownerDocument;
|
|
1826
|
+
let paragraph = item.lastElementChild;
|
|
1827
|
+
if (paragraph?.nodeName !== "P") item.append(paragraph = document.createElement("p"));
|
|
1828
|
+
for (let occurrence = 1; occurrence <= count; occurrence++) {
|
|
1829
|
+
const link = document.createElement("a");
|
|
1830
|
+
link.setAttribute("href", `#fnref:${label}${occurrence > 1 ? `:${occurrence}` : ""}`);
|
|
1831
|
+
link.setAttribute("data-footnote-backref", "");
|
|
1832
|
+
link.setAttribute("aria-label", `Back to reference ${number}${occurrence > 1 ? `-${occurrence}` : ""}`);
|
|
1833
|
+
link.append("↩");
|
|
1834
|
+
if (occurrence > 1) link.append(Object.assign(document.createElement("sup"), { textContent: String(occurrence) }));
|
|
1835
|
+
paragraph.append(" ", link);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
var FootnoteSerializer = class extends DOMSerializer {
|
|
1839
|
+
serializeFragment(fragment, options = {}, target) {
|
|
1840
|
+
const dom = super.serializeFragment(fragment, options, target);
|
|
1841
|
+
fragment.forEach((node, _offset, index) => {
|
|
1842
|
+
if (node.type.name === NOTE) appendBackLinks(dom.childNodes[index], String(node.attrs.label), index + 1, Number(node.attrs.count) || 1);
|
|
1843
|
+
});
|
|
1844
|
+
return dom;
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
function installSerializer(schema) {
|
|
1848
|
+
if (!(schema.cached.domSerializer instanceof FootnoteSerializer)) schema.cached.domSerializer = new FootnoteSerializer(DOMSerializer.nodesFromSchema(schema), DOMSerializer.marksFromSchema(schema));
|
|
1849
|
+
}
|
|
1850
|
+
Node$1.create({
|
|
1851
|
+
name: REFERENCE,
|
|
1852
|
+
group: "inline",
|
|
1853
|
+
inline: true,
|
|
1854
|
+
atom: true,
|
|
1855
|
+
selectable: true,
|
|
1856
|
+
addAttributes() {
|
|
1857
|
+
const link = (element) => element.querySelector(":scope > a[data-footnote-ref]");
|
|
1858
|
+
const label = (element) => labelFrom(link(element)?.getAttribute("href"), "fn:") ?? "";
|
|
1859
|
+
return {
|
|
1860
|
+
label: {
|
|
1861
|
+
default: "",
|
|
1862
|
+
parseHTML: label,
|
|
1863
|
+
renderHTML: () => ({})
|
|
1864
|
+
},
|
|
1865
|
+
number: {
|
|
1866
|
+
default: null,
|
|
1867
|
+
parseHTML: (element) => Number(element.textContent) || null,
|
|
1868
|
+
renderHTML: () => ({})
|
|
1869
|
+
},
|
|
1870
|
+
occurrence: {
|
|
1871
|
+
default: 1,
|
|
1872
|
+
parseHTML: (element) => Number(link(element)?.id.slice(`fnref:${label(element)}:`.length)) || 1,
|
|
1873
|
+
renderHTML: () => ({})
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
},
|
|
1877
|
+
parseHTML() {
|
|
1878
|
+
return [{
|
|
1879
|
+
tag: "sup",
|
|
1880
|
+
priority: 1100,
|
|
1881
|
+
getAttrs: (element) => element.querySelector(":scope > a[data-footnote-ref]") ? null : false
|
|
1882
|
+
}];
|
|
1883
|
+
},
|
|
1884
|
+
renderHTML({ node }) {
|
|
1885
|
+
const { label, number, occurrence } = node.attrs;
|
|
1886
|
+
return ["sup", [
|
|
1887
|
+
"a",
|
|
1888
|
+
{
|
|
1889
|
+
href: `#fn:${label}`,
|
|
1890
|
+
id: `fnref:${label}${occurrence > 1 ? `:${occurrence}` : ""}`,
|
|
1891
|
+
"data-footnote-ref": "",
|
|
1892
|
+
"aria-describedby": "footnote:label"
|
|
1893
|
+
},
|
|
1894
|
+
String(number ?? label)
|
|
1895
|
+
]];
|
|
1896
|
+
},
|
|
1897
|
+
renderText({ node }) {
|
|
1898
|
+
return `[^${node.attrs.label}]`;
|
|
1899
|
+
},
|
|
1900
|
+
addInputRules() {
|
|
1901
|
+
return [new InputRule({
|
|
1902
|
+
find: /\[\^([^\s\]]+)\]$/,
|
|
1903
|
+
handler: ({ state, range, match }) => {
|
|
1904
|
+
state.tr.replaceWith(range.from, range.to, this.type.create({ label: match[1] }));
|
|
1905
|
+
}
|
|
1906
|
+
})];
|
|
1907
|
+
},
|
|
1908
|
+
addPasteRules() {
|
|
1909
|
+
return [new PasteRule({
|
|
1910
|
+
find: /\[\^([^\s\]]+)\]/g,
|
|
1911
|
+
handler: ({ state, range, match }) => {
|
|
1912
|
+
state.tr.replaceWith(range.from, range.to, this.type.create({ label: match[1] }));
|
|
1913
|
+
}
|
|
1914
|
+
})];
|
|
1915
|
+
},
|
|
1916
|
+
addNodeView() {
|
|
1917
|
+
return ({ node, editor }) => {
|
|
1918
|
+
const dom = document.createElement("sup");
|
|
1919
|
+
dom.dataset.loomaFootnoteRef = "";
|
|
1920
|
+
dom.contentEditable = "false";
|
|
1921
|
+
dom.setAttribute("role", "button");
|
|
1922
|
+
dom.setAttribute("aria-haspopup", "dialog");
|
|
1923
|
+
dom.setAttribute("aria-disabled", String(!editor.isEditable));
|
|
1924
|
+
dom.tabIndex = 0;
|
|
1925
|
+
const number = dom.appendChild(document.createElement("a"));
|
|
1926
|
+
const update = (next) => {
|
|
1927
|
+
if (next.type !== node.type) return false;
|
|
1928
|
+
const shown = String(next.attrs.number ?? next.attrs.label);
|
|
1929
|
+
dom.dataset.label = String(next.attrs.label);
|
|
1930
|
+
if (number.textContent !== shown) number.textContent = shown;
|
|
1931
|
+
dom.setAttribute("aria-label", `Footnote ${shown}`);
|
|
1932
|
+
return true;
|
|
1933
|
+
};
|
|
1934
|
+
update(node);
|
|
1935
|
+
return {
|
|
1936
|
+
dom,
|
|
1937
|
+
update,
|
|
1938
|
+
stopEvent: (event) => event.type === "click" || event.type === "keydown"
|
|
1939
|
+
};
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
Node$1.create({
|
|
1944
|
+
name: NOTES,
|
|
1945
|
+
group: "block",
|
|
1946
|
+
content: `${NOTE}+`,
|
|
1947
|
+
isolating: true,
|
|
1948
|
+
parseHTML() {
|
|
1949
|
+
return [{
|
|
1950
|
+
tag: "section[data-footnotes]",
|
|
1951
|
+
contentElement: "ol"
|
|
1952
|
+
}];
|
|
1953
|
+
},
|
|
1954
|
+
renderHTML() {
|
|
1955
|
+
return [
|
|
1956
|
+
"section",
|
|
1957
|
+
{
|
|
1958
|
+
"data-footnotes": "",
|
|
1959
|
+
class: "footnotes"
|
|
1960
|
+
},
|
|
1961
|
+
[
|
|
1962
|
+
"h2",
|
|
1963
|
+
{
|
|
1964
|
+
id: "footnote:label",
|
|
1965
|
+
class: "sr-only"
|
|
1966
|
+
},
|
|
1967
|
+
"Footnotes"
|
|
1968
|
+
],
|
|
1969
|
+
["ol", 0]
|
|
1970
|
+
];
|
|
1971
|
+
},
|
|
1972
|
+
onBeforeCreate() {
|
|
1973
|
+
installSerializer(this.editor.schema);
|
|
1974
|
+
},
|
|
1975
|
+
onCreate() {
|
|
1976
|
+
const transaction = footnoteTransaction(this.editor.state, this.storage.known);
|
|
1977
|
+
if (transaction) this.editor.view.dispatch(transaction.setMeta("addToHistory", false));
|
|
1978
|
+
},
|
|
1979
|
+
addStorage: () => ({ known: /* @__PURE__ */ new Map() }),
|
|
1980
|
+
addProseMirrorPlugins() {
|
|
1981
|
+
return [new Plugin({
|
|
1982
|
+
appendTransaction: (transactions, _oldState, state) => transactions.some((transaction) => transaction.docChanged) ? footnoteTransaction(state, this.storage.known) : null,
|
|
1983
|
+
props: { transformPasted: (slice) => {
|
|
1984
|
+
const kept = [];
|
|
1985
|
+
slice.content.forEach((node) => {
|
|
1986
|
+
if (node.type.name === NOTES) node.forEach((note) => {
|
|
1987
|
+
this.storage.known.set(note.attrs.label, note);
|
|
1988
|
+
});
|
|
1989
|
+
else kept.push(node);
|
|
1990
|
+
});
|
|
1991
|
+
if (!kept.length || kept.length === slice.content.childCount) return slice;
|
|
1992
|
+
const content = Fragment$1.from(kept);
|
|
1993
|
+
const open = Slice.maxOpen(content);
|
|
1994
|
+
return new Slice(content, kept[0] === slice.content.firstChild ? slice.openStart : open.openStart, kept.at(-1) === slice.content.lastChild ? slice.openEnd : open.openEnd);
|
|
1995
|
+
} }
|
|
1996
|
+
})];
|
|
1997
|
+
},
|
|
1998
|
+
addCommands() {
|
|
1999
|
+
return {
|
|
2000
|
+
insertLoomaFootnote: (label) => ({ state, tr, dispatch }) => {
|
|
2001
|
+
const name = label ?? (() => {
|
|
2002
|
+
const used = usedLabels(state.doc);
|
|
2003
|
+
let next = 1;
|
|
2004
|
+
while (used.has(String(next))) next++;
|
|
2005
|
+
return String(next);
|
|
2006
|
+
})();
|
|
2007
|
+
const { $from, $to } = state.selection;
|
|
2008
|
+
if (!isLabel(name) || !$from.parent.canReplaceWith($from.index(), $to.index(), state.schema.nodes[REFERENCE])) return false;
|
|
2009
|
+
if (!dispatch) return true;
|
|
2010
|
+
tr.replaceSelectionWith(state.schema.nodes[REFERENCE].create({ label: name }));
|
|
2011
|
+
if (!findLoomaFootnote(tr.doc, name)) {
|
|
2012
|
+
const note = state.schema.nodes[NOTE].create({ label: name }, state.schema.nodes.paragraph.create());
|
|
2013
|
+
if (tr.doc.lastChild?.type.name === NOTES) tr.insert(tr.doc.content.size - 1, note);
|
|
2014
|
+
else tr.insert(tr.doc.content.size, state.schema.nodes[NOTES].create(null, note));
|
|
2015
|
+
tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size - 3));
|
|
2016
|
+
}
|
|
2017
|
+
tr.scrollIntoView();
|
|
2018
|
+
return true;
|
|
2019
|
+
},
|
|
2020
|
+
renameLoomaFootnote: (from, to) => ({ state, tr, dispatch }) => {
|
|
2021
|
+
if (!isLabel(to) || usedLabels(state.doc).has(to) || !findLoomaFootnote(state.doc, from)) return false;
|
|
2022
|
+
if (dispatch) state.doc.descendants((node, position) => {
|
|
2023
|
+
if ((node.type.name === REFERENCE || node.type.name === NOTE) && node.attrs.label === from) tr.setNodeMarkup(position, void 0, {
|
|
2024
|
+
...node.attrs,
|
|
2025
|
+
label: to
|
|
2026
|
+
});
|
|
2027
|
+
});
|
|
2028
|
+
return true;
|
|
2029
|
+
},
|
|
2030
|
+
goToLoomaFootnote: () => ({ state, tr, dispatch }) => {
|
|
2031
|
+
const { selection } = state;
|
|
2032
|
+
const reference = selection instanceof NodeSelection ? selection.node : selection.empty ? selection.$from.nodeBefore : null;
|
|
2033
|
+
if (reference?.type.name === REFERENCE) {
|
|
2034
|
+
const note = findLoomaFootnote(state.doc, reference.attrs.label);
|
|
2035
|
+
if (!note) return false;
|
|
2036
|
+
if (dispatch) tr.setSelection(Selection.near(state.doc.resolve(note.position + note.node.nodeSize - 1), -1)).scrollIntoView();
|
|
2037
|
+
return true;
|
|
2038
|
+
}
|
|
2039
|
+
const note = noteAt(state.doc, selection.from);
|
|
2040
|
+
let first = -1;
|
|
2041
|
+
if (note) state.doc.descendants((node, position) => {
|
|
2042
|
+
if (first >= 0) return false;
|
|
2043
|
+
if (node.type.name === REFERENCE && node.attrs.label === note.node.attrs.label) first = position;
|
|
2044
|
+
});
|
|
2045
|
+
if (first < 0) return false;
|
|
2046
|
+
if (dispatch) tr.setSelection(TextSelection.create(state.doc, first + 1)).scrollIntoView();
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
},
|
|
2051
|
+
addKeyboardShortcuts() {
|
|
2052
|
+
return {
|
|
2053
|
+
"Mod-Alt-f": () => this.editor.commands.insertLoomaFootnote(),
|
|
2054
|
+
"Mod-Alt-n": () => this.editor.commands.goToLoomaFootnote()
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
Node$1.create({
|
|
2059
|
+
name: NOTE,
|
|
2060
|
+
content: "block+",
|
|
2061
|
+
defining: true,
|
|
2062
|
+
addAttributes() {
|
|
2063
|
+
return {
|
|
2064
|
+
label: {
|
|
2065
|
+
default: "",
|
|
2066
|
+
parseHTML: (element) => labelFrom(`#${element.id}`, "fn:") ?? "",
|
|
2067
|
+
renderHTML: () => ({})
|
|
2068
|
+
},
|
|
2069
|
+
count: {
|
|
2070
|
+
default: 1,
|
|
2071
|
+
parseHTML: (element) => element.querySelectorAll("a[data-footnote-backref]").length || 1,
|
|
2072
|
+
renderHTML: () => ({})
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
},
|
|
2076
|
+
parseHTML() {
|
|
2077
|
+
return [{
|
|
2078
|
+
tag: "li",
|
|
2079
|
+
priority: 1100,
|
|
2080
|
+
context: `${NOTES}/`
|
|
2081
|
+
}, {
|
|
2082
|
+
tag: "a[data-footnote-backref]",
|
|
2083
|
+
priority: 1100,
|
|
2084
|
+
ignore: true
|
|
2085
|
+
}];
|
|
2086
|
+
},
|
|
2087
|
+
renderHTML({ node }) {
|
|
2088
|
+
return [
|
|
2089
|
+
"li",
|
|
2090
|
+
{ id: `fn:${node.attrs.label}` },
|
|
2091
|
+
0
|
|
2092
|
+
];
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
//#endregion
|
|
2096
|
+
//#region src/editor/footnote-editing.ts
|
|
2097
|
+
var LOOMA_FOOTNOTE_EDITING_IDLE = Object.freeze({
|
|
2098
|
+
open: false,
|
|
2099
|
+
label: "",
|
|
2100
|
+
note: "",
|
|
2101
|
+
editable: false,
|
|
2102
|
+
error: ""
|
|
2103
|
+
});
|
|
2104
|
+
var plain = (note) => {
|
|
2105
|
+
const paragraph = note.firstChild;
|
|
2106
|
+
if (note.childCount !== 1 || paragraph?.type.name !== "paragraph") return false;
|
|
2107
|
+
let text = true;
|
|
2108
|
+
paragraph.forEach((child) => {
|
|
2109
|
+
if (!child.isText || child.marks.length) text = false;
|
|
2110
|
+
});
|
|
2111
|
+
return text;
|
|
2112
|
+
};
|
|
2113
|
+
function createFootnoteEditing(options) {
|
|
2114
|
+
const { editor } = options;
|
|
2115
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2116
|
+
let state = LOOMA_FOOTNOTE_EDITING_IDLE;
|
|
2117
|
+
let element = null;
|
|
2118
|
+
const set = (next) => {
|
|
2119
|
+
state = next;
|
|
2120
|
+
listeners.forEach((listener) => listener());
|
|
2121
|
+
};
|
|
2122
|
+
const close = () => {
|
|
2123
|
+
element = null;
|
|
2124
|
+
if (state.open) set({
|
|
2125
|
+
...state,
|
|
2126
|
+
open: false
|
|
2127
|
+
});
|
|
2128
|
+
};
|
|
2129
|
+
const note = () => state.open && !editor.isDestroyed && element?.isConnected ? findLoomaFootnote(editor.state.doc, state.label) : null;
|
|
2130
|
+
return {
|
|
2131
|
+
get state() {
|
|
2132
|
+
return state;
|
|
2133
|
+
},
|
|
2134
|
+
get element() {
|
|
2135
|
+
return element;
|
|
2136
|
+
},
|
|
2137
|
+
subscribe(listener) {
|
|
2138
|
+
listeners.add(listener);
|
|
2139
|
+
return () => {
|
|
2140
|
+
listeners.delete(listener);
|
|
2141
|
+
};
|
|
2142
|
+
},
|
|
2143
|
+
open(position) {
|
|
2144
|
+
if (!options.isEditable() || editor.isDestroyed) return false;
|
|
2145
|
+
const node = editor.state.doc.nodeAt(position);
|
|
2146
|
+
const dom = editor.view.nodeDOM(position);
|
|
2147
|
+
if (node?.type.name !== "loomaFootnoteReference" || !(dom instanceof HTMLElement)) return false;
|
|
2148
|
+
const label = String(node.attrs.label);
|
|
2149
|
+
const found = findLoomaFootnote(editor.state.doc, label);
|
|
2150
|
+
if (!found) return false;
|
|
2151
|
+
element = dom;
|
|
2152
|
+
set({
|
|
2153
|
+
open: true,
|
|
2154
|
+
label,
|
|
2155
|
+
note: found.node.textBetween(0, found.node.content.size, "\n"),
|
|
2156
|
+
editable: plain(found.node),
|
|
2157
|
+
error: ""
|
|
2158
|
+
});
|
|
2159
|
+
return true;
|
|
2160
|
+
},
|
|
2161
|
+
setNote(text) {
|
|
2162
|
+
const found = note();
|
|
2163
|
+
if (!found) return close();
|
|
2164
|
+
if (!state.editable) return;
|
|
2165
|
+
const value = text.replace(/\s*\n\s*/g, " ");
|
|
2166
|
+
const start = found.position + 2;
|
|
2167
|
+
const end = start + found.node.firstChild.content.size;
|
|
2168
|
+
editor.view.dispatch(editor.state.tr.replaceWith(start, end, value ? editor.schema.text(value) : Fragment$1.empty));
|
|
2169
|
+
set({
|
|
2170
|
+
...state,
|
|
2171
|
+
note: value
|
|
2172
|
+
});
|
|
2173
|
+
},
|
|
2174
|
+
rename(label) {
|
|
2175
|
+
if (!note()) return close();
|
|
2176
|
+
if (label === state.label) return state.error ? set({
|
|
2177
|
+
...state,
|
|
2178
|
+
error: ""
|
|
2179
|
+
}) : void 0;
|
|
2180
|
+
if (!LOOMA_FOOTNOTE_LABEL.test(label)) return set({
|
|
2181
|
+
...state,
|
|
2182
|
+
error: "A label is one word, without ]."
|
|
2183
|
+
});
|
|
2184
|
+
if (!editor.commands.renameLoomaFootnote(state.label, label)) return set({
|
|
2185
|
+
...state,
|
|
2186
|
+
error: "Another footnote uses this label."
|
|
2187
|
+
});
|
|
2188
|
+
set({
|
|
2189
|
+
...state,
|
|
2190
|
+
label,
|
|
2191
|
+
error: ""
|
|
2192
|
+
});
|
|
2193
|
+
},
|
|
2194
|
+
goToNote() {
|
|
2195
|
+
const reference = element;
|
|
2196
|
+
close();
|
|
2197
|
+
if (!reference?.isConnected || editor.isDestroyed) return;
|
|
2198
|
+
editor.chain().focus().setNodeSelection(editor.view.posAtDOM(reference, 0)).goToLoomaFootnote().run();
|
|
2199
|
+
},
|
|
2200
|
+
close
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
//#endregion
|
|
1681
2204
|
//#region src/editor/host-actions.ts
|
|
1682
2205
|
/** Characters of context on each side of a selected passage. */
|
|
1683
2206
|
var CONTEXT = 48;
|
|
@@ -1768,25 +2291,10 @@ function tableControls(state, selectionInTable, narrow) {
|
|
|
1768
2291
|
capabilities: state.capabilities
|
|
1769
2292
|
};
|
|
1770
2293
|
}
|
|
1771
|
-
/**
|
|
1772
|
-
var
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
* above the table when there is room and below it otherwise.
|
|
1776
|
-
*/
|
|
1777
|
-
function tableToolbarPlacement(table, viewportWidth) {
|
|
1778
|
-
const width = Math.min(420, viewportWidth - 24);
|
|
1779
|
-
const left = Math.min(Math.max(12, table.left + (table.width - width) / 2), Math.max(12, viewportWidth - width - 12));
|
|
1780
|
-
return {
|
|
1781
|
-
top: `${table.top > ROOM_ABOVE$1 ? table.top - 52 : table.bottom + 12}px`,
|
|
1782
|
-
left: `${left}px`,
|
|
1783
|
-
maxWidth: `${width}px`
|
|
1784
|
-
};
|
|
1785
|
-
}
|
|
1786
|
-
/** The overlay covers the table exactly. */
|
|
1787
|
-
var tableOverlayPlacement = (table) => ({
|
|
1788
|
-
top: `${table.top}px`,
|
|
1789
|
-
left: `${table.left}px`,
|
|
2294
|
+
/** Inline controls cover the table in the editor's coordinate system. */
|
|
2295
|
+
var tableOverlayPlacement = (table, editor) => ({
|
|
2296
|
+
top: `${table.top - editor.top}px`,
|
|
2297
|
+
left: `${table.left - editor.left}px`,
|
|
1790
2298
|
width: `${table.width}px`,
|
|
1791
2299
|
height: `${table.height}px`
|
|
1792
2300
|
});
|
|
@@ -2591,8 +3099,17 @@ function formatEditorShortcut(keys, apple) {
|
|
|
2591
3099
|
].filter(Boolean).join("+");
|
|
2592
3100
|
}
|
|
2593
3101
|
var isApplePlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
|
|
3102
|
+
//#endregion
|
|
3103
|
+
//#region src/components/shared/tooltip.js
|
|
3104
|
+
/** Resolve the inherited pointer-intent delay; CSS time values and millisecond numbers are accepted. */
|
|
3105
|
+
function tooltipShowDelay$1(element, fallback = 500) {
|
|
3106
|
+
if (!element) return fallback;
|
|
3107
|
+
const value = element.ownerDocument.defaultView.getComputedStyle(element).getPropertyValue("--ui-tooltip-show-delay").trim();
|
|
3108
|
+
const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s)?$/.exec(value);
|
|
3109
|
+
return match ? Number(match[1]) * (match[2] === "s" ? 1e3 : 1) : fallback;
|
|
3110
|
+
}
|
|
2594
3111
|
/** Calls `onChange` after every state change. */
|
|
2595
|
-
function createToolbarTooltip(onChange) {
|
|
3112
|
+
function createToolbarTooltip(onChange, delay = () => 400) {
|
|
2596
3113
|
let state = {
|
|
2597
3114
|
for: "",
|
|
2598
3115
|
label: "",
|
|
@@ -2627,7 +3144,7 @@ function createToolbarTooltip(onChange) {
|
|
|
2627
3144
|
if (!open) timer = setTimeout(() => {
|
|
2628
3145
|
timer = void 0;
|
|
2629
3146
|
set({ open: true });
|
|
2630
|
-
},
|
|
3147
|
+
}, delay());
|
|
2631
3148
|
},
|
|
2632
3149
|
hide() {
|
|
2633
3150
|
clear();
|
|
@@ -6212,6 +6729,20 @@ function createUiIcon(options = {}, html) {
|
|
|
6212
6729
|
"y2": "12"
|
|
6213
6730
|
}
|
|
6214
6731
|
],
|
|
6732
|
+
"superscript": [
|
|
6733
|
+
{
|
|
6734
|
+
"tag": "path",
|
|
6735
|
+
"d": "m4 19 8-8"
|
|
6736
|
+
},
|
|
6737
|
+
{
|
|
6738
|
+
"tag": "path",
|
|
6739
|
+
"d": "m12 19-8-8"
|
|
6740
|
+
},
|
|
6741
|
+
{
|
|
6742
|
+
"tag": "path",
|
|
6743
|
+
"d": "M20 12h-4c0-1.5.442-2 1.5-2.5S20 8.334 20 7.002c0-.472-.17-.93-.484-1.29a2.105 2.105 0 0 0-2.617-.436c-.42.239-.738.614-.899 1.06"
|
|
6744
|
+
}
|
|
6745
|
+
],
|
|
6215
6746
|
"table": [{
|
|
6216
6747
|
"tag": "path",
|
|
6217
6748
|
"d": "M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"
|
|
@@ -9993,6 +10524,15 @@ defineComponent$1("ui-text", createUiText, [
|
|
|
9993
10524
|
"wrap"
|
|
9994
10525
|
]);
|
|
9995
10526
|
//#endregion
|
|
10527
|
+
//#region components/shared/tooltip.js
|
|
10528
|
+
/** Resolve the inherited pointer-intent delay; CSS time values and millisecond numbers are accepted. */
|
|
10529
|
+
function tooltipShowDelay(element, fallback = 500) {
|
|
10530
|
+
if (!element) return fallback;
|
|
10531
|
+
const value = element.ownerDocument.defaultView.getComputedStyle(element).getPropertyValue("--ui-tooltip-show-delay").trim();
|
|
10532
|
+
const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s)?$/.exec(value);
|
|
10533
|
+
return match ? Number(match[1]) * (match[2] === "s" ? 1e3 : 1) : fallback;
|
|
10534
|
+
}
|
|
10535
|
+
//#endregion
|
|
9996
10536
|
//#region components/ui-tooltip/ui-tooltip.js
|
|
9997
10537
|
var ui_tooltip_exports = /* @__PURE__ */ __exportAll({ connect: () => connect$4 });
|
|
9998
10538
|
var warmTooltips = /* @__PURE__ */ new WeakMap();
|
|
@@ -10069,7 +10609,7 @@ function connect$4(host) {
|
|
|
10069
10609
|
showTimer = setTimeout(() => {
|
|
10070
10610
|
showTimer = null;
|
|
10071
10611
|
setOpen(true, "pointer");
|
|
10072
|
-
}, warm.active > 0 || warm.until > Date.now() ? 0 : Math.max(0, Number(host.props.showDelay.value
|
|
10612
|
+
}, warm.active > 0 || warm.until > Date.now() ? 0 : Math.max(0, host.props.showDelay.inputValue != null ? Number(host.props.showDelay.value) : tooltipShowDelay(element)));
|
|
10073
10613
|
};
|
|
10074
10614
|
const onLeave = () => {
|
|
10075
10615
|
clearShow();
|
|
@@ -10139,6 +10679,7 @@ function connect$4(host) {
|
|
|
10139
10679
|
if (how === "hover") {
|
|
10140
10680
|
trigger.addEventListener("pointerenter", onEnter);
|
|
10141
10681
|
trigger.addEventListener("pointerleave", onLeave);
|
|
10682
|
+
if (trigger.matches(":hover")) onEnter({ pointerType: "mouse" });
|
|
10142
10683
|
}
|
|
10143
10684
|
trigger.addEventListener("focusin", onFocusin);
|
|
10144
10685
|
trigger.addEventListener("focusout", onFocusout);
|
|
@@ -14210,10 +14751,18 @@ var LoomaEditor = defineComponent({
|
|
|
14210
14751
|
const chipInput = () => chipEditorRoot.value?.querySelector("[data-chip-label]");
|
|
14211
14752
|
let chipEditing;
|
|
14212
14753
|
const chipState = shallowRef(LOOMA_CHIP_EDITING_IDLE);
|
|
14754
|
+
const footnoteAnchorId = `looma-editor-footnote-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
14755
|
+
const footnoteEditorRoot = ref(null);
|
|
14756
|
+
const setFootnoteEditorRoot = (instance) => {
|
|
14757
|
+
const element = instance?.$el;
|
|
14758
|
+
footnoteEditorRoot.value = element instanceof HTMLElement ? element : null;
|
|
14759
|
+
};
|
|
14760
|
+
let footnoteEditing;
|
|
14761
|
+
const footnoteState = shallowRef(LOOMA_FOOTNOTE_EDITING_IDLE);
|
|
14213
14762
|
const toolbarScope = `looma-editor-tool-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
14214
14763
|
const toolbarTooltip = createToolbarTooltip((state) => {
|
|
14215
14764
|
tooltip.value = state;
|
|
14216
|
-
});
|
|
14765
|
+
}, () => tooltipShowDelay$1(root.value));
|
|
14217
14766
|
const tooltip = shallowRef(toolbarTooltip.state);
|
|
14218
14767
|
const resizeHintFor = ref("");
|
|
14219
14768
|
let resizeHintSequence = 0;
|
|
@@ -14285,7 +14834,7 @@ var LoomaEditor = defineComponent({
|
|
|
14285
14834
|
rows: 0,
|
|
14286
14835
|
cols: 0,
|
|
14287
14836
|
geometry: null,
|
|
14288
|
-
|
|
14837
|
+
toolbarTarget: null,
|
|
14289
14838
|
overlayStyle: {},
|
|
14290
14839
|
menuStyle: {},
|
|
14291
14840
|
capabilities: LOOMA_TABLE_CONTROLS_CLOSED.capabilities
|
|
@@ -14296,6 +14845,17 @@ var LoomaEditor = defineComponent({
|
|
|
14296
14845
|
chipEditing.element.id = chipAnchorId;
|
|
14297
14846
|
nextTick(() => chipInput()?.focus());
|
|
14298
14847
|
};
|
|
14848
|
+
const openFootnoteEditor = (position) => {
|
|
14849
|
+
if (!footnoteEditing?.open(position)) return;
|
|
14850
|
+
root.value?.querySelector(`#${footnoteAnchorId}`)?.removeAttribute("id");
|
|
14851
|
+
footnoteEditing.element.id = footnoteAnchorId;
|
|
14852
|
+
nextTick(() => footnoteEditorRoot.value?.querySelector("[data-footnote-note]")?.focus());
|
|
14853
|
+
};
|
|
14854
|
+
const closeFootnote = (focusReference = false) => {
|
|
14855
|
+
const element = footnoteEditing?.element;
|
|
14856
|
+
footnoteEditing?.close();
|
|
14857
|
+
if (focusReference) element?.focus();
|
|
14858
|
+
};
|
|
14299
14859
|
const closeChip = (focusChip = false) => {
|
|
14300
14860
|
const element = chipEditing?.element;
|
|
14301
14861
|
chipEditing?.close();
|
|
@@ -14525,6 +15085,14 @@ var LoomaEditor = defineComponent({
|
|
|
14525
15085
|
chipState.value = chips.state;
|
|
14526
15086
|
});
|
|
14527
15087
|
chipEditing = chips;
|
|
15088
|
+
const footnotes = createFootnoteEditing({
|
|
15089
|
+
editor: session.editor,
|
|
15090
|
+
isEditable: () => props.editable
|
|
15091
|
+
});
|
|
15092
|
+
footnotes.subscribe(() => {
|
|
15093
|
+
footnoteState.value = footnotes.state;
|
|
15094
|
+
});
|
|
15095
|
+
footnoteEditing = footnotes;
|
|
14528
15096
|
const imageUploads = createImageUploads({
|
|
14529
15097
|
editor: session.editor,
|
|
14530
15098
|
upload: () => (file, init) => ask("image-upload", { file }, init),
|
|
@@ -14551,6 +15119,7 @@ var LoomaEditor = defineComponent({
|
|
|
14551
15119
|
const content = edited ? previous.editor.getJSON() : props.modelValue;
|
|
14552
15120
|
linkEditing?.destroy();
|
|
14553
15121
|
chipEditing?.close();
|
|
15122
|
+
footnoteEditing?.close();
|
|
14554
15123
|
formatting?.destroy();
|
|
14555
15124
|
uploads?.cancel();
|
|
14556
15125
|
previous.destroy();
|
|
@@ -14673,10 +15242,11 @@ var LoomaEditor = defineComponent({
|
|
|
14673
15242
|
editor.value?.setEditable(editable, false);
|
|
14674
15243
|
if (!editable) formattingPopup?.hide();
|
|
14675
15244
|
if (editor.value) imageDelivery.reset(editor.value);
|
|
14676
|
-
root.value?.querySelectorAll("[data-looma-chip]").forEach((chip) => {
|
|
15245
|
+
root.value?.querySelectorAll("[data-looma-chip], [data-looma-footnote-ref]").forEach((chip) => {
|
|
14677
15246
|
chip.setAttribute("aria-disabled", String(!editable));
|
|
14678
15247
|
});
|
|
14679
15248
|
if (!editable) chipEditing?.close();
|
|
15249
|
+
if (!editable) footnoteEditing?.close();
|
|
14680
15250
|
});
|
|
14681
15251
|
watch([
|
|
14682
15252
|
() => linkState.value.surface === "popover",
|
|
@@ -14796,8 +15366,8 @@ var LoomaEditor = defineComponent({
|
|
|
14796
15366
|
tableUi.rows = Math.max(0, geometry.rowBoundaries.length - 1);
|
|
14797
15367
|
tableUi.cols = Math.max(0, geometry.columnBoundaries.length - 1);
|
|
14798
15368
|
tableUi.geometry = geometry;
|
|
14799
|
-
tableUi.
|
|
14800
|
-
tableUi.overlayStyle = tableOverlayPlacement(rect);
|
|
15369
|
+
tableUi.toolbarTarget = table.closest("[data-looma-table-block]")?.querySelector("[data-looma-table-toolbar-host]") ?? null;
|
|
15370
|
+
tableUi.overlayStyle = tableOverlayPlacement(rect, root.value.getBoundingClientRect());
|
|
14801
15371
|
};
|
|
14802
15372
|
const onEditorFocus = () => {
|
|
14803
15373
|
editorFocused.value = true;
|
|
@@ -14863,13 +15433,10 @@ var LoomaEditor = defineComponent({
|
|
|
14863
15433
|
presentation?.show();
|
|
14864
15434
|
};
|
|
14865
15435
|
};
|
|
14866
|
-
const tableToolbarRef = popupRef(tableToolbarShell);
|
|
14867
15436
|
const codeLanguageRef = popupRef(codeLanguageShell, true);
|
|
14868
15437
|
const mobileToolbarRef = popupRef(mobileToolbarShell, true);
|
|
14869
|
-
const presentTableOverlay = popupRef(tableOverlayShell);
|
|
14870
15438
|
const tableOverlayRef = (node) => {
|
|
14871
|
-
|
|
14872
|
-
if (node instanceof HTMLElement) node.hidden = !tableUi.overlayOpen;
|
|
15439
|
+
tableOverlayShell.value = node instanceof HTMLElement ? node : null;
|
|
14873
15440
|
};
|
|
14874
15441
|
const onDocumentPointerDown = (event) => {
|
|
14875
15442
|
const path = typeof event.composedPath === "function" ? event.composedPath() : [];
|
|
@@ -15023,7 +15590,18 @@ var LoomaEditor = defineComponent({
|
|
|
15023
15590
|
const element = event.target instanceof Element ? event.target.closest("[data-looma-chip]") : null;
|
|
15024
15591
|
return element && root.value?.contains(element) ? element : null;
|
|
15025
15592
|
};
|
|
15593
|
+
const footnoteElementForEvent = (event) => {
|
|
15594
|
+
const element = event.target instanceof Element ? event.target.closest("[data-looma-footnote-ref]") : null;
|
|
15595
|
+
return element && root.value?.contains(element) ? element : null;
|
|
15596
|
+
};
|
|
15026
15597
|
const onChipClickCapture = (event) => {
|
|
15598
|
+
const reference = footnoteElementForEvent(event);
|
|
15599
|
+
if (reference && editor.value && props.editable) {
|
|
15600
|
+
event.preventDefault();
|
|
15601
|
+
event.stopPropagation();
|
|
15602
|
+
openFootnoteEditor(editor.value.view.posAtDOM(reference, 0));
|
|
15603
|
+
return;
|
|
15604
|
+
}
|
|
15027
15605
|
const chip = chipElementForEvent(event);
|
|
15028
15606
|
if (chip && editor.value && props.editable) {
|
|
15029
15607
|
event.preventDefault();
|
|
@@ -15041,6 +15619,12 @@ var LoomaEditor = defineComponent({
|
|
|
15041
15619
|
};
|
|
15042
15620
|
const onImageKeyDown = (event) => {
|
|
15043
15621
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
15622
|
+
const reference = footnoteElementForEvent(event);
|
|
15623
|
+
if (reference && editor.value && props.editable) {
|
|
15624
|
+
event.preventDefault();
|
|
15625
|
+
openFootnoteEditor(editor.value.view.posAtDOM(reference, 0));
|
|
15626
|
+
return;
|
|
15627
|
+
}
|
|
15044
15628
|
const chip = chipElementForEvent(event);
|
|
15045
15629
|
if (chip && editor.value && props.editable) {
|
|
15046
15630
|
event.preventDefault();
|
|
@@ -15465,8 +16049,6 @@ var LoomaEditor = defineComponent({
|
|
|
15465
16049
|
}, [
|
|
15466
16050
|
resizeHintFor.value ? h(Tooltip, {
|
|
15467
16051
|
for: resizeHintFor.value,
|
|
15468
|
-
open: true,
|
|
15469
|
-
trigger: "focus",
|
|
15470
16052
|
placement: "top",
|
|
15471
16053
|
inverse: true
|
|
15472
16054
|
}, () => "Drag to resize column") : null,
|
|
@@ -15576,6 +16158,23 @@ var LoomaEditor = defineComponent({
|
|
|
15576
16158
|
onColor: (event) => chipEditing?.setColor(event.detail.color),
|
|
15577
16159
|
onDone: () => closeChip(true)
|
|
15578
16160
|
})),
|
|
16161
|
+
h(Popover, {
|
|
16162
|
+
size: "sm",
|
|
16163
|
+
open: footnoteState.value.open,
|
|
16164
|
+
for: footnoteAnchorId,
|
|
16165
|
+
placement: mobile.value ? "top-start" : "bottom-start",
|
|
16166
|
+
onClose: () => footnoteEditing?.close()
|
|
16167
|
+
}, () => h(EditorFootnoteEditor, {
|
|
16168
|
+
ref: setFootnoteEditorRoot,
|
|
16169
|
+
label: footnoteState.value.label,
|
|
16170
|
+
note: footnoteState.value.note,
|
|
16171
|
+
editable: footnoteState.value.editable,
|
|
16172
|
+
error: footnoteState.value.error,
|
|
16173
|
+
onText: (event) => footnoteEditing?.setNote(event.detail.text),
|
|
16174
|
+
onLabel: (event) => footnoteEditing?.rename(event.detail.label),
|
|
16175
|
+
onGo: () => footnoteEditing?.goToNote(),
|
|
16176
|
+
onDone: () => closeFootnote(true)
|
|
16177
|
+
})),
|
|
15579
16178
|
h(Popover, {
|
|
15580
16179
|
class: "looma-editor__link-popover",
|
|
15581
16180
|
size: "sm",
|
|
@@ -15663,12 +16262,11 @@ var LoomaEditor = defineComponent({
|
|
|
15663
16262
|
mention.select?.(event.detail.index);
|
|
15664
16263
|
}
|
|
15665
16264
|
}) : null,
|
|
15666
|
-
tableUi.toolbarOpen ? h("div", {
|
|
16265
|
+
tableUi.toolbarOpen && tableUi.toolbarTarget ? h(Teleport, { to: tableUi.toolbarTarget }, [h("div", {
|
|
15667
16266
|
key: "looma-table-toolbar-shell",
|
|
15668
|
-
ref:
|
|
15669
|
-
class: "looma-editor__table-toolbar-shell"
|
|
15670
|
-
|
|
15671
|
-
}, [h(EditorTableToolbar, tableProps)]) : null,
|
|
16267
|
+
ref: tableToolbarShell,
|
|
16268
|
+
class: "looma-editor__table-toolbar-shell"
|
|
16269
|
+
}, [h(EditorTableToolbar, tableProps)])]) : null,
|
|
15672
16270
|
instance && props.editable && !mobile.value ? h("div", {
|
|
15673
16271
|
key: "looma-table-overlay-shell",
|
|
15674
16272
|
ref: tableOverlayRef,
|
|
@@ -15739,4 +16337,4 @@ var LoomaEditor = defineComponent({
|
|
|
15739
16337
|
}
|
|
15740
16338
|
});
|
|
15741
16339
|
//#endregion
|
|
15742
|
-
export { EDITOR_ADAPTER_COMPONENT_TAG_MAP, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, LoomaEditor };
|
|
16340
|
+
export { EDITOR_ADAPTER_COMPONENT_TAG_MAP, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorFootnoteEditor, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, LoomaEditor };
|