@threadlabs/looma 0.29.6 → 0.29.8
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/editor/code-language-picker.d.ts +29 -0
- package/editor/extensions/table-commands.d.ts +1 -0
- package/editor/node-views/divider.d.ts +8 -0
- package/editor/table-ui.d.ts +35 -0
- package/package.json +1 -1
- package/vue/components.css +55 -0
- package/vue/editor/LoomaEditor.d.ts +1 -1
- package/vue/editor/compact/index.js +1 -1
- package/vue/editor/index.js +279 -289
- package/vue/editor/divider.d.ts +0 -6
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Editor } from "@tiptap/core";
|
|
2
|
+
/** The code block the selection lies in: its position, saved language (`auto` when none), text, and element. */
|
|
3
|
+
export interface LoomaCodeBlockAtSelection {
|
|
4
|
+
readonly pos: number;
|
|
5
|
+
readonly language: string;
|
|
6
|
+
readonly text: string;
|
|
7
|
+
readonly element: HTMLElement;
|
|
8
|
+
}
|
|
9
|
+
/** The code block holding the whole selection, or null when the selection is elsewhere or spans blocks. */
|
|
10
|
+
export declare function codeBlockAtSelection(editor: Editor): LoomaCodeBlockAtSelection | null;
|
|
11
|
+
/**
|
|
12
|
+
* The picker's choices: Auto, naming the detected language when there is one; a saved language the
|
|
13
|
+
* catalog lacks, marked unavailable, so it stays selected; then every catalog language.
|
|
14
|
+
*/
|
|
15
|
+
export declare function codeLanguageChoices(language: string, detected: string | null, names: readonly string[]): Array<{
|
|
16
|
+
value: string;
|
|
17
|
+
label: string;
|
|
18
|
+
}>;
|
|
19
|
+
/** Centers the picker over the code block, above it when there is room and below it otherwise. */
|
|
20
|
+
export declare function codeLanguagePickerPlacement(block: DOMRect): {
|
|
21
|
+
top: string;
|
|
22
|
+
left: string;
|
|
23
|
+
transform: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Saves `value` as the language of the code block at `pos`, as its own undo step; `auto` clears it.
|
|
27
|
+
* @failure Returns false, and changes nothing, for a language outside `names` or no code block at `pos`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function setCodeBlockLanguage(editor: Editor, pos: number, value: string, names: readonly string[]): boolean;
|
|
@@ -53,6 +53,7 @@ export interface ActiveTableUiState {
|
|
|
53
53
|
headerColumn: boolean;
|
|
54
54
|
capabilities: TableActionCapabilities;
|
|
55
55
|
}
|
|
56
|
+
export declare const NO_TABLE_CAPABILITIES: TableActionCapabilities;
|
|
56
57
|
/**
|
|
57
58
|
* Text formatting belongs to actual text selections, not table CellSelections.
|
|
58
59
|
*
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { NodeViewRenderer } from "@tiptap/core";
|
|
2
|
+
/**
|
|
3
|
+
* A pointer-sized document divider drawn by Separator, selectable while the editor is editable.
|
|
4
|
+
* @lifecycle The editor owns the node and its deletion. Separator connects while ProseMirror keeps its
|
|
5
|
+
* element in the document and disconnects once ProseMirror removes it; destroying the view stops
|
|
6
|
+
* following the editor's editability.
|
|
7
|
+
*/
|
|
8
|
+
export declare const dividerNodeView: NodeViewRenderer;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ActiveTableUiState, type TableActionCapabilities } from "./extensions/table-commands.js";
|
|
2
|
+
import type { TableCellAlignment, TableCellBackground } from "./extensions/table-formatting.js";
|
|
3
|
+
/** What the table's controls show: the toolbar and overlay, and the selected cells' formatting and actions. */
|
|
4
|
+
export interface LoomaTableControls {
|
|
5
|
+
readonly toolbarOpen: boolean;
|
|
6
|
+
readonly overlayOpen: boolean;
|
|
7
|
+
readonly alignment: TableCellAlignment;
|
|
8
|
+
readonly background: TableCellBackground;
|
|
9
|
+
readonly headerRow: boolean;
|
|
10
|
+
readonly headerColumn: boolean;
|
|
11
|
+
readonly capabilities: TableActionCapabilities;
|
|
12
|
+
}
|
|
13
|
+
export declare const LOOMA_TABLE_CONTROLS_CLOSED: LoomaTableControls;
|
|
14
|
+
/**
|
|
15
|
+
* The controls for the table shown: its overlay on wider screens, and the toolbar and the selection's
|
|
16
|
+
* formatting only while the selection is in that table. A table that is only hovered shows its overlay
|
|
17
|
+
* with nothing selected; narrow screens use the editing dock instead of the toolbar and overlay.
|
|
18
|
+
*/
|
|
19
|
+
export declare function tableControls(state: ActiveTableUiState, selectionInTable: boolean, narrow: boolean): LoomaTableControls;
|
|
20
|
+
/**
|
|
21
|
+
* Centers the table toolbar on the table, at most 420px wide and 12px inside the viewport's sides,
|
|
22
|
+
* above the table when there is room and below it otherwise.
|
|
23
|
+
*/
|
|
24
|
+
export declare function tableToolbarPlacement(table: DOMRect, viewportWidth: number): {
|
|
25
|
+
top: string;
|
|
26
|
+
left: string;
|
|
27
|
+
maxWidth: string;
|
|
28
|
+
};
|
|
29
|
+
/** The overlay covers the table exactly. */
|
|
30
|
+
export declare const tableOverlayPlacement: (table: DOMRect) => {
|
|
31
|
+
top: string;
|
|
32
|
+
left: string;
|
|
33
|
+
width: string;
|
|
34
|
+
height: string;
|
|
35
|
+
};
|
package/package.json
CHANGED
package/vue/components.css
CHANGED
|
@@ -10965,4 +10965,59 @@ html[data-ui-input-modality="touch"]
|
|
|
10965
10965
|
width: 3px;
|
|
10966
10966
|
background: var(--ui-affordance-direct-color, var(--ui-accent-solid, #5b55d6));
|
|
10967
10967
|
}
|
|
10968
|
+
/* Generated by HTML Next 1.0.0-alpha.55. Do not edit. */
|
|
10969
|
+
@supports (-moz-appearance: none) {
|
|
10970
|
+
:where([data-component], [data-slotted]) { --html-next-scope-owner: boundary; }
|
|
10971
|
+
:where([data-slotted]:not([data-component])) { --html-next-scope-owner: projected !important; }
|
|
10972
|
+
}
|
|
10973
|
+
@supports (-moz-appearance: none) {
|
|
10974
|
+
[data-component~="ui-separator"] { --html-next-scope-owner: "ui-separator" !important; }
|
|
10975
|
+
}
|
|
10976
|
+
@scope ([data-component~="ui-separator"]) to ([data-component], [data-slotted]) {
|
|
10977
|
+
/* This component's hooks style the element they are set on, and never a nested one. */
|
|
10978
|
+
@property --ui-separator-color {
|
|
10979
|
+
syntax: "*";
|
|
10980
|
+
inherits: false;
|
|
10981
|
+
}
|
|
10982
|
+
|
|
10983
|
+
/* The root is the hr, so the rules name :host; a bare hr matches only a descendant. */
|
|
10984
|
+
:scope:where([data-component~="ui-separator"]) {
|
|
10985
|
+
box-sizing: border-box;
|
|
10986
|
+
align-self: stretch;
|
|
10987
|
+
margin: 0;
|
|
10988
|
+
border: 0;
|
|
10989
|
+
border-block-start: var(--ui-border-width) solid var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
|
|
10990
|
+
}
|
|
10991
|
+
|
|
10992
|
+
:scope:where([data-component~="ui-separator"])[data-ui-separator-state~="emphasis=strong"] {
|
|
10993
|
+
--_separator-color: var(--ui-border-strong);
|
|
10994
|
+
}
|
|
10995
|
+
|
|
10996
|
+
:scope:where([data-component~="ui-separator"])[aria-orientation="vertical"] {
|
|
10997
|
+
block-size: auto;
|
|
10998
|
+
border-block-start: 0;
|
|
10999
|
+
border-inline-start: var(--ui-border-width) solid var(--ui-separator-color, var(--_separator-color, var(--ui-border-divider)));
|
|
11000
|
+
}
|
|
11001
|
+
|
|
11002
|
+
:scope:where([data-component~="ui-separator"])[data-ui-separator-state~="selectable"] {
|
|
11003
|
+
min-block-size: var(--ui-control-size-sm);
|
|
11004
|
+
border: 0;
|
|
11005
|
+
background: linear-gradient(var(--ui-separator-color, var(--ui-border-divider)), var(--ui-separator-color, var(--ui-border-divider))) center / 100%
|
|
11006
|
+
var(--ui-border-width) no-repeat;
|
|
11007
|
+
cursor: pointer;
|
|
11008
|
+
}
|
|
11009
|
+
:scope:where([data-component~="ui-separator"])[data-ui-separator-state~="selectable"]:hover {
|
|
11010
|
+
background-color: var(--ui-surface-hover);
|
|
11011
|
+
}
|
|
11012
|
+
:scope:where([data-component~="ui-separator"])[data-ui-separator-state~="selected"] {
|
|
11013
|
+
outline: var(--ui-focus-width) solid var(--ui-focus-ring);
|
|
11014
|
+
outline-offset: var(--ui-focus-width);
|
|
11015
|
+
border-radius: var(--ui-radius-sm);
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
@scope ([data-component~="ui-separator"]) to ([data-component]) {
|
|
11019
|
+
/* This component's hooks style the element they are set on, and never a nested one. */
|
|
11020
|
+
|
|
11021
|
+
/* The root is the hr, so the rules name :host; a bare hr matches only a descendant. */
|
|
11022
|
+
}
|
|
10968
11023
|
/*$vite$:1*/
|
|
@@ -384,6 +384,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
384
384
|
linkBaseUrl: string;
|
|
385
385
|
disableHighlight: boolean;
|
|
386
386
|
codeLanguages: Readonly<Record<string, import("highlight.js").LanguageFn>> | null;
|
|
387
|
+
toolbarOpen: boolean;
|
|
387
388
|
modelValue: JSONContent;
|
|
388
389
|
slashCommands: LoomaEditorSlashCommands | undefined;
|
|
389
390
|
mentionQuery: LoomaMentionQuery | undefined;
|
|
@@ -401,5 +402,4 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
401
402
|
selectionActions: LoomaEditorSelectionAction[];
|
|
402
403
|
rangeActions: LoomaEditorRangeAction[];
|
|
403
404
|
toolbarTriggerId: string;
|
|
404
|
-
toolbarOpen: boolean;
|
|
405
405
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
|
|
2
2
|
import { Extension, Mark, Node, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
3
3
|
import { Editor as Editor$1, EditorContent } from "@tiptap/vue-3";
|
|
4
|
-
import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
|
|
5
4
|
import { Plugin } from "@tiptap/pm/state";
|
|
5
|
+
import { closeHistory, history, redo, undo } from "@tiptap/pm/history";
|
|
6
6
|
//#region ../../node_modules/.pnpm/@tiptap+extension-document@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-document/dist/index.js
|
|
7
7
|
/**
|
|
8
8
|
* The default document node which represents the top level node of the editor.
|
package/vue/editor/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { computed, createApp, 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, Callout, Card, Checkbox, Cluster, Disclosure, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, FormField, Icon, IconButton, Image, Input, List, Menu, MenuItem, Popover,
|
|
3
|
+
import { Button, Callout, Card, Checkbox, Cluster, Disclosure, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, FormField, Icon, IconButton, Image, Input, List, Menu, MenuItem, Popover, Stack, StatusMessage, 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
|
-
import { closeHistory } from "@tiptap/pm/history";
|
|
7
6
|
import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
8
7
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
9
8
|
import { createLowlight } from "lowlight";
|
|
9
|
+
import { RequestEvent, updateComponentProps } from "@nextwebwg/html-next/runtime";
|
|
10
10
|
import "@tiptap/pm/tables";
|
|
11
11
|
import "@tiptap/pm/transform";
|
|
12
|
+
import { closeHistory } from "@tiptap/pm/history";
|
|
12
13
|
import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
|
|
14
|
+
import { acceptProps, attachGeneratedController, checkBoolean, checkFormat, formatOf, hostState, keywordFormat, manageProps, toAttribute, writeAttribute } from "@nextwebwg/html-next/generated-runtime";
|
|
13
15
|
export * from "@threadlabs/looma/editor";
|
|
14
16
|
//#region src/vue/editor/primitives.ts
|
|
15
17
|
/** Each Vue editor component's element tag, for tooling that enumerates them. */
|
|
@@ -1224,179 +1226,6 @@ function createImageUploads(options) {
|
|
|
1224
1226
|
};
|
|
1225
1227
|
}
|
|
1226
1228
|
//#endregion
|
|
1227
|
-
//#region ../../node_modules/.pnpm/@nextwebwg+html-next@1.0.0-alpha.55/node_modules/@nextwebwg/html-next/dist/requests.js
|
|
1228
|
-
/**
|
|
1229
|
-
* Requests: behavior a component asks its consumer for, answered through a native event's
|
|
1230
|
-
* `respondWith` (https://nextwebwg.org/declarative-components/javascript#requests).
|
|
1231
|
-
*
|
|
1232
|
-
* Every HTML Next bundle on a page (the live runtime, compiled modules and framework helpers) keeps
|
|
1233
|
-
* its requests in one record on the document, so any consumer answers any component, and one
|
|
1234
|
-
* document listener relays late responders. Converted framework components carry this module's
|
|
1235
|
-
* source (scripts/generate-literal-source.ts), so it imports nothing.
|
|
1236
|
-
*/
|
|
1237
|
-
var ports = /* @__PURE__ */ new WeakMap();
|
|
1238
|
-
var port = (event) => {
|
|
1239
|
-
let found = ports.get(event);
|
|
1240
|
-
if (found === void 0) ports.set(event, found = {});
|
|
1241
|
-
return found;
|
|
1242
|
-
};
|
|
1243
|
-
/**
|
|
1244
|
-
* A request as its responders receive it: the nearest one answers with `respondWith`. Its static
|
|
1245
|
-
* `respond`, `request` and `hasResponder` are how code without a `host` takes part
|
|
1246
|
-
* (https://nextwebwg.org/declarative-components/javascript#outside-a-controller). They never read
|
|
1247
|
-
* `this`, so `const { respond, request, hasResponder } = RequestEvent` works.
|
|
1248
|
-
*/
|
|
1249
|
-
var RequestEvent = class extends CustomEvent {
|
|
1250
|
-
/** Aborts when the asker no longer needs the answer, or its component disconnects. */
|
|
1251
|
-
signal;
|
|
1252
|
-
constructor(type, init) {
|
|
1253
|
-
super(type, init);
|
|
1254
|
-
this.signal = init.signal;
|
|
1255
|
-
}
|
|
1256
|
-
/** Answer the request with a value or a promise of one; the event stops propagating. */
|
|
1257
|
-
respondWith(answer) {
|
|
1258
|
-
const recorded = port(this);
|
|
1259
|
-
if (this.eventPhase === 0 || recorded.a !== void 0) throw new DOMException(recorded.a === void 0 ? "A request is answered while it is dispatched." : "The request already has an answer.", "InvalidStateError");
|
|
1260
|
-
recorded.a = [answer];
|
|
1261
|
-
this.stopImmediatePropagation();
|
|
1262
|
-
}
|
|
1263
|
-
/** Report progress to the asker while the request is pending. */
|
|
1264
|
-
progress(loaded, total) {
|
|
1265
|
-
const report = this.signal.aborted ? void 0 : ports.get(this)?.p;
|
|
1266
|
-
try {
|
|
1267
|
-
report?.(loaded, total);
|
|
1268
|
-
} catch (error) {
|
|
1269
|
-
reportError(error);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Answers `name` requests that reach `target` with `answer`, records it as a known responder, then
|
|
1274
|
-
* announces it so a component whose request went unanswered can ask again. Returns a function that
|
|
1275
|
-
* removes it.
|
|
1276
|
-
*/
|
|
1277
|
-
static respond(target, name, answer) {
|
|
1278
|
-
const listener = (event) => {
|
|
1279
|
-
if ("respondWith" in event) answerRequest(event, answer);
|
|
1280
|
-
};
|
|
1281
|
-
target.addEventListener(name, listener);
|
|
1282
|
-
const forget = knownResponder(target, name);
|
|
1283
|
-
target.dispatchEvent(new CustomEvent("responder", {
|
|
1284
|
-
bubbles: true,
|
|
1285
|
-
detail: { name }
|
|
1286
|
-
}));
|
|
1287
|
-
return () => {
|
|
1288
|
-
target.removeEventListener(name, listener);
|
|
1289
|
-
forget();
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Asks `name` at `target` with the request steps `host.request` uses, with no declaration to check
|
|
1294
|
-
* the detail or the answer against, no connection signal, and nothing remembered for late responders.
|
|
1295
|
-
*/
|
|
1296
|
-
static request(target, name, detail, options) {
|
|
1297
|
-
return dispatchRequest(target, target.nodeType ? target : null, name, detail ?? null, options?.signal ?? new AbortController().signal, options?.progress);
|
|
1298
|
-
}
|
|
1299
|
-
/** Whether a known responder for `name` is on the path a request from `target` takes, now. */
|
|
1300
|
-
static hasResponder(target, name) {
|
|
1301
|
-
return knownCount(target, name) > 0;
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
/** Answers a request event with a responder function, as `RequestEvent.respond` and framework function props do. */
|
|
1305
|
-
function answerRequest(event, responder) {
|
|
1306
|
-
const request = event;
|
|
1307
|
-
request.respondWith(responder(request.detail, {
|
|
1308
|
-
signal: request.signal,
|
|
1309
|
-
progress: (loaded, total) => request.progress(loaded, total)
|
|
1310
|
-
}));
|
|
1311
|
-
}
|
|
1312
|
-
var runtimeKey = Symbol.for("@nextwebwg/html-next.runtime.v1");
|
|
1313
|
-
var documentRecord = (document) => document[runtimeKey] ??= {};
|
|
1314
|
-
/** A target's document: a node's owner, a window's document, or a target outside the DOM itself. */
|
|
1315
|
-
var recordOf = (target) => documentRecord(target.nodeType === 9 ? target : target.ownerDocument ?? target.document ?? target);
|
|
1316
|
-
var requestState = (record) => record.requests ??= { g: /* @__PURE__ */ new WeakMap() };
|
|
1317
|
-
/**
|
|
1318
|
-
* Records a responder for `name` on `target` that `hasResponder` can see, until the returned function
|
|
1319
|
-
* runs. `own` counts the entries a controller's `host.on` adds on its own root, which its own
|
|
1320
|
-
* `host.hasResponder` does not count.
|
|
1321
|
-
*/
|
|
1322
|
-
function knownResponder(target, name, own) {
|
|
1323
|
-
const record = recordOf(target);
|
|
1324
|
-
const known = record.k ??= /* @__PURE__ */ new WeakMap();
|
|
1325
|
-
const names = known.get(target) ?? known.set(target, []).get(target);
|
|
1326
|
-
const change = (add) => {
|
|
1327
|
-
for (const list of own === void 0 ? [names] : [names, own]) if (add) list.push(name);
|
|
1328
|
-
else list.splice(list.indexOf(name), 1);
|
|
1329
|
-
record.w?.forEach((changed) => changed(name));
|
|
1330
|
-
};
|
|
1331
|
-
change(true);
|
|
1332
|
-
let held = true;
|
|
1333
|
-
return () => {
|
|
1334
|
-
if (held) {
|
|
1335
|
-
held = false;
|
|
1336
|
-
change(false);
|
|
1337
|
-
}
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
/** The next object on a composed event's path: an assigned slot, a parent, a shadow host, the window. */
|
|
1341
|
-
var parentOf = (node) => node.nodeType === 9 ? node.defaultView : node.nodeType === 11 ? node.host : node.nodeType ? node.assignedSlot ?? node.parentNode : null;
|
|
1342
|
-
/** How many of `names` are `name`. */
|
|
1343
|
-
var countOf = (names, name) => names?.filter((known) => known === name).length ?? 0;
|
|
1344
|
-
/** How many known responders for `name` are on the path a request from `target` takes. */
|
|
1345
|
-
function knownCount(target, name) {
|
|
1346
|
-
const known = recordOf(target).k;
|
|
1347
|
-
let count = 0;
|
|
1348
|
-
if (known !== void 0) for (let node = target; node; node = parentOf(node)) count += countOf(known.get(node), name);
|
|
1349
|
-
return count;
|
|
1350
|
-
}
|
|
1351
|
-
/** Settles with `value`, or rejects with the signal's reason when it aborts first; a late value is discarded. */
|
|
1352
|
-
function settle(value, signal) {
|
|
1353
|
-
return new Promise((resolve, reject) => {
|
|
1354
|
-
const abort = () => reject(signal.reason);
|
|
1355
|
-
if (signal.aborted) abort();
|
|
1356
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
1357
|
-
Promise.resolve(value).then((result) => {
|
|
1358
|
-
signal.removeEventListener("abort", abort);
|
|
1359
|
-
resolve(result);
|
|
1360
|
-
}, (error) => {
|
|
1361
|
-
signal.removeEventListener("abort", abort);
|
|
1362
|
-
reject(error);
|
|
1363
|
-
});
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* The request steps every asker shares: wait for the controllers of components at `from` and around
|
|
1368
|
-
* it, dispatch the `RequestEvent` at `target`, and settle with the answer, or `null` when nobody
|
|
1369
|
-
* answered. `answered` hears, during the dispatch, whether anyone did.
|
|
1370
|
-
*/
|
|
1371
|
-
async function dispatchRequest(target, from, name, detail, signal, progress, answered) {
|
|
1372
|
-
signal.throwIfAborted();
|
|
1373
|
-
const state = requestState(recordOf(target));
|
|
1374
|
-
for (;;) {
|
|
1375
|
-
const waits = [];
|
|
1376
|
-
for (let node = from; node !== null; node = node.parentNode ?? node.host ?? null) {
|
|
1377
|
-
const gate = state.g.get(node);
|
|
1378
|
-
if (gate !== void 0) waits.push(gate);
|
|
1379
|
-
}
|
|
1380
|
-
if (waits.length === 0) break;
|
|
1381
|
-
await settle(Promise.all(waits), signal);
|
|
1382
|
-
}
|
|
1383
|
-
const event = new RequestEvent(name, {
|
|
1384
|
-
detail,
|
|
1385
|
-
signal,
|
|
1386
|
-
bubbles: true,
|
|
1387
|
-
composed: true
|
|
1388
|
-
});
|
|
1389
|
-
const recorded = port(event);
|
|
1390
|
-
recorded.p = progress;
|
|
1391
|
-
target.dispatchEvent(event);
|
|
1392
|
-
answered?.(recorded.a !== void 0);
|
|
1393
|
-
try {
|
|
1394
|
-
return recorded.a === void 0 ? null : await settle(recorded.a[0], signal);
|
|
1395
|
-
} finally {
|
|
1396
|
-
recorded.p = void 0;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
//#endregion
|
|
1400
1229
|
//#region src/editor/requests.ts
|
|
1401
1230
|
/** Answers a request event with a responder function, as an HTML Next framework function prop does. */
|
|
1402
1231
|
function respondWithFunction(event, responder) {
|
|
@@ -1562,6 +1391,23 @@ function selectedTableCellElement(editor) {
|
|
|
1562
1391
|
}
|
|
1563
1392
|
//#endregion
|
|
1564
1393
|
//#region src/editor/extensions/table-commands.ts
|
|
1394
|
+
var NO_TABLE_CAPABILITIES = {
|
|
1395
|
+
canToggleHeaderRow: false,
|
|
1396
|
+
canToggleHeaderColumn: false,
|
|
1397
|
+
canMoveRowUp: false,
|
|
1398
|
+
canMoveRowDown: false,
|
|
1399
|
+
canMoveColumnLeft: false,
|
|
1400
|
+
canMoveColumnRight: false,
|
|
1401
|
+
canAddRowBefore: false,
|
|
1402
|
+
canAddRowAfter: false,
|
|
1403
|
+
canAddColumnBefore: false,
|
|
1404
|
+
canAddColumnAfter: false,
|
|
1405
|
+
canDeleteRow: false,
|
|
1406
|
+
canDeleteColumn: false,
|
|
1407
|
+
canDeleteTable: false,
|
|
1408
|
+
canMergeCells: false,
|
|
1409
|
+
canSplitCell: false
|
|
1410
|
+
};
|
|
1565
1411
|
/**
|
|
1566
1412
|
* Moves the caret to the cell an overlay action names when the action comes from a table other
|
|
1567
1413
|
* than the selected one, so table commands act on that table. A cell menu also selects its cell,
|
|
@@ -1879,6 +1725,59 @@ function selectionActionDetail(state, id) {
|
|
|
1879
1725
|
after: doc.textBetween(to, Math.min(doc.content.size, to + CONTEXT), "\n")
|
|
1880
1726
|
};
|
|
1881
1727
|
}
|
|
1728
|
+
//#endregion
|
|
1729
|
+
//#region src/editor/table-ui.ts
|
|
1730
|
+
var LOOMA_TABLE_CONTROLS_CLOSED = Object.freeze({
|
|
1731
|
+
toolbarOpen: false,
|
|
1732
|
+
overlayOpen: false,
|
|
1733
|
+
alignment: "left",
|
|
1734
|
+
background: null,
|
|
1735
|
+
headerRow: false,
|
|
1736
|
+
headerColumn: false,
|
|
1737
|
+
capabilities: Object.freeze({ ...NO_TABLE_CAPABILITIES })
|
|
1738
|
+
});
|
|
1739
|
+
/**
|
|
1740
|
+
* The controls for the table shown: its overlay on wider screens, and the toolbar and the selection's
|
|
1741
|
+
* formatting only while the selection is in that table. A table that is only hovered shows its overlay
|
|
1742
|
+
* with nothing selected; narrow screens use the editing dock instead of the toolbar and overlay.
|
|
1743
|
+
*/
|
|
1744
|
+
function tableControls(state, selectionInTable, narrow) {
|
|
1745
|
+
if (!selectionInTable) return {
|
|
1746
|
+
...LOOMA_TABLE_CONTROLS_CLOSED,
|
|
1747
|
+
overlayOpen: !narrow
|
|
1748
|
+
};
|
|
1749
|
+
return {
|
|
1750
|
+
toolbarOpen: state.showToolbar && !narrow,
|
|
1751
|
+
overlayOpen: !narrow,
|
|
1752
|
+
alignment: state.cellAlignment,
|
|
1753
|
+
background: state.cellBackground,
|
|
1754
|
+
headerRow: state.headerRow,
|
|
1755
|
+
headerColumn: state.headerColumn,
|
|
1756
|
+
capabilities: state.capabilities
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
/** Space, in pixels, the toolbar needs above a table; with less it sits below. */
|
|
1760
|
+
var ROOM_ABOVE$1 = 76;
|
|
1761
|
+
/**
|
|
1762
|
+
* Centers the table toolbar on the table, at most 420px wide and 12px inside the viewport's sides,
|
|
1763
|
+
* above the table when there is room and below it otherwise.
|
|
1764
|
+
*/
|
|
1765
|
+
function tableToolbarPlacement(table, viewportWidth) {
|
|
1766
|
+
const width = Math.min(420, viewportWidth - 24);
|
|
1767
|
+
const left = Math.min(Math.max(12, table.left + (table.width - width) / 2), Math.max(12, viewportWidth - width - 12));
|
|
1768
|
+
return {
|
|
1769
|
+
top: `${table.top > ROOM_ABOVE$1 ? table.top - 52 : table.bottom + 12}px`,
|
|
1770
|
+
left: `${left}px`,
|
|
1771
|
+
maxWidth: `${width}px`
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
/** The overlay covers the table exactly. */
|
|
1775
|
+
var tableOverlayPlacement = (table) => ({
|
|
1776
|
+
top: `${table.top}px`,
|
|
1777
|
+
left: `${table.left}px`,
|
|
1778
|
+
width: `${table.width}px`,
|
|
1779
|
+
height: `${table.height}px`
|
|
1780
|
+
});
|
|
1882
1781
|
/** Whether a viewport this wide gets the editing dock rather than floating tools. */
|
|
1883
1782
|
var usesMobileDock = (viewportWidth) => viewportWidth <= 767;
|
|
1884
1783
|
/**
|
|
@@ -2485,6 +2384,71 @@ function createToolbarTooltip(onChange) {
|
|
|
2485
2384
|
};
|
|
2486
2385
|
}
|
|
2487
2386
|
//#endregion
|
|
2387
|
+
//#region src/editor/code-language-picker.ts
|
|
2388
|
+
/** The code block holding the whole selection, or null when the selection is elsewhere or spans blocks. */
|
|
2389
|
+
function codeBlockAtSelection(editor) {
|
|
2390
|
+
const { $from, $to } = editor.state.selection;
|
|
2391
|
+
const block = $from.parent;
|
|
2392
|
+
if (block.type.name !== "codeBlock" || !$from.sameParent($to)) return null;
|
|
2393
|
+
const pos = $from.before();
|
|
2394
|
+
const element = editor.view.nodeDOM(pos);
|
|
2395
|
+
if (!(element instanceof HTMLElement)) return null;
|
|
2396
|
+
return {
|
|
2397
|
+
pos,
|
|
2398
|
+
language: typeof block.attrs.language === "string" && block.attrs.language ? block.attrs.language : "auto",
|
|
2399
|
+
text: block.textContent,
|
|
2400
|
+
element
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* The picker's choices: Auto, naming the detected language when there is one; a saved language the
|
|
2405
|
+
* catalog lacks, marked unavailable, so it stays selected; then every catalog language.
|
|
2406
|
+
*/
|
|
2407
|
+
function codeLanguageChoices(language, detected, names) {
|
|
2408
|
+
return [
|
|
2409
|
+
{
|
|
2410
|
+
value: "auto",
|
|
2411
|
+
label: detected ? `Auto (${detected.toUpperCase()})` : "Auto"
|
|
2412
|
+
},
|
|
2413
|
+
...language !== "auto" && !names.includes(language) ? [{
|
|
2414
|
+
value: language,
|
|
2415
|
+
label: `${language.toUpperCase()} (unavailable)`
|
|
2416
|
+
}] : [],
|
|
2417
|
+
...names.map((name) => ({
|
|
2418
|
+
value: name,
|
|
2419
|
+
label: name.toUpperCase()
|
|
2420
|
+
}))
|
|
2421
|
+
];
|
|
2422
|
+
}
|
|
2423
|
+
/** Space, in pixels, the picker needs above a code block; with less it sits below. */
|
|
2424
|
+
var ROOM_ABOVE = 76;
|
|
2425
|
+
/** Centers the picker over the code block, above it when there is room and below it otherwise. */
|
|
2426
|
+
function codeLanguagePickerPlacement(block) {
|
|
2427
|
+
const above = block.top > ROOM_ABOVE;
|
|
2428
|
+
return {
|
|
2429
|
+
top: `${above ? block.top - 8 : block.bottom + 8}px`,
|
|
2430
|
+
left: `${block.left + block.width / 2}px`,
|
|
2431
|
+
transform: `translate(-50%, ${above ? "-100%" : "0"})`
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Saves `value` as the language of the code block at `pos`, as its own undo step; `auto` clears it.
|
|
2436
|
+
* @failure Returns false, and changes nothing, for a language outside `names` or no code block at `pos`.
|
|
2437
|
+
*/
|
|
2438
|
+
function setCodeBlockLanguage(editor, pos, value, names) {
|
|
2439
|
+
const language = value === "auto" ? null : value;
|
|
2440
|
+
if (language !== null && !names.includes(language)) return false;
|
|
2441
|
+
const node = editor.state.doc.nodeAt(pos);
|
|
2442
|
+
if (node?.type.name !== "codeBlock") return false;
|
|
2443
|
+
editor.view.dispatch(closeHistory(editor.state.tr));
|
|
2444
|
+
editor.view.dispatch(editor.state.tr.setNodeMarkup(pos, void 0, {
|
|
2445
|
+
...node.attrs,
|
|
2446
|
+
language
|
|
2447
|
+
}));
|
|
2448
|
+
editor.view.dispatch(closeHistory(editor.state.tr));
|
|
2449
|
+
return true;
|
|
2450
|
+
}
|
|
2451
|
+
//#endregion
|
|
2488
2452
|
//#region src/editor/extensions/expand.ts
|
|
2489
2453
|
/**
|
|
2490
2454
|
* Reveals collapsed ancestors before a host navigates to a document position.
|
|
@@ -3210,45 +3174,143 @@ var expandNodeView = ({ editor, node, getPos }) => {
|
|
|
3210
3174
|
};
|
|
3211
3175
|
};
|
|
3212
3176
|
//#endregion
|
|
3213
|
-
//#region
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3177
|
+
//#region vanilla/UiSeparator.js
|
|
3178
|
+
var S = {
|
|
3179
|
+
n: [],
|
|
3180
|
+
t: [],
|
|
3181
|
+
f: import.meta.url,
|
|
3182
|
+
g: "ui-separator"
|
|
3183
|
+
};
|
|
3184
|
+
var D = {
|
|
3185
|
+
g: "ui-separator",
|
|
3186
|
+
props: {
|
|
3187
|
+
"emphasis": {
|
|
3188
|
+
"required": false,
|
|
3189
|
+
"values": ["normal", "strong"],
|
|
3190
|
+
"default": "normal",
|
|
3191
|
+
c: checkFormat("keyword", keywordFormat),
|
|
3192
|
+
t: "keyword",
|
|
3193
|
+
j: 0,
|
|
3194
|
+
m: [
|
|
3195
|
+
"p",
|
|
3196
|
+
formatOf(keywordFormat),
|
|
3197
|
+
false
|
|
3198
|
+
]
|
|
3199
|
+
},
|
|
3200
|
+
"orientation": {
|
|
3201
|
+
"required": false,
|
|
3202
|
+
"values": ["horizontal", "vertical"],
|
|
3203
|
+
"default": "horizontal",
|
|
3204
|
+
c: checkFormat("keyword", keywordFormat),
|
|
3205
|
+
t: "keyword",
|
|
3206
|
+
j: 0,
|
|
3207
|
+
m: [
|
|
3208
|
+
"p",
|
|
3209
|
+
formatOf(keywordFormat),
|
|
3210
|
+
false
|
|
3211
|
+
]
|
|
3212
|
+
},
|
|
3213
|
+
"selectable": {
|
|
3214
|
+
"required": false,
|
|
3215
|
+
"default": false,
|
|
3216
|
+
c: checkBoolean,
|
|
3217
|
+
t: "boolean",
|
|
3218
|
+
j: 0,
|
|
3219
|
+
m: "b"
|
|
3220
|
+
},
|
|
3221
|
+
"selected": {
|
|
3222
|
+
"required": false,
|
|
3223
|
+
"default": false,
|
|
3224
|
+
c: checkBoolean,
|
|
3225
|
+
t: "boolean",
|
|
3226
|
+
j: 0,
|
|
3227
|
+
m: "b"
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
function createUiSeparator(options = {}, html) {
|
|
3232
|
+
const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
|
|
3233
|
+
const element = document.createElement("hr");
|
|
3234
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
3235
|
+
if (value === null || value === void 0 || value === false) continue;
|
|
3236
|
+
element.setAttribute(name, value === true ? "" : String(value));
|
|
3237
|
+
}
|
|
3238
|
+
element.setAttribute("data-component", "ui-separator");
|
|
3239
|
+
const v = [
|
|
3240
|
+
null,
|
|
3241
|
+
null,
|
|
3242
|
+
null,
|
|
3243
|
+
null
|
|
3244
|
+
];
|
|
3245
|
+
const B = acceptProps(element, D, S.n, v, componentProps, [], html);
|
|
3246
|
+
const p0 = (r, c, d) => {
|
|
3247
|
+
if (c & 1073741826) {
|
|
3248
|
+
const x0 = v[1];
|
|
3249
|
+
const s0 = toAttribute(x0, "aria-orientation");
|
|
3250
|
+
if (s0 !== r.v0) writeAttribute(r.n, "aria-orientation", r.v0 = s0);
|
|
3251
|
+
}
|
|
3252
|
+
if (c & 13) {
|
|
3253
|
+
const s1 = hostState([
|
|
3254
|
+
"emphasis",
|
|
3255
|
+
"selectable",
|
|
3256
|
+
"selected"
|
|
3257
|
+
], [
|
|
3258
|
+
v[0],
|
|
3259
|
+
v[2],
|
|
3260
|
+
v[3]
|
|
3261
|
+
]);
|
|
3262
|
+
if (s1 !== r.v1) writeAttribute(r.n, "data-ui-separator-state", (r.v1 = s1) === "" ? null : s1);
|
|
3263
|
+
}
|
|
3264
|
+
};
|
|
3265
|
+
const R = {
|
|
3266
|
+
n: element,
|
|
3267
|
+
v0: void 0,
|
|
3268
|
+
v1: void 0
|
|
3269
|
+
};
|
|
3270
|
+
const I = { B };
|
|
3271
|
+
attachGeneratedController(element, S, v, ((c, d) => p0(R, c, d)), void 0, void 0, I);
|
|
3272
|
+
manageProps(I);
|
|
3273
|
+
return element;
|
|
3274
|
+
}
|
|
3275
|
+
//#endregion
|
|
3276
|
+
//#region src/editor/node-views/divider.ts
|
|
3277
|
+
/**
|
|
3278
|
+
* A pointer-sized document divider drawn by Separator, selectable while the editor is editable.
|
|
3279
|
+
* @lifecycle The editor owns the node and its deletion. Separator connects while ProseMirror keeps its
|
|
3280
|
+
* element in the document and disconnects once ProseMirror removes it; destroying the view stops
|
|
3281
|
+
* following the editor's editability.
|
|
3217
3282
|
*/
|
|
3218
3283
|
var dividerNodeView = ({ editor, getPos }) => {
|
|
3219
|
-
|
|
3220
|
-
const
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
}) });
|
|
3236
|
-
app.mount(mount);
|
|
3237
|
-
const dom = mount.firstElementChild;
|
|
3238
|
-
editor.on("update", refresh);
|
|
3284
|
+
let selected = false;
|
|
3285
|
+
const dom = createUiSeparator({
|
|
3286
|
+
selectable: editor.isEditable,
|
|
3287
|
+
attributes: { contenteditable: "false" }
|
|
3288
|
+
});
|
|
3289
|
+
const render = () => updateComponentProps(dom, {
|
|
3290
|
+
selectable: editor.isEditable,
|
|
3291
|
+
selected: editor.isEditable && selected
|
|
3292
|
+
});
|
|
3293
|
+
dom.addEventListener("click", () => {
|
|
3294
|
+
const position = getPos();
|
|
3295
|
+
if (!editor.isEditable || typeof position !== "number") return;
|
|
3296
|
+
editor.view.dispatch(editor.state.tr.setSelection(NodeSelection.create(editor.state.doc, position)));
|
|
3297
|
+
editor.view.focus();
|
|
3298
|
+
});
|
|
3299
|
+
editor.on("update", render);
|
|
3239
3300
|
return {
|
|
3240
3301
|
dom,
|
|
3241
3302
|
selectNode() {
|
|
3242
|
-
selected
|
|
3303
|
+
selected = true;
|
|
3304
|
+
render();
|
|
3243
3305
|
},
|
|
3244
3306
|
deselectNode() {
|
|
3245
|
-
selected
|
|
3307
|
+
selected = false;
|
|
3308
|
+
render();
|
|
3246
3309
|
},
|
|
3247
3310
|
stopEvent: () => false,
|
|
3248
3311
|
ignoreMutation: (mutation) => mutation.type !== "selection",
|
|
3249
3312
|
destroy() {
|
|
3250
|
-
editor.off("update",
|
|
3251
|
-
app.unmount();
|
|
3313
|
+
editor.off("update", render);
|
|
3252
3314
|
}
|
|
3253
3315
|
};
|
|
3254
3316
|
};
|
|
@@ -3389,23 +3451,6 @@ var EMPTY_DOCUMENT = {
|
|
|
3389
3451
|
};
|
|
3390
3452
|
var uploadProgressText = (progress) => progress && progress.total > 0 ? `Uploading image… ${Math.round(Math.min(1, progress.loaded / progress.total) * 100)}%` : "Uploading image…";
|
|
3391
3453
|
var editorInstanceSequence = 0;
|
|
3392
|
-
var EMPTY_CAPABILITIES = {
|
|
3393
|
-
canToggleHeaderRow: false,
|
|
3394
|
-
canToggleHeaderColumn: false,
|
|
3395
|
-
canMoveRowUp: false,
|
|
3396
|
-
canMoveRowDown: false,
|
|
3397
|
-
canMoveColumnLeft: false,
|
|
3398
|
-
canMoveColumnRight: false,
|
|
3399
|
-
canAddRowBefore: false,
|
|
3400
|
-
canAddRowAfter: false,
|
|
3401
|
-
canAddColumnBefore: false,
|
|
3402
|
-
canAddColumnAfter: false,
|
|
3403
|
-
canDeleteRow: false,
|
|
3404
|
-
canDeleteColumn: false,
|
|
3405
|
-
canDeleteTable: false,
|
|
3406
|
-
canMergeCells: false,
|
|
3407
|
-
canSplitCell: false
|
|
3408
|
-
};
|
|
3409
3454
|
function slashMenuRows(items) {
|
|
3410
3455
|
const groups = [];
|
|
3411
3456
|
for (const [index, item] of items.entries()) {
|
|
@@ -3695,7 +3740,7 @@ var LoomaEditor = defineComponent({
|
|
|
3695
3740
|
toolbarStyle: {},
|
|
3696
3741
|
overlayStyle: {},
|
|
3697
3742
|
menuStyle: {},
|
|
3698
|
-
capabilities:
|
|
3743
|
+
capabilities: LOOMA_TABLE_CONTROLS_CLOSED.capabilities
|
|
3699
3744
|
});
|
|
3700
3745
|
const openChipEditor = (_instance, position) => {
|
|
3701
3746
|
if (!chipEditing?.open(position)) return;
|
|
@@ -4114,16 +4159,10 @@ var LoomaEditor = defineComponent({
|
|
|
4114
4159
|
if (session.setContent(value, lastFocusedSelection) && wasFocused) rememberSelection(instance);
|
|
4115
4160
|
}, { deep: true });
|
|
4116
4161
|
const closeTableUi = () => {
|
|
4117
|
-
tableUi
|
|
4118
|
-
tableUi.overlayOpen = false;
|
|
4162
|
+
Object.assign(tableUi, LOOMA_TABLE_CONTROLS_CLOSED);
|
|
4119
4163
|
tableUi.menuOpen = false;
|
|
4120
4164
|
tableUi.menuScope = "cell";
|
|
4121
|
-
tableUi.alignment = "left";
|
|
4122
|
-
tableUi.background = null;
|
|
4123
|
-
tableUi.headerRow = false;
|
|
4124
|
-
tableUi.headerColumn = false;
|
|
4125
4165
|
tableUi.geometry = null;
|
|
4126
|
-
tableUi.capabilities = { ...EMPTY_CAPABILITIES };
|
|
4127
4166
|
overlayTableElement = null;
|
|
4128
4167
|
mobileDock.tableInactive();
|
|
4129
4168
|
};
|
|
@@ -4145,63 +4184,31 @@ var LoomaEditor = defineComponent({
|
|
|
4145
4184
|
let codeBlurTimer;
|
|
4146
4185
|
const updateCodeUi = (focusSettled = false) => {
|
|
4147
4186
|
const instance = editor.value?.isDestroyed ? void 0 : editor.value;
|
|
4148
|
-
const
|
|
4149
|
-
|
|
4150
|
-
const dom = instance && block?.type.name === "codeBlock" && selection.$from.sameParent(selection.$to) ? instance.view.nodeDOM(selection.$from.before()) : null;
|
|
4151
|
-
if (!instance || !props.editable || !codeLanguageNames.length || !(dom instanceof HTMLElement)) {
|
|
4187
|
+
const block = instance ? codeBlockAtSelection(instance) : null;
|
|
4188
|
+
if (!instance || !props.editable || !codeLanguageNames.length || !block) {
|
|
4152
4189
|
codeUi.open = false;
|
|
4153
4190
|
return;
|
|
4154
4191
|
}
|
|
4155
|
-
loadForCodeBlock(instance, block.
|
|
4192
|
+
loadForCodeBlock(instance, block.language === "auto" ? null : block.language, block.text);
|
|
4156
4193
|
if (!instance.isFocused && !codeLanguageShell.value?.contains(document.activeElement)) {
|
|
4157
4194
|
clearTimeout(codeBlurTimer);
|
|
4158
4195
|
if (codeUi.open && !focusSettled) codeBlurTimer = setTimeout(() => updateCodeUi(true), 0);
|
|
4159
4196
|
else codeUi.open = false;
|
|
4160
4197
|
return;
|
|
4161
4198
|
}
|
|
4162
|
-
const rect = dom.getBoundingClientRect();
|
|
4163
|
-
const above = rect.top > 76;
|
|
4164
4199
|
codeUi.open = true;
|
|
4165
|
-
codeUi.pos =
|
|
4166
|
-
codeUi.language =
|
|
4167
|
-
codeUi.text = block.
|
|
4168
|
-
codeUi.style =
|
|
4169
|
-
top: `${above ? rect.top - 8 : rect.bottom + 8}px`,
|
|
4170
|
-
left: `${rect.left + rect.width / 2}px`,
|
|
4171
|
-
transform: `translate(-50%, ${above ? "-100%" : "0"})`
|
|
4172
|
-
};
|
|
4200
|
+
codeUi.pos = block.pos;
|
|
4201
|
+
codeUi.language = block.language;
|
|
4202
|
+
codeUi.text = block.text;
|
|
4203
|
+
codeUi.style = codeLanguagePickerPlacement(block.element.getBoundingClientRect());
|
|
4173
4204
|
};
|
|
4174
4205
|
const codeLanguageOptions = computed(() => {
|
|
4175
4206
|
codeUi.grammarVersion;
|
|
4176
4207
|
const detected = codeUi.open && codeUi.text.trim() ? codeHighlighting.lowlight.highlightAuto(codeUi.text).data?.language : null;
|
|
4177
|
-
return
|
|
4178
|
-
{
|
|
4179
|
-
value: "auto",
|
|
4180
|
-
label: typeof detected === "string" ? `Auto (${detected.toUpperCase()})` : "Auto"
|
|
4181
|
-
},
|
|
4182
|
-
...codeUi.language !== "auto" && !codeLanguageNames.includes(codeUi.language) ? [{
|
|
4183
|
-
value: codeUi.language,
|
|
4184
|
-
label: `${codeUi.language.toUpperCase()} (unavailable)`
|
|
4185
|
-
}] : [],
|
|
4186
|
-
...codeLanguageNames.map((name) => ({
|
|
4187
|
-
value: name,
|
|
4188
|
-
label: name.toUpperCase()
|
|
4189
|
-
}))
|
|
4190
|
-
];
|
|
4208
|
+
return codeLanguageChoices(codeUi.language, typeof detected === "string" ? detected : null, codeLanguageNames);
|
|
4191
4209
|
});
|
|
4192
4210
|
const chooseCodeLanguage = (value) => {
|
|
4193
|
-
|
|
4194
|
-
if (!instance) return;
|
|
4195
|
-
const language = value === "auto" ? null : value;
|
|
4196
|
-
if (language !== null && !codeLanguageNames.includes(language)) return;
|
|
4197
|
-
const node = instance.state.doc.nodeAt(codeUi.pos);
|
|
4198
|
-
if (node?.type.name !== "codeBlock") return;
|
|
4199
|
-
instance.view.dispatch(closeHistory(instance.state.tr));
|
|
4200
|
-
instance.view.dispatch(instance.state.tr.setNodeMarkup(codeUi.pos, void 0, {
|
|
4201
|
-
...node.attrs,
|
|
4202
|
-
language
|
|
4203
|
-
}));
|
|
4204
|
-
instance.view.dispatch(closeHistory(instance.state.tr));
|
|
4211
|
+
if (editor.value) setCodeBlockLanguage(editor.value, codeUi.pos, value, codeLanguageNames);
|
|
4205
4212
|
};
|
|
4206
4213
|
const onCodeTransaction = () => updateCodeUi();
|
|
4207
4214
|
watch(() => props.editable, () => nextTick(onCodeTransaction));
|
|
@@ -4230,31 +4237,14 @@ var LoomaEditor = defineComponent({
|
|
|
4230
4237
|
overlayTableElement = table;
|
|
4231
4238
|
if (selectedTableIsActive) mobileDock.tableActive();
|
|
4232
4239
|
const rect = table.getBoundingClientRect();
|
|
4233
|
-
|
|
4234
|
-
const toolbarLeft = Math.min(Math.max(12, rect.left + (rect.width - maxToolbarWidth) / 2), Math.max(12, window.innerWidth - maxToolbarWidth - 12));
|
|
4235
|
-
tableUi.toolbarOpen = selectedTableIsActive && state.showToolbar && !mobile.value;
|
|
4236
|
-
tableUi.overlayOpen = !mobile.value;
|
|
4237
|
-
tableUi.alignment = selectedTableIsActive ? state.cellAlignment : "left";
|
|
4238
|
-
tableUi.background = selectedTableIsActive ? state.cellBackground : null;
|
|
4239
|
-
tableUi.headerRow = selectedTableIsActive && state.headerRow;
|
|
4240
|
-
tableUi.headerColumn = selectedTableIsActive && state.headerColumn;
|
|
4241
|
-
tableUi.capabilities = selectedTableIsActive ? state.capabilities : { ...EMPTY_CAPABILITIES };
|
|
4240
|
+
Object.assign(tableUi, tableControls(state, selectedTableIsActive, mobile.value));
|
|
4242
4241
|
const selectedCell = selectedTableIsActive ? selectedTableCellElement(instance) : null;
|
|
4243
4242
|
const geometry = measureTableOverlayGeometry(table, selectedCell, hoveredTableCell && table.contains(hoveredTableCell) ? hoveredTableCell : null);
|
|
4244
4243
|
tableUi.rows = Math.max(0, geometry.rowBoundaries.length - 1);
|
|
4245
4244
|
tableUi.cols = Math.max(0, geometry.columnBoundaries.length - 1);
|
|
4246
4245
|
tableUi.geometry = geometry;
|
|
4247
|
-
tableUi.toolbarStyle =
|
|
4248
|
-
|
|
4249
|
-
left: `${toolbarLeft}px`,
|
|
4250
|
-
maxWidth: `${maxToolbarWidth}px`
|
|
4251
|
-
};
|
|
4252
|
-
tableUi.overlayStyle = {
|
|
4253
|
-
top: `${rect.top}px`,
|
|
4254
|
-
left: `${rect.left}px`,
|
|
4255
|
-
width: `${rect.width}px`,
|
|
4256
|
-
height: `${rect.height}px`
|
|
4257
|
-
};
|
|
4246
|
+
tableUi.toolbarStyle = tableToolbarPlacement(rect, window.innerWidth);
|
|
4247
|
+
tableUi.overlayStyle = tableOverlayPlacement(rect);
|
|
4258
4248
|
};
|
|
4259
4249
|
const onEditorFocus = () => {
|
|
4260
4250
|
editorFocused.value = true;
|
package/vue/editor/divider.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { NodeViewRenderer } from "@tiptap/core";
|
|
2
|
-
/** A pointer-sized document divider using the shared separator's selected state.
|
|
3
|
-
* @lifecycle The editor owns the node and deletion; destroying the view releases
|
|
4
|
-
* its Vue app and editability subscription.
|
|
5
|
-
*/
|
|
6
|
-
export declare const dividerNodeView: NodeViewRenderer;
|