@threadlabs/looma 0.28.8 → 0.28.10
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/ui-editor-code-language/ui-editor-code-language.html +17 -0
- package/components/ui-editor-code-language/ui-editor-code-language.js +14 -0
- package/dist/index.js +37 -36
- package/editor/link-editing.d.ts +85 -0
- package/package.json +1 -1
- package/styles/ui-editor-code-language.css +1 -0
- package/vanilla/UiEditorCodeLanguage.d.ts +16 -0
- package/vanilla/UiEditorCodeLanguage.js +64 -0
- package/vanilla/index.js +1 -0
- package/vue/UiEditorCodeLanguage.d.ts +44 -0
- package/vue/UiEditorCodeLanguage.js +2 -0
- package/vue/UiEditorCodeLanguage.vue +156 -0
- package/vue/chunks/UiEditorCodeLanguage.js +163 -0
- package/vue/editor/LoomaEditor.d.ts +2 -19
- package/vue/editor/index.js +334 -237
- package/vue/editor/primitives.d.ts +5 -1
- package/vue/index.d.ts +1 -0
- package/vue/index.js +2 -1
package/vue/editor/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { computed, createApp, defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, shallowRef, useId, watch } from "vue";
|
|
2
|
-
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt
|
|
3
|
-
import { Button, Callout, Card, Checkbox, Cluster,
|
|
2
|
+
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, normalizeTableOfContentsConfig, resolveTableCellAt } from "@threadlabs/looma/editor";
|
|
3
|
+
import { Button, Callout, Card, Checkbox, Cluster, Disclosure, EditorBlockActions, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, FormField, Icon, IconButton, Image, Input, List, Menu, MenuItem, Popover, Separator, Stack, Text, Tooltip } from "@threadlabs/looma/vue";
|
|
4
4
|
import { Editor, Extension, Node as Node$1, getText, getTextSerializersFromSchema, isNodeSelection, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
|
|
5
5
|
import { Editor as Editor$1 } from "@tiptap/vue-3";
|
|
6
6
|
import { closeHistory } from "@tiptap/pm/history";
|
|
7
7
|
import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
8
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
9
9
|
import { createLowlight } from "lowlight";
|
|
10
|
+
import "@tiptap/pm/transform";
|
|
10
11
|
import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
|
|
11
12
|
export * from "@threadlabs/looma/editor";
|
|
12
13
|
//#region src/vue/editor/primitives.ts
|
|
@@ -25,7 +26,8 @@ var EDITOR_ADAPTER_COMPONENT_TAG_MAP = {
|
|
|
25
26
|
EditorImageToolbar: "ui-editor-image-toolbar",
|
|
26
27
|
EditorLinkCard: "ui-editor-link-card",
|
|
27
28
|
EditorLinkForm: "ui-editor-link-form",
|
|
28
|
-
EditorBlockActions: "ui-editor-block-actions"
|
|
29
|
+
EditorBlockActions: "ui-editor-block-actions",
|
|
30
|
+
EditorCodeLanguage: "ui-editor-code-language"
|
|
29
31
|
};
|
|
30
32
|
//#endregion
|
|
31
33
|
//#region src/components/shared/overlay.js
|
|
@@ -1120,6 +1122,273 @@ function validLinkHref(value) {
|
|
|
1120
1122
|
}
|
|
1121
1123
|
}
|
|
1122
1124
|
//#endregion
|
|
1125
|
+
//#region src/editor/extensions/relative-links.ts
|
|
1126
|
+
/** Keep other origins and non-web schemes intact; same-origin links retain path, query, and fragment. */
|
|
1127
|
+
function siteRelativeHref(href, baseUrl) {
|
|
1128
|
+
if (!/^(https?:\/\/|\/\/)/i.test(href.trim())) return href;
|
|
1129
|
+
try {
|
|
1130
|
+
const base = new URL(baseUrl);
|
|
1131
|
+
const url = new URL(href, base);
|
|
1132
|
+
if (!/^https?:$/.test(base.protocol) || url.origin !== base.origin || url.username || url.password) return href;
|
|
1133
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
1134
|
+
} catch {
|
|
1135
|
+
return href;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
//#endregion
|
|
1139
|
+
//#region src/editor/link-editing.ts
|
|
1140
|
+
/** The status line above search results: progress, a hint, or an empty result. */
|
|
1141
|
+
function linkFormStatus(state) {
|
|
1142
|
+
if (state.searching) return "Searching…";
|
|
1143
|
+
if (state.selected) return "";
|
|
1144
|
+
if (state.query.trim().length < 2) return "Type at least two letters to search.";
|
|
1145
|
+
return state.results.length || state.error ? "" : "No results found.";
|
|
1146
|
+
}
|
|
1147
|
+
var LOOMA_LINK_EDITING_IDLE = Object.freeze({
|
|
1148
|
+
surface: "none",
|
|
1149
|
+
range: null,
|
|
1150
|
+
mode: "url",
|
|
1151
|
+
query: "",
|
|
1152
|
+
href: "",
|
|
1153
|
+
text: "",
|
|
1154
|
+
textEditable: false,
|
|
1155
|
+
newTab: true,
|
|
1156
|
+
error: "",
|
|
1157
|
+
results: [],
|
|
1158
|
+
searching: false,
|
|
1159
|
+
selected: null,
|
|
1160
|
+
current: null
|
|
1161
|
+
});
|
|
1162
|
+
function createLinkEditing(options) {
|
|
1163
|
+
const { editor } = options;
|
|
1164
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1165
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1166
|
+
let state = LOOMA_LINK_EDITING_IDLE;
|
|
1167
|
+
let searchSequence = 0;
|
|
1168
|
+
let resolveSequence = 0;
|
|
1169
|
+
let currentHref = null;
|
|
1170
|
+
let destroyed = false;
|
|
1171
|
+
const set = (patch) => {
|
|
1172
|
+
const previous = state;
|
|
1173
|
+
state = {
|
|
1174
|
+
...state,
|
|
1175
|
+
...patch
|
|
1176
|
+
};
|
|
1177
|
+
if (state.query !== previous.query || state.mode !== previous.mode || state.surface !== previous.surface) search();
|
|
1178
|
+
listeners.forEach((listener) => listener());
|
|
1179
|
+
};
|
|
1180
|
+
const search = async () => {
|
|
1181
|
+
const sequence = ++searchSequence;
|
|
1182
|
+
const { query, mode, surface, selected } = state;
|
|
1183
|
+
state = {
|
|
1184
|
+
...state,
|
|
1185
|
+
results: [],
|
|
1186
|
+
searching: false
|
|
1187
|
+
};
|
|
1188
|
+
const find = options.search();
|
|
1189
|
+
if (!find || mode !== "target" || surface === "none" || query.trim().length < 2 || selected && query === selected.label) return;
|
|
1190
|
+
state = {
|
|
1191
|
+
...state,
|
|
1192
|
+
searching: true
|
|
1193
|
+
};
|
|
1194
|
+
try {
|
|
1195
|
+
const results = await find(query.trim());
|
|
1196
|
+
if (sequence === searchSequence && !destroyed) set({ results: results.filter((target) => validLinkHref(target.href)) });
|
|
1197
|
+
} catch {
|
|
1198
|
+
if (sequence === searchSequence && !destroyed) set({ error: "Search is unavailable. Try again." });
|
|
1199
|
+
} finally {
|
|
1200
|
+
if (sequence === searchSequence && !destroyed) set({ searching: false });
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
const resolveCurrent = async (href) => {
|
|
1204
|
+
const sequence = ++resolveSequence;
|
|
1205
|
+
if (state.current) set({ current: null });
|
|
1206
|
+
const resolve = options.resolve();
|
|
1207
|
+
if (!href || !resolve) return;
|
|
1208
|
+
if (cache.has(href)) {
|
|
1209
|
+
set({ current: cache.get(href) ?? null });
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
try {
|
|
1213
|
+
const target = await resolve(href);
|
|
1214
|
+
cache.set(href, target);
|
|
1215
|
+
if (sequence === resolveSequence && !destroyed) set({ current: target });
|
|
1216
|
+
} catch {}
|
|
1217
|
+
};
|
|
1218
|
+
const onTransaction = () => {
|
|
1219
|
+
const href = editor.isActive("link") ? String(editor.getAttributes("link").href ?? "") : null;
|
|
1220
|
+
if (href !== currentHref) {
|
|
1221
|
+
currentHref = href;
|
|
1222
|
+
resolveCurrent(href);
|
|
1223
|
+
}
|
|
1224
|
+
if (state.surface === "context" && editor.state.selection.from !== state.range?.from) set({ surface: "none" });
|
|
1225
|
+
};
|
|
1226
|
+
editor.on("transaction", onTransaction);
|
|
1227
|
+
const apply = (reportError = false) => {
|
|
1228
|
+
const range = state.range;
|
|
1229
|
+
if (editor.isDestroyed || !range) return false;
|
|
1230
|
+
const fail = (error) => {
|
|
1231
|
+
if (reportError) set({ error });
|
|
1232
|
+
return false;
|
|
1233
|
+
};
|
|
1234
|
+
const href = state.mode === "target" ? validLinkHref(state.selected?.href ?? "") : validLinkHref(state.href);
|
|
1235
|
+
if (!href) return fail(state.mode === "target" ? "Choose a destination from the results." : "Enter an http, https, mailto, tel, or relative URL.");
|
|
1236
|
+
if (state.mode === "url" && options.search() && !range.existing && !/^(https?:|mailto:|tel:)/i.test(href)) return fail("Enter a full website, email, or phone URL, or choose a destination.");
|
|
1237
|
+
const baseUrl = options.baseUrl();
|
|
1238
|
+
const attrs = {
|
|
1239
|
+
href: baseUrl ? siteRelativeHref(href, baseUrl) : href,
|
|
1240
|
+
target: state.newTab ? "_blank" : "_self",
|
|
1241
|
+
rel: state.newTab ? "noopener noreferrer" : null
|
|
1242
|
+
};
|
|
1243
|
+
const current = editor.state.selection;
|
|
1244
|
+
const chain = editor.chain().setTextSelection({
|
|
1245
|
+
from: range.from,
|
|
1246
|
+
to: range.to
|
|
1247
|
+
});
|
|
1248
|
+
if (state.textEditable) {
|
|
1249
|
+
const text = state.text.trim();
|
|
1250
|
+
if (!text) return fail("Enter link text.");
|
|
1251
|
+
chain.insertContent({
|
|
1252
|
+
type: "text",
|
|
1253
|
+
text,
|
|
1254
|
+
marks: [{
|
|
1255
|
+
type: "link",
|
|
1256
|
+
attrs
|
|
1257
|
+
}]
|
|
1258
|
+
}).setTextSelection(range.from).run();
|
|
1259
|
+
set({
|
|
1260
|
+
range: {
|
|
1261
|
+
from: range.from,
|
|
1262
|
+
to: range.from + text.length,
|
|
1263
|
+
existing: true
|
|
1264
|
+
},
|
|
1265
|
+
error: ""
|
|
1266
|
+
});
|
|
1267
|
+
} else {
|
|
1268
|
+
if (range.existing && range.from === range.to) chain.extendMarkRange("link");
|
|
1269
|
+
chain.setLink(attrs).setTextSelection({
|
|
1270
|
+
from: current.from,
|
|
1271
|
+
to: current.to
|
|
1272
|
+
}).run();
|
|
1273
|
+
set({
|
|
1274
|
+
range: {
|
|
1275
|
+
...range,
|
|
1276
|
+
existing: true
|
|
1277
|
+
},
|
|
1278
|
+
error: ""
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
return true;
|
|
1282
|
+
};
|
|
1283
|
+
return {
|
|
1284
|
+
get state() {
|
|
1285
|
+
return state;
|
|
1286
|
+
},
|
|
1287
|
+
subscribe(listener) {
|
|
1288
|
+
listeners.add(listener);
|
|
1289
|
+
return () => {
|
|
1290
|
+
listeners.delete(listener);
|
|
1291
|
+
};
|
|
1292
|
+
},
|
|
1293
|
+
open(from, to, surface) {
|
|
1294
|
+
const empty = from === to;
|
|
1295
|
+
const existing = editor.isActive("link");
|
|
1296
|
+
const attrs = existing ? editor.getAttributes("link") : {};
|
|
1297
|
+
const href = typeof attrs.href === "string" ? attrs.href : "";
|
|
1298
|
+
const selected = cache.get(href) ?? null;
|
|
1299
|
+
set({
|
|
1300
|
+
surface,
|
|
1301
|
+
range: {
|
|
1302
|
+
from,
|
|
1303
|
+
to,
|
|
1304
|
+
existing
|
|
1305
|
+
},
|
|
1306
|
+
textEditable: empty && !existing,
|
|
1307
|
+
href,
|
|
1308
|
+
newTab: attrs.target !== "_self",
|
|
1309
|
+
text: empty && !existing ? "" : editor.state.doc.textBetween(from, to),
|
|
1310
|
+
error: "",
|
|
1311
|
+
selected,
|
|
1312
|
+
mode: options.search() && (!existing || selected) ? "target" : "url",
|
|
1313
|
+
query: selected?.label ?? href
|
|
1314
|
+
});
|
|
1315
|
+
const resolve = options.resolve();
|
|
1316
|
+
if (existing && resolve && !cache.has(href)) resolve(href).then((target) => {
|
|
1317
|
+
cache.set(href, target);
|
|
1318
|
+
if (!destroyed && state.href === href && target) set({
|
|
1319
|
+
selected: target,
|
|
1320
|
+
mode: "target",
|
|
1321
|
+
query: target.label
|
|
1322
|
+
});
|
|
1323
|
+
}).catch(() => {});
|
|
1324
|
+
},
|
|
1325
|
+
setDestination(value) {
|
|
1326
|
+
if (options.search()) {
|
|
1327
|
+
const trimmed = value.trim();
|
|
1328
|
+
const url = /^(https?:\/\/|mailto:|tel:)/i.test(trimmed) || Boolean(state.range?.existing && /^(\/|\.\.?\/|#|\?)/.test(trimmed));
|
|
1329
|
+
set({
|
|
1330
|
+
query: value,
|
|
1331
|
+
mode: url ? "url" : "target",
|
|
1332
|
+
selected: null,
|
|
1333
|
+
href: value,
|
|
1334
|
+
error: ""
|
|
1335
|
+
});
|
|
1336
|
+
} else set({
|
|
1337
|
+
href: value,
|
|
1338
|
+
error: ""
|
|
1339
|
+
});
|
|
1340
|
+
apply();
|
|
1341
|
+
},
|
|
1342
|
+
clear() {
|
|
1343
|
+
set({
|
|
1344
|
+
query: "",
|
|
1345
|
+
href: "",
|
|
1346
|
+
mode: "target",
|
|
1347
|
+
selected: null,
|
|
1348
|
+
error: ""
|
|
1349
|
+
});
|
|
1350
|
+
},
|
|
1351
|
+
choose(target) {
|
|
1352
|
+
cache.set(target.href, target);
|
|
1353
|
+
set({
|
|
1354
|
+
selected: target,
|
|
1355
|
+
href: target.href,
|
|
1356
|
+
query: target.label,
|
|
1357
|
+
results: [],
|
|
1358
|
+
newTab: false,
|
|
1359
|
+
error: "",
|
|
1360
|
+
...state.textEditable && !state.text.trim() ? { text: target.label } : {}
|
|
1361
|
+
});
|
|
1362
|
+
apply();
|
|
1363
|
+
},
|
|
1364
|
+
setNewTab(newTab) {
|
|
1365
|
+
set({ newTab });
|
|
1366
|
+
apply();
|
|
1367
|
+
},
|
|
1368
|
+
setText(text) {
|
|
1369
|
+
set({
|
|
1370
|
+
text,
|
|
1371
|
+
error: ""
|
|
1372
|
+
});
|
|
1373
|
+
apply();
|
|
1374
|
+
},
|
|
1375
|
+
apply,
|
|
1376
|
+
close() {
|
|
1377
|
+
if (state.surface !== "none") set({ surface: "none" });
|
|
1378
|
+
},
|
|
1379
|
+
remove() {
|
|
1380
|
+
if (!editor.isActive("link")) return;
|
|
1381
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
1382
|
+
if (state.surface === "context") set({ surface: "none" });
|
|
1383
|
+
},
|
|
1384
|
+
destroy() {
|
|
1385
|
+
destroyed = true;
|
|
1386
|
+
listeners.clear();
|
|
1387
|
+
editor.off("transaction", onTransaction);
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
//#endregion
|
|
1123
1392
|
//#region src/editor/selection-geometry.ts
|
|
1124
1393
|
/** A plain copy of a client rectangle, the anchor shape the slash and mention menus accept as a prop. */
|
|
1125
1394
|
function menuAnchorRect(rect) {
|
|
@@ -2492,28 +2761,13 @@ var LoomaEditor = defineComponent({
|
|
|
2492
2761
|
const element = instance?.$el;
|
|
2493
2762
|
linkFormRoot.value = element instanceof HTMLElement ? element : null;
|
|
2494
2763
|
};
|
|
2495
|
-
|
|
2496
|
-
const
|
|
2497
|
-
const linkHref = ref("");
|
|
2498
|
-
const linkText = ref("");
|
|
2499
|
-
const linkTextEditable = ref(false);
|
|
2500
|
-
const linkNewTab = ref(true);
|
|
2501
|
-
const linkError = ref("");
|
|
2502
|
-
const linkMode = ref("url");
|
|
2503
|
-
const linkQuery = ref("");
|
|
2504
|
-
const linkResults = ref([]);
|
|
2505
|
-
const linkSearching = ref(false);
|
|
2506
|
-
const linkSelectedTarget = ref(null);
|
|
2507
|
-
const linkContextTarget = ref(null);
|
|
2508
|
-
const linkTargetCache = /* @__PURE__ */ new Map();
|
|
2509
|
-
let linkSearchSequence = 0;
|
|
2510
|
-
let linkResolveSequence = 0;
|
|
2764
|
+
let linkEditing;
|
|
2765
|
+
const linkState = shallowRef(LOOMA_LINK_EDITING_IDLE);
|
|
2511
2766
|
const chipAnchorId = `looma-editor-chip-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
2512
2767
|
const chipInput = ref(null);
|
|
2513
2768
|
const chipOpen = ref(false);
|
|
2514
2769
|
const chipLabel = ref("");
|
|
2515
2770
|
const chipColor = ref("neutral");
|
|
2516
|
-
let linkSelection = null;
|
|
2517
2771
|
const toolbarScope = `looma-editor-tool-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
2518
2772
|
const tooltipFor = ref("");
|
|
2519
2773
|
const tooltipLabel = ref("");
|
|
@@ -2564,7 +2818,7 @@ var LoomaEditor = defineComponent({
|
|
|
2564
2818
|
let heldPressTimer;
|
|
2565
2819
|
let heldPress = null;
|
|
2566
2820
|
const floatingFormattingMode = () => props.toolbarMode !== "sticky" && !(props.toolbarMode === "popover" && props.toolbarOpen) && !mobile.value;
|
|
2567
|
-
const formattingPickerOpen = () =>
|
|
2821
|
+
const formattingPickerOpen = () => linkState.value.surface === "popover" || tablePickerOpen.value || formatOpen.value;
|
|
2568
2822
|
const cancelHeldPress = () => {
|
|
2569
2823
|
clearTimeout(heldPressTimer);
|
|
2570
2824
|
heldPressTimer = void 0;
|
|
@@ -2753,7 +3007,7 @@ var LoomaEditor = defineComponent({
|
|
|
2753
3007
|
if (view.isDestroyed) return;
|
|
2754
3008
|
editorStateVersion.value += 1;
|
|
2755
3009
|
const instance = editor.value;
|
|
2756
|
-
if (instance?.isFocused && instance.state.selection.empty && instance.isActive("link") &&
|
|
3010
|
+
if (instance?.isFocused && instance.state.selection.empty && instance.isActive("link") && linkState.value.surface !== "popover") {
|
|
2757
3011
|
linkPopup?.setAnchor(() => link.getBoundingClientRect());
|
|
2758
3012
|
linkPopup?.show();
|
|
2759
3013
|
}
|
|
@@ -2776,7 +3030,6 @@ var LoomaEditor = defineComponent({
|
|
|
2776
3030
|
if (!heldFormattingSelection?.eq(selection)) heldFormattingSelection = null;
|
|
2777
3031
|
if (!formattingSelectionActive(instance)) formattingPopup?.hide();
|
|
2778
3032
|
}
|
|
2779
|
-
if (linkContextEditing.value && instance.state.selection.from !== linkSelection?.from) linkContextEditing.value = false;
|
|
2780
3033
|
},
|
|
2781
3034
|
onUpdate: ({ editor: instance }) => {
|
|
2782
3035
|
const value = instance.getJSON();
|
|
@@ -2788,6 +3041,16 @@ var LoomaEditor = defineComponent({
|
|
|
2788
3041
|
editorStateVersion.value += 1;
|
|
2789
3042
|
});
|
|
2790
3043
|
editor.value = session.editor;
|
|
3044
|
+
const engine = createLinkEditing({
|
|
3045
|
+
editor: session.editor,
|
|
3046
|
+
search: () => props.linkSearch,
|
|
3047
|
+
resolve: () => props.linkResolve,
|
|
3048
|
+
baseUrl: () => props.linkBaseUrl
|
|
3049
|
+
});
|
|
3050
|
+
engine.subscribe(() => {
|
|
3051
|
+
linkState.value = engine.state;
|
|
3052
|
+
});
|
|
3053
|
+
linkEditing = engine;
|
|
2791
3054
|
});
|
|
2792
3055
|
const attachDocument = (element) => {
|
|
2793
3056
|
if (element instanceof HTMLElement) session?.attach(element);
|
|
@@ -2795,56 +3058,9 @@ var LoomaEditor = defineComponent({
|
|
|
2795
3058
|
onBeforeUnmount(() => {
|
|
2796
3059
|
const element = session?.editor.options.element;
|
|
2797
3060
|
element?.parentNode?.replaceChild(element.cloneNode(true), element);
|
|
3061
|
+
linkEditing?.destroy();
|
|
2798
3062
|
session?.destroy();
|
|
2799
3063
|
});
|
|
2800
|
-
watch(() => {
|
|
2801
|
-
editorStateVersion.value;
|
|
2802
|
-
return editor.value?.isActive("link") ? editor.value.getAttributes("link").href : null;
|
|
2803
|
-
}, async (href) => {
|
|
2804
|
-
const sequence = ++linkResolveSequence;
|
|
2805
|
-
linkContextTarget.value = null;
|
|
2806
|
-
if (!href || !props.linkResolve) return;
|
|
2807
|
-
if (linkTargetCache.has(href)) {
|
|
2808
|
-
linkContextTarget.value = linkTargetCache.get(href) ?? null;
|
|
2809
|
-
return;
|
|
2810
|
-
}
|
|
2811
|
-
try {
|
|
2812
|
-
const target = await props.linkResolve(href);
|
|
2813
|
-
linkTargetCache.set(href, target);
|
|
2814
|
-
if (sequence === linkResolveSequence) linkContextTarget.value = target;
|
|
2815
|
-
} catch {}
|
|
2816
|
-
});
|
|
2817
|
-
watch([
|
|
2818
|
-
linkQuery,
|
|
2819
|
-
linkMode,
|
|
2820
|
-
linkOpen,
|
|
2821
|
-
linkContextEditing
|
|
2822
|
-
], async ([query, mode, open, contextEditing]) => {
|
|
2823
|
-
const sequence = ++linkSearchSequence;
|
|
2824
|
-
linkResults.value = [];
|
|
2825
|
-
linkSearching.value = false;
|
|
2826
|
-
if (!props.linkSearch || mode !== "target" || !open && !contextEditing || query.trim().length < 2 || linkSelectedTarget.value && query === linkSelectedTarget.value.label) return;
|
|
2827
|
-
linkSearching.value = true;
|
|
2828
|
-
try {
|
|
2829
|
-
const results = await props.linkSearch(query.trim());
|
|
2830
|
-
if (sequence === linkSearchSequence) linkResults.value = results.filter((target) => validLinkHref(target.href));
|
|
2831
|
-
} catch {
|
|
2832
|
-
if (sequence === linkSearchSequence) linkError.value = "Search is unavailable. Try again.";
|
|
2833
|
-
} finally {
|
|
2834
|
-
if (sequence === linkSearchSequence) linkSearching.value = false;
|
|
2835
|
-
}
|
|
2836
|
-
});
|
|
2837
|
-
const selectLinkTarget = (target) => {
|
|
2838
|
-
linkSelectedTarget.value = target;
|
|
2839
|
-
linkTargetCache.set(target.href, target);
|
|
2840
|
-
linkHref.value = target.href;
|
|
2841
|
-
linkQuery.value = target.label;
|
|
2842
|
-
linkResults.value = [];
|
|
2843
|
-
linkNewTab.value = false;
|
|
2844
|
-
linkError.value = "";
|
|
2845
|
-
if (linkTextEditable.value && !linkText.value.trim()) linkText.value = target.label;
|
|
2846
|
-
applyLink();
|
|
2847
|
-
};
|
|
2848
3064
|
const openLinkEditor = (context = false) => {
|
|
2849
3065
|
const instance = editor.value;
|
|
2850
3066
|
if (!instance || !props.editable) return;
|
|
@@ -2854,130 +3070,37 @@ var LoomaEditor = defineComponent({
|
|
|
2854
3070
|
linkPressedSelection = null;
|
|
2855
3071
|
const from = selection?.from ?? current.from;
|
|
2856
3072
|
const to = selection?.to ?? current.to;
|
|
2857
|
-
|
|
2858
|
-
const existing = instance.isActive("link");
|
|
2859
|
-
linkSelection = {
|
|
2860
|
-
from,
|
|
2861
|
-
to,
|
|
2862
|
-
existing
|
|
2863
|
-
};
|
|
2864
|
-
linkTextEditable.value = empty && !existing;
|
|
2865
|
-
const attrs = existing ? instance.getAttributes("link") : {};
|
|
2866
|
-
linkHref.value = typeof attrs.href === "string" ? attrs.href : "";
|
|
2867
|
-
linkNewTab.value = attrs.target !== "_self";
|
|
2868
|
-
linkText.value = empty && !existing ? "" : instance.state.doc.textBetween(from, to);
|
|
2869
|
-
linkError.value = "";
|
|
2870
|
-
linkSelectedTarget.value = linkTargetCache.get(linkHref.value) ?? null;
|
|
2871
|
-
linkMode.value = props.linkSearch && (!existing || linkSelectedTarget.value) ? "target" : "url";
|
|
2872
|
-
linkQuery.value = linkSelectedTarget.value?.label ?? linkHref.value;
|
|
2873
|
-
linkContextEditing.value = context;
|
|
2874
|
-
linkOpen.value = !context;
|
|
2875
|
-
if (existing && props.linkResolve && !linkTargetCache.has(linkHref.value)) {
|
|
2876
|
-
const href = linkHref.value;
|
|
2877
|
-
props.linkResolve(href).then((target) => {
|
|
2878
|
-
linkTargetCache.set(href, target);
|
|
2879
|
-
if (linkHref.value === href && target) {
|
|
2880
|
-
linkSelectedTarget.value = target;
|
|
2881
|
-
linkMode.value = "target";
|
|
2882
|
-
linkQuery.value = target.label;
|
|
2883
|
-
}
|
|
2884
|
-
}).catch(() => {});
|
|
2885
|
-
}
|
|
3073
|
+
linkEditing?.open(from, to, context ? "context" : "popover");
|
|
2886
3074
|
nextTick(() => {
|
|
2887
3075
|
if (context) (props.toolbarMode === "contextual" && floatingFormattingMode() && formattingSelectionActive(instance) ? formattingPopup : linkPopup)?.setAnchor(() => posToDOMRect(instance.view, instance.state.selection.from, instance.state.selection.to));
|
|
2888
|
-
(
|
|
3076
|
+
(linkState.value.mode === "url" ? linkDestinationInput() : linkFormRoot.value?.querySelector("[data-link-search]"))?.focus();
|
|
2889
3077
|
});
|
|
2890
3078
|
};
|
|
2891
|
-
const applyLink = (reportError = false) => {
|
|
2892
|
-
const instance = editor.value;
|
|
2893
|
-
const selection = linkSelection;
|
|
2894
|
-
if (!instance || !selection) return false;
|
|
2895
|
-
const href = linkMode.value === "target" ? validLinkHref(linkSelectedTarget.value?.href ?? "") : validLinkHref(linkHref.value);
|
|
2896
|
-
if (!href) {
|
|
2897
|
-
if (reportError) linkError.value = linkMode.value === "target" ? "Choose a destination from the results." : "Enter an http, https, mailto, tel, or relative URL.";
|
|
2898
|
-
return false;
|
|
2899
|
-
}
|
|
2900
|
-
if (linkMode.value === "url" && props.linkSearch && !selection.existing && !/^(https?:|mailto:|tel:)/i.test(href)) {
|
|
2901
|
-
if (reportError) linkError.value = "Enter a full website, email, or phone URL, or choose a destination.";
|
|
2902
|
-
return false;
|
|
2903
|
-
}
|
|
2904
|
-
const attrs = {
|
|
2905
|
-
href: props.linkBaseUrl ? siteRelativeHref(href, props.linkBaseUrl) : href,
|
|
2906
|
-
target: linkNewTab.value ? "_blank" : "_self",
|
|
2907
|
-
rel: linkNewTab.value ? "noopener noreferrer" : null
|
|
2908
|
-
};
|
|
2909
|
-
const current = instance.state.selection;
|
|
2910
|
-
const chain = instance.chain().setTextSelection({
|
|
2911
|
-
from: selection.from,
|
|
2912
|
-
to: selection.to
|
|
2913
|
-
});
|
|
2914
|
-
if (linkTextEditable.value) {
|
|
2915
|
-
const text = linkText.value.trim();
|
|
2916
|
-
if (!text) {
|
|
2917
|
-
if (reportError) linkError.value = "Enter link text.";
|
|
2918
|
-
return false;
|
|
2919
|
-
}
|
|
2920
|
-
linkSelection = {
|
|
2921
|
-
from: selection.from,
|
|
2922
|
-
to: selection.from + text.length,
|
|
2923
|
-
existing: true
|
|
2924
|
-
};
|
|
2925
|
-
chain.insertContent({
|
|
2926
|
-
type: "text",
|
|
2927
|
-
text,
|
|
2928
|
-
marks: [{
|
|
2929
|
-
type: "link",
|
|
2930
|
-
attrs
|
|
2931
|
-
}]
|
|
2932
|
-
}).setTextSelection(selection.from).run();
|
|
2933
|
-
} else {
|
|
2934
|
-
if (selection.existing && selection.from === selection.to) chain.extendMarkRange("link");
|
|
2935
|
-
chain.setLink(attrs).setTextSelection({
|
|
2936
|
-
from: current.from,
|
|
2937
|
-
to: current.to
|
|
2938
|
-
}).run();
|
|
2939
|
-
selection.existing = true;
|
|
2940
|
-
}
|
|
2941
|
-
linkError.value = "";
|
|
2942
|
-
return true;
|
|
2943
|
-
};
|
|
2944
|
-
const removeCurrentLink = () => {
|
|
2945
|
-
const instance = editor.value;
|
|
2946
|
-
if (!instance?.isActive("link")) return;
|
|
2947
|
-
instance.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
2948
|
-
linkContextEditing.value = false;
|
|
2949
|
-
};
|
|
2950
3079
|
const dismissLink = (restoreFocus = true) => {
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
if (restoreFocus && selection) editor.value?.chain().focus().setTextSelection(selection.to).run();
|
|
3080
|
+
linkEditing?.close();
|
|
3081
|
+
const range = linkState.value.range;
|
|
3082
|
+
if (restoreFocus && range) editor.value?.chain().focus().setTextSelection(range.to).run();
|
|
2955
3083
|
dismissedFormattingSelection = editor.value?.state.selection ?? null;
|
|
2956
3084
|
heldFormattingSelection = null;
|
|
2957
3085
|
linkPopup?.hide();
|
|
2958
3086
|
formattingPopup?.hide();
|
|
2959
3087
|
};
|
|
2960
|
-
const linkStatus = () => {
|
|
2961
|
-
if (linkSearching.value) return "Searching…";
|
|
2962
|
-
if (linkSelectedTarget.value) return "";
|
|
2963
|
-
if (linkQuery.value.trim().length < 2) return "Type at least two letters to search.";
|
|
2964
|
-
return linkResults.value.length || linkError.value ? "" : "No results found.";
|
|
2965
|
-
};
|
|
2966
3088
|
const renderLinkForm = () => {
|
|
2967
|
-
const
|
|
3089
|
+
const state = linkState.value;
|
|
3090
|
+
const shown = state.selected ? [state.selected] : state.results;
|
|
2968
3091
|
return h(EditorLinkForm, {
|
|
2969
3092
|
ref: setLinkFormRoot,
|
|
2970
3093
|
class: "looma-editor__link-form",
|
|
2971
|
-
existing: Boolean(
|
|
3094
|
+
existing: Boolean(state.range?.existing),
|
|
2972
3095
|
searchable: Boolean(props.linkSearch),
|
|
2973
|
-
mode:
|
|
2974
|
-
value: props.linkSearch ?
|
|
3096
|
+
mode: state.mode,
|
|
3097
|
+
value: props.linkSearch ? state.query : state.href,
|
|
2975
3098
|
placeholder: props.linkSearch ? `Search ${props.linkTargetLabel.toLowerCase()}s or paste a URL…` : "https://example.com",
|
|
2976
|
-
newTab:
|
|
2977
|
-
textEditable:
|
|
2978
|
-
text:
|
|
2979
|
-
error:
|
|
2980
|
-
status:
|
|
3099
|
+
newTab: state.newTab,
|
|
3100
|
+
textEditable: state.textEditable,
|
|
3101
|
+
text: state.text,
|
|
3102
|
+
error: state.error,
|
|
3103
|
+
status: linkFormStatus(state),
|
|
2981
3104
|
results: shown.map(({ id, label, detail }) => detail ? {
|
|
2982
3105
|
id,
|
|
2983
3106
|
label,
|
|
@@ -2986,46 +3109,23 @@ var LoomaEditor = defineComponent({
|
|
|
2986
3109
|
id,
|
|
2987
3110
|
label
|
|
2988
3111
|
}),
|
|
2989
|
-
selected:
|
|
2990
|
-
onDestination: (event) =>
|
|
2991
|
-
|
|
2992
|
-
if (props.linkSearch) {
|
|
2993
|
-
linkQuery.value = value;
|
|
2994
|
-
linkMode.value = /^(https?:\/\/|mailto:|tel:)/i.test(value.trim()) || linkSelection?.existing && /^(\/|\.\.?\/|#|\?)/.test(value.trim()) ? "url" : "target";
|
|
2995
|
-
linkSelectedTarget.value = null;
|
|
2996
|
-
}
|
|
2997
|
-
linkHref.value = value;
|
|
2998
|
-
linkError.value = "";
|
|
2999
|
-
applyLink();
|
|
3000
|
-
},
|
|
3001
|
-
onClear: () => {
|
|
3002
|
-
linkQuery.value = "";
|
|
3003
|
-
linkHref.value = "";
|
|
3004
|
-
linkMode.value = "target";
|
|
3005
|
-
linkSelectedTarget.value = null;
|
|
3006
|
-
linkError.value = "";
|
|
3007
|
-
},
|
|
3112
|
+
selected: state.selected?.id ?? "",
|
|
3113
|
+
onDestination: (event) => linkEditing?.setDestination(event.detail.value),
|
|
3114
|
+
onClear: () => linkEditing?.clear(),
|
|
3008
3115
|
onChoose: (event) => {
|
|
3009
3116
|
const target = shown.find(({ id }) => id === event.detail.id);
|
|
3010
|
-
if (target)
|
|
3011
|
-
},
|
|
3012
|
-
onNewTab: (event) => {
|
|
3013
|
-
linkNewTab.value = event.detail.newTab;
|
|
3014
|
-
applyLink();
|
|
3015
|
-
},
|
|
3016
|
-
onText: (event) => {
|
|
3017
|
-
linkText.value = event.detail.text;
|
|
3018
|
-
linkError.value = "";
|
|
3019
|
-
applyLink();
|
|
3117
|
+
if (target) linkEditing?.choose(target);
|
|
3020
3118
|
},
|
|
3119
|
+
onNewTab: (event) => linkEditing?.setNewTab(event.detail.newTab),
|
|
3120
|
+
onText: (event) => linkEditing?.setText(event.detail.text),
|
|
3021
3121
|
onSave: () => {
|
|
3022
|
-
if (
|
|
3122
|
+
if (linkEditing?.apply(true)) dismissLink();
|
|
3023
3123
|
},
|
|
3024
3124
|
onDismiss: () => dismissLink()
|
|
3025
3125
|
});
|
|
3026
3126
|
};
|
|
3027
3127
|
const renderLinkContextCard = (instance) => {
|
|
3028
|
-
if (
|
|
3128
|
+
if (linkState.value.surface === "context") return h(Card, {
|
|
3029
3129
|
variant: "elevated",
|
|
3030
3130
|
padding: "sm",
|
|
3031
3131
|
size: "sm",
|
|
@@ -3033,7 +3133,7 @@ var LoomaEditor = defineComponent({
|
|
|
3033
3133
|
}, () => [renderLinkForm()]);
|
|
3034
3134
|
const rawHref = instance.getAttributes("link").href;
|
|
3035
3135
|
const href = typeof rawHref === "string" ? rawHref : "";
|
|
3036
|
-
const target =
|
|
3136
|
+
const target = linkState.value.current;
|
|
3037
3137
|
return h(EditorLinkCard, {
|
|
3038
3138
|
class: "looma-editor__link-context",
|
|
3039
3139
|
href,
|
|
@@ -3042,7 +3142,7 @@ var LoomaEditor = defineComponent({
|
|
|
3042
3142
|
detail: target?.detail ?? "",
|
|
3043
3143
|
targetLabel: props.linkTargetLabel,
|
|
3044
3144
|
onEdit: () => openLinkEditor(true),
|
|
3045
|
-
onRemove:
|
|
3145
|
+
onRemove: () => linkEditing?.remove()
|
|
3046
3146
|
});
|
|
3047
3147
|
};
|
|
3048
3148
|
const runBlockAction = (value) => {
|
|
@@ -3087,11 +3187,11 @@ var LoomaEditor = defineComponent({
|
|
|
3087
3187
|
if (!editable) chipOpen.value = false;
|
|
3088
3188
|
});
|
|
3089
3189
|
watch([
|
|
3090
|
-
|
|
3190
|
+
() => linkState.value.surface === "popover",
|
|
3091
3191
|
tablePickerOpen,
|
|
3092
3192
|
formatOpen
|
|
3093
|
-
], ([
|
|
3094
|
-
if (
|
|
3193
|
+
], ([linking, table, format]) => {
|
|
3194
|
+
if (linking || table || format || !floatingFormattingMode() || editor.value?.isFocused || formattingPopup?.element.contains(document.activeElement)) return;
|
|
3095
3195
|
heldFormattingSelection = null;
|
|
3096
3196
|
formattingPopup?.hide();
|
|
3097
3197
|
});
|
|
@@ -3194,10 +3294,10 @@ var LoomaEditor = defineComponent({
|
|
|
3194
3294
|
}))
|
|
3195
3295
|
];
|
|
3196
3296
|
});
|
|
3197
|
-
const chooseCodeLanguage = (
|
|
3297
|
+
const chooseCodeLanguage = (value) => {
|
|
3198
3298
|
const instance = editor.value;
|
|
3199
|
-
if (!instance
|
|
3200
|
-
const language =
|
|
3299
|
+
if (!instance) return;
|
|
3300
|
+
const language = value === "auto" ? null : value;
|
|
3201
3301
|
if (language !== null && !codeLanguageNames.includes(language)) return;
|
|
3202
3302
|
const node = instance.state.doc.nodeAt(codeUi.pos);
|
|
3203
3303
|
if (node?.type.name !== "codeBlock") return;
|
|
@@ -3336,7 +3436,7 @@ var LoomaEditor = defineComponent({
|
|
|
3336
3436
|
const onDocumentPointerDown = (event) => {
|
|
3337
3437
|
const path = typeof event.composedPath === "function" ? event.composedPath() : [];
|
|
3338
3438
|
const inElement = (element) => Boolean(element && (path.includes(element) || event.target instanceof Node && element.contains(event.target)));
|
|
3339
|
-
if (
|
|
3439
|
+
if (linkState.value.surface !== "none" && !inElement(linkFormRoot.value)) dismissLink(false);
|
|
3340
3440
|
const inTableUi = inElement(tableToolbarShell.value) || inElement(tableOverlayShell.value) || inElement(tableMenuShell.value) || inElement(mobileToolbarShell.value);
|
|
3341
3441
|
tableInteractionActive = inTableUi;
|
|
3342
3442
|
if (inTableUi) setTimeout(() => {
|
|
@@ -3402,7 +3502,7 @@ var LoomaEditor = defineComponent({
|
|
|
3402
3502
|
editor: instance,
|
|
3403
3503
|
element,
|
|
3404
3504
|
pluginKey: "looma-text-formatting-menu",
|
|
3405
|
-
shouldShow: ({ editor: menuEditor }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && !slash.active && !mention.active && (!props.editable && (props.selectionActions.length > 0 || props.rangeActions.length > 0) || menuEditor.isFocused || formattingPopup?.element.contains(document.activeElement) ||
|
|
3505
|
+
shouldShow: ({ editor: menuEditor }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && !slash.active && !mention.active && (!props.editable && (props.selectionActions.length > 0 || props.rangeActions.length > 0) || menuEditor.isFocused || formattingPopup?.element.contains(document.activeElement) || linkState.value.surface === "context" || formattingPickerOpen()) && formattingSelectionActive(menuEditor),
|
|
3406
3506
|
boundary: () => props.toolbarMode === "contextual" ? root.value : null,
|
|
3407
3507
|
related: editorRoot,
|
|
3408
3508
|
onHide: () => instance.isEditable && floatingFormattingMode() && formattingPickerOpen() && formattingSelectionActive(instance) ? false : void 0,
|
|
@@ -3414,7 +3514,7 @@ var LoomaEditor = defineComponent({
|
|
|
3414
3514
|
editor: instance,
|
|
3415
3515
|
element,
|
|
3416
3516
|
pluginKey: "looma-link-context-menu",
|
|
3417
|
-
shouldShow: ({ editor: menuEditor, from, to }) => from === to && menuEditor.isActive("link") &&
|
|
3517
|
+
shouldShow: ({ editor: menuEditor, from, to }) => from === to && menuEditor.isActive("link") && linkState.value.surface !== "popover" && !dismissedFormattingSelection?.eq(menuEditor.state.selection) && Boolean(menuEditor.isFocused || linkState.value.surface === "context" || linkPopup?.element.contains(document.activeElement)) && !(props.toolbarMode === "contextual" && floatingFormattingMode() && formattingSelectionActive(menuEditor)),
|
|
3418
3518
|
maxWidth: () => Math.min(480, window.innerWidth - 24),
|
|
3419
3519
|
related: editorRoot,
|
|
3420
3520
|
onDismiss: (reason) => reason === "escape" ? dismissFormatting() : linkPopup?.hide()
|
|
@@ -3832,7 +3932,7 @@ var LoomaEditor = defineComponent({
|
|
|
3832
3932
|
label: "Link",
|
|
3833
3933
|
size: "sm",
|
|
3834
3934
|
variant: "ghost",
|
|
3835
|
-
"aria-expanded":
|
|
3935
|
+
"aria-expanded": linkState.value.surface === "popover" ? "true" : "false",
|
|
3836
3936
|
"aria-pressed": instance.isActive("link") ? "true" : "false",
|
|
3837
3937
|
"data-active": instance.isActive("link") ? "true" : "false",
|
|
3838
3938
|
onPointerenter: () => showTool(linkTriggerId, "Link", false),
|
|
@@ -4016,7 +4116,7 @@ var LoomaEditor = defineComponent({
|
|
|
4016
4116
|
ref: formattingSurfaceRef,
|
|
4017
4117
|
class: "looma-editor__floating-shell looma-editor__formatting-shell",
|
|
4018
4118
|
hidden: true
|
|
4019
|
-
}, [props.toolbarMode === "contextual" ? h(Stack, { gap: "xs" }, () => [renderToolbar(instance, true), (
|
|
4119
|
+
}, [props.toolbarMode === "contextual" ? h(Stack, { gap: "xs" }, () => [renderToolbar(instance, true), (linkState.value.surface === "context" || instance.state.selection.empty && instance.isActive("link")) && linkState.value.surface !== "popover" && formattingSelectionActive(instance) ? renderLinkContextCard(instance) : null]) : renderToolbar(instance, true, props.toolbarMode === "popover")])]) : null,
|
|
4020
4120
|
instance && props.editable ? h("div", { key: "looma-link-host" }, [h("div", {
|
|
4021
4121
|
ref: linkSurfaceRef,
|
|
4022
4122
|
class: "looma-editor__floating-shell looma-editor__link-shell",
|
|
@@ -4049,16 +4149,12 @@ var LoomaEditor = defineComponent({
|
|
|
4049
4149
|
class: "looma-editor__code-language",
|
|
4050
4150
|
style: codeUi.style,
|
|
4051
4151
|
onFocusout: () => updateCodeUi()
|
|
4052
|
-
}, [h(
|
|
4053
|
-
label: "Code language",
|
|
4054
|
-
labelVisibility: "sr-only",
|
|
4055
|
-
size: "sm",
|
|
4056
|
-
disclosure: true,
|
|
4057
|
-
filter: "label",
|
|
4152
|
+
}, [h(EditorCodeLanguage, {
|
|
4058
4153
|
value: codeUi.language,
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4154
|
+
languages: codeLanguageOptions.value,
|
|
4155
|
+
onLanguage: (event) => chooseCodeLanguage(event.detail.value)
|
|
4156
|
+
})]) : null,
|
|
4157
|
+
instance && (props.editable || props.selectionActions.length && !instance.state.selection.empty || activeRangeActions(instance).length > 0) && mobile.value && (editorFocused.value || linkState.value.surface === "popover" || !props.editable) ? h("div", {
|
|
4062
4158
|
key: "looma-mobile-toolbar-shell",
|
|
4063
4159
|
ref: mobileToolbarRef,
|
|
4064
4160
|
class: "looma-editor__mobile-toolbar-shell",
|
|
@@ -4153,17 +4249,18 @@ var LoomaEditor = defineComponent({
|
|
|
4153
4249
|
h(Popover, {
|
|
4154
4250
|
class: "looma-editor__link-popover",
|
|
4155
4251
|
size: "sm",
|
|
4156
|
-
open:
|
|
4252
|
+
open: linkState.value.surface === "popover",
|
|
4157
4253
|
for: linkPopoverAnchorId.value,
|
|
4158
4254
|
placement: mobile.value ? "top-start" : "bottom-start",
|
|
4159
4255
|
onOpen: () => openLinkEditor(),
|
|
4160
4256
|
onClose: () => {
|
|
4161
|
-
|
|
4257
|
+
if (linkState.value.surface === "popover") linkEditing?.close();
|
|
4162
4258
|
linkPressedSelection = null;
|
|
4163
4259
|
const current = editor.value?.state.selection;
|
|
4164
|
-
|
|
4260
|
+
const range = linkState.value.range;
|
|
4261
|
+
if (current && !current.empty && range && current.from === range.from && current.to === range.to) editor.value?.commands.setTextSelection(current.to);
|
|
4165
4262
|
}
|
|
4166
|
-
}, () =>
|
|
4263
|
+
}, () => linkState.value.surface === "popover" ? renderLinkForm() : null),
|
|
4167
4264
|
props.editable && props.compactToolbar && !mobile.value ? h(Popover, {
|
|
4168
4265
|
class: "looma-editor__compact-formatting",
|
|
4169
4266
|
for: formatAnchorId,
|
|
@@ -4310,4 +4407,4 @@ var LoomaEditor = defineComponent({
|
|
|
4310
4407
|
}
|
|
4311
4408
|
});
|
|
4312
4409
|
//#endregion
|
|
4313
|
-
export { EDITOR_ADAPTER_COMPONENT_TAG_MAP, EditorBlockActions, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, LoomaEditor };
|
|
4410
|
+
export { EDITOR_ADAPTER_COMPONENT_TAG_MAP, EditorBlockActions, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, LoomaEditor };
|