@olddevs/ui 4.4.0 → 4.5.1
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/CHANGELOG.md +19 -0
- package/dist/editor/RichTextEditor.d.ts +11 -4
- package/dist/editor/RichTextEditor.d.ts.map +1 -1
- package/dist/editor/RichTextEditor.js +90 -60
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/editor/RichTextView.d.ts +2 -1
- package/dist/editor/RichTextView.d.ts.map +1 -1
- package/dist/editor/RichTextView.js.map +1 -1
- package/dist/editor/core/containers.d.ts +2 -6
- package/dist/editor/core/containers.d.ts.map +1 -1
- package/dist/editor/core/containers.js +2 -1
- package/dist/editor/core/containers.js.map +1 -1
- package/dist/editor/core/customTool.d.ts +8 -2
- package/dist/editor/core/customTool.d.ts.map +1 -1
- package/dist/editor/core/customTool.js.map +1 -1
- package/dist/editor/core/customToolEngine.d.ts +9 -1
- package/dist/editor/core/customToolEngine.d.ts.map +1 -1
- package/dist/editor/core/customToolEngine.js +12 -0
- package/dist/editor/core/customToolEngine.js.map +1 -1
- package/dist/editor/core/extensions.d.ts.map +1 -1
- package/dist/editor/core/extensions.js +1 -1
- package/dist/editor/core/extensions.js.map +1 -1
- package/dist/editor/core/handle.d.ts +18 -1
- package/dist/editor/core/handle.d.ts.map +1 -1
- package/dist/editor/core/handle.js +2 -1
- package/dist/editor/core/handle.js.map +1 -1
- package/dist/editor/core/handleWatchers.d.ts +30 -0
- package/dist/editor/core/handleWatchers.d.ts.map +1 -1
- package/dist/editor/core/handleWatchers.js +15 -0
- package/dist/editor/core/handleWatchers.js.map +1 -1
- package/dist/editor/core/nodeNames.d.ts +7 -0
- package/dist/editor/core/nodeNames.d.ts.map +1 -0
- package/dist/editor/core/nodeNames.js +2 -0
- package/dist/editor/core/nodeNames.js.map +1 -0
- package/dist/editor/core/schema.d.ts.map +1 -1
- package/dist/editor/core/schema.js +4 -2
- package/dist/editor/core/schema.js.map +1 -1
- package/dist/editor/core/toolCommands.d.ts +78 -0
- package/dist/editor/core/toolCommands.d.ts.map +1 -0
- package/dist/editor/core/toolCommands.js +2 -0
- package/dist/editor/core/toolCommands.js.map +1 -0
- package/dist/editor/core/toolDefinition.d.ts +139 -0
- package/dist/editor/core/toolDefinition.d.ts.map +1 -0
- package/dist/editor/core/toolDefinition.js +28 -0
- package/dist/editor/core/toolDefinition.js.map +1 -0
- package/dist/editor/core/toolDescriptor.d.ts +71 -2
- package/dist/editor/core/toolDescriptor.d.ts.map +1 -1
- package/dist/editor/core/toolDescriptor.js +6 -2
- package/dist/editor/core/toolDescriptor.js.map +1 -1
- package/dist/editor/core/toolOptions.d.ts +157 -0
- package/dist/editor/core/toolOptions.d.ts.map +1 -0
- package/dist/editor/core/toolOptions.js +21 -0
- package/dist/editor/core/toolOptions.js.map +1 -0
- package/dist/editor/core/toolState.d.ts +46 -0
- package/dist/editor/core/toolState.d.ts.map +1 -0
- package/dist/editor/core/toolState.js +2 -0
- package/dist/editor/core/toolState.js.map +1 -0
- package/dist/editor/core/tools.d.ts +3 -1
- package/dist/editor/core/tools.d.ts.map +1 -1
- package/dist/editor/core/tools.js.map +1 -1
- package/dist/editor/core/viewIsland.d.ts.map +1 -1
- package/dist/editor/core/viewIsland.js +2 -1
- package/dist/editor/core/viewIsland.js.map +1 -1
- package/dist/editor/features/builtins.d.ts +9 -4
- package/dist/editor/features/builtins.d.ts.map +1 -1
- package/dist/editor/features/builtins.js +36 -7
- package/dist/editor/features/builtins.js.map +1 -1
- package/dist/editor/features/search/SearchPanel.d.ts.map +1 -1
- package/dist/editor/features/search/SearchPanel.js +26 -12
- package/dist/editor/features/search/SearchPanel.js.map +1 -1
- package/dist/editor/features/search/revealMatch.d.ts +16 -0
- package/dist/editor/features/search/revealMatch.d.ts.map +1 -1
- package/dist/editor/features/search/revealMatch.js +22 -0
- package/dist/editor/features/search/revealMatch.js.map +1 -1
- package/dist/editor/features/search/searchPlugin.d.ts +8 -3
- package/dist/editor/features/search/searchPlugin.d.ts.map +1 -1
- package/dist/editor/features/search/searchPlugin.js +17 -6
- package/dist/editor/features/search/searchPlugin.js.map +1 -1
- package/dist/editor/features/search/searchSnapshot.d.ts +14 -0
- package/dist/editor/features/search/searchSnapshot.d.ts.map +1 -0
- package/dist/editor/features/search/searchSnapshot.js +17 -0
- package/dist/editor/features/search/searchSnapshot.js.map +1 -0
- package/dist/editor/features/search/searchTool.d.ts +3 -0
- package/dist/editor/features/search/searchTool.d.ts.map +1 -1
- package/dist/editor/features/search/searchTool.js +30 -1
- package/dist/editor/features/search/searchTool.js.map +1 -1
- package/dist/editor/features/toc/RichTextToc.d.ts +29 -1
- package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -1
- package/dist/editor/features/toc/RichTextToc.js +64 -12
- package/dist/editor/features/toc/RichTextToc.js.map +1 -1
- package/dist/editor/features/toc/documentHeadings.d.ts +30 -0
- package/dist/editor/features/toc/documentHeadings.d.ts.map +1 -0
- package/dist/editor/features/toc/documentHeadings.js +20 -0
- package/dist/editor/features/toc/documentHeadings.js.map +1 -0
- package/dist/editor/features/toc/headings.d.ts +6 -2
- package/dist/editor/features/toc/headings.d.ts.map +1 -1
- package/dist/editor/features/toc/headings.js +4 -2
- package/dist/editor/features/toc/headings.js.map +1 -1
- package/dist/editor/features/toc/tocSnapshot.d.ts +10 -0
- package/dist/editor/features/toc/tocSnapshot.d.ts.map +1 -0
- package/dist/editor/features/toc/tocSnapshot.js +8 -0
- package/dist/editor/features/toc/tocSnapshot.js.map +1 -0
- package/dist/editor/features/toc/tocTool.d.ts +6 -0
- package/dist/editor/features/toc/tocTool.d.ts.map +1 -0
- package/dist/editor/features/toc/tocTool.js +21 -0
- package/dist/editor/features/toc/tocTool.js.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts +43 -0
- package/dist/editor/hooks/useRichTextToolState.d.ts.map +1 -0
- package/dist/editor/hooks/useRichTextToolState.js +77 -0
- package/dist/editor/hooks/useRichTextToolState.js.map +1 -0
- package/dist/editor/index.d.ts +8 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +3 -0
- package/dist/editor/index.js.map +1 -1
- package/docs/api/editor-extension.en.md +223 -23
- package/docs/api/editor-extension.md +224 -22
- package/docs/api/migration-notes.en.md +36 -1
- package/docs/api/migration-notes.md +35 -0
- package/docs/components/content/rich-text-toc.en.md +13 -11
- package/docs/components/content/rich-text-toc.md +12 -10
- package/docs/components/index.en.md +2 -2
- package/docs/components/index.md +1 -1
- package/docs/components/inputs/rich-text-editor.en.md +227 -11
- package/docs/components/inputs/rich-text-editor.md +228 -10
- package/docs/getting-started.en.md +7 -7
- package/docs/getting-started.md +6 -6
- package/docs/glossary.en.md +52 -48
- package/docs/glossary.md +51 -47
- package/package.json +1 -1
- package/src/editor/RichTextEditor.tsx +94 -39
- package/src/editor/RichTextView.tsx +2 -1
- package/src/editor/core/containers.ts +8 -6
- package/src/editor/core/customTool.ts +8 -2
- package/src/editor/core/customToolEngine.ts +20 -1
- package/src/editor/core/extensions.ts +2 -1
- package/src/editor/core/handle.ts +25 -1
- package/src/editor/core/handleWatchers.ts +45 -0
- package/src/editor/core/nodeNames.ts +7 -0
- package/src/editor/core/schema.ts +5 -2
- package/src/editor/core/toolCommands.ts +85 -0
- package/src/editor/core/toolDefinition.ts +208 -0
- package/src/editor/core/toolDescriptor.ts +94 -3
- package/src/editor/core/toolOptions.ts +211 -0
- package/src/editor/core/toolState.ts +56 -0
- package/src/editor/core/tools.ts +3 -1
- package/src/editor/core/viewIsland.ts +5 -4
- package/src/editor/features/builtins.ts +72 -13
- package/src/editor/features/search/SearchPanel.tsx +38 -14
- package/src/editor/features/search/revealMatch.ts +45 -0
- package/src/editor/features/search/searchPlugin.ts +40 -11
- package/src/editor/features/search/searchSnapshot.ts +29 -0
- package/src/editor/features/search/searchTool.ts +51 -1
- package/src/editor/features/toc/RichTextToc.tsx +122 -13
- package/src/editor/features/toc/documentHeadings.ts +59 -0
- package/src/editor/features/toc/headings.ts +10 -3
- package/src/editor/features/toc/tocSnapshot.ts +24 -0
- package/src/editor/features/toc/tocTool.ts +41 -0
- package/src/editor/hooks/useRichTextToolState.ts +153 -0
- package/src/editor/index.ts +19 -0
|
@@ -32,7 +32,8 @@ import { BlockHandle } from "./ui/BlockHandle.js";
|
|
|
32
32
|
import { expandTools } from "./features/builtins.js";
|
|
33
33
|
import {
|
|
34
34
|
createHandleMethods,
|
|
35
|
-
|
|
35
|
+
clearToolSignals,
|
|
36
|
+
publishToolSignals,
|
|
36
37
|
type RichTextEditorHandleMethods,
|
|
37
38
|
} from "./core/handle.js";
|
|
38
39
|
import { EditorToolbar, ToolbarSkeleton } from "./ui/EditorToolbar.js";
|
|
@@ -47,14 +48,14 @@ import { customToolsError, FALLBACK_EXTENSIONS } from "./core/customToolEngine.j
|
|
|
47
48
|
import { startLoad, type ToolLoad } from "./core/toolLoader.js";
|
|
48
49
|
import { skeletonHtml } from "./core/skeleton.js";
|
|
49
50
|
import { Button } from "../components/actions/Button.js";
|
|
50
|
-
import { devError } from "../lib/dev-warn.js";
|
|
51
|
+
import { devError, devWarn } from "../lib/dev-warn.js";
|
|
51
52
|
import { describeImageNotice } from "./features/image/imageNotice.js";
|
|
52
53
|
import { insertItemsFor } from "./core/insertItems.js";
|
|
53
54
|
import type { CustomColorKind } from "./features/color/customColor.js";
|
|
54
55
|
import { isImageSelection } from "./features/image/image.js";
|
|
55
56
|
import { ImagePopover } from "./features/image/ImagePopover.js";
|
|
56
57
|
import { ImageToolbar } from "./features/image/ImageToolbar.js";
|
|
57
|
-
import { textCardsOf, triggersOf } from "./core/toolDescriptor.js";
|
|
58
|
+
import { textCardsOf, toolOptions, triggersOf, type ToolBridge } from "./core/toolDescriptor.js";
|
|
58
59
|
import type { ToolPopoverRequest } from "./core/toolButtons.js";
|
|
59
60
|
import type { TextCardControl } from "./ui/TextCard.js";
|
|
60
61
|
import { SelectionPopover } from "./ui/ToolButtonView.js";
|
|
@@ -254,14 +255,18 @@ export interface RichTextEditorProps {
|
|
|
254
255
|
* клавиш, ни markdown-шорткатом, ни вставкой.
|
|
255
256
|
*
|
|
256
257
|
* Вперемешку с именами кита — описания своих Инструментов (`CustomTool`):
|
|
257
|
-
* `[...RICH_TEXT_PRESETS.full, mergeTagTool]`.
|
|
258
|
+
* `[...RICH_TEXT_PRESETS.full, mergeTagTool]`. Встроенный Инструмент с
|
|
259
|
+
* Опциями (`search`, `toc`) настраивается записью `tool()`:
|
|
260
|
+
* `[...RICH_TEXT_PRESETS.full, tool("toc", { levels: [2, 3] })]` — запись
|
|
261
|
+
* включает Инструмент и без имени, а имя из пресета уступает ей; две записи
|
|
262
|
+
* `tool()` с одним именем — ошибка поля. Неизвестное имя отбрасывается
|
|
258
263
|
* молча; описание с ошибкой (имя без Префикса потребителя, совпадение имён,
|
|
259
264
|
* Часть для движка мимо правил Схемы) — ошибка поля: движок не создаётся,
|
|
260
265
|
* значение формы остаётся входящим, причина — в консоли разработчика.
|
|
261
266
|
*
|
|
262
|
-
* `preset` и `tools
|
|
263
|
-
* этого не меняется. Чтобы сменить набор у
|
|
264
|
-
* (`key`) — история правок при этом теряется.
|
|
267
|
+
* `preset` и `tools`, а с ними и Опции Инструментов, читаются при создании
|
|
268
|
+
* движка: Схема ProseMirror после этого не меняется. Чтобы сменить набор у
|
|
269
|
+
* живого поля, перемонтируйте его (`key`) — история правок при этом теряется.
|
|
265
270
|
*/
|
|
266
271
|
tools?: readonly ToolEntry[];
|
|
267
272
|
/**
|
|
@@ -362,6 +367,9 @@ export interface RichTextEditorHandle extends RichTextEditorHandleMethods {
|
|
|
362
367
|
* Открыть панель «Поиск и замена» из кода экрана — то же, что
|
|
363
368
|
* кнопка «Поиск» и `Mod+F`; `replace` — фокус в поле замены. Без Инструмента
|
|
364
369
|
* `search`, в HTML-режиме и до монтажа — ничего.
|
|
370
|
+
*
|
|
371
|
+
* @deprecated Работает как `handle.tool("search")?.open(options)`; в разработке
|
|
372
|
+
* один раз предупреждает об этом в консоли. Будет удалён в 5.0.
|
|
365
373
|
*/
|
|
366
374
|
openSearch(options?: { replace?: boolean }): void;
|
|
367
375
|
/**
|
|
@@ -662,6 +670,8 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
662
670
|
replace: boolean;
|
|
663
671
|
query: string;
|
|
664
672
|
} | null>(null);
|
|
673
|
+
/** Переход ядра Оглавления (`TocCore`): есть, пока оно смонтировано. */
|
|
674
|
+
const tocJumpRef = useRef<((index: number) => boolean) | null>(null);
|
|
665
675
|
const sourceFieldRef = useRef<HTMLTextAreaElement>(null);
|
|
666
676
|
const sourceTextRef = useRef<string | null>(null);
|
|
667
677
|
const sourceBaseRef = useRef("");
|
|
@@ -794,8 +804,9 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
794
804
|
const openSource = () => {
|
|
795
805
|
const current = editorRef.current;
|
|
796
806
|
if (!current) return;
|
|
797
|
-
// Документа на экране не будет — искать в нём негде.
|
|
798
|
-
|
|
807
|
+
// Документа на экране не будет — искать в нём негде. Подсветка бывает и без
|
|
808
|
+
// панели (команда `setQuery`): `next()` в скрытом Документе вышла бы удачной.
|
|
809
|
+
if (enabledTools.has("search")) closeSearch(false);
|
|
799
810
|
const text = writeSource(current);
|
|
800
811
|
switchFocusRef.current = "source";
|
|
801
812
|
setSource({ text, minHeight: current.view.dom.getBoundingClientRect().height });
|
|
@@ -816,18 +827,25 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
816
827
|
|
|
817
828
|
/*
|
|
818
829
|
Открыть панель поиска: кнопка Панели, `Mod+F`/`Mod+Alt+F` из движка и
|
|
819
|
-
`
|
|
830
|
+
команда `open` Инструмента. Выделенный текст одного Блока становится запросом,
|
|
820
831
|
как в редакторах кода; выделение через Блоки — нет: запрос не переходит
|
|
821
|
-
границу Блока, и такой текст не нашёлся бы.
|
|
822
|
-
экране нет — искать негде, и `Mod+F`
|
|
832
|
+
границу Блока, и такой текст не нашёлся бы. Явный `query` команды главнее
|
|
833
|
+
выделения. В HTML-режиме Документа на экране нет — искать негде, и `Mod+F`
|
|
834
|
+
остаётся браузеру.
|
|
823
835
|
*/
|
|
824
|
-
const openSearch = (replace: boolean): boolean => {
|
|
836
|
+
const openSearch = (replace: boolean, query?: string): boolean => {
|
|
825
837
|
const current = editorRef.current;
|
|
826
838
|
if (!current || !enabledTools.has("search") || sourceTextRef.current !== null) return false;
|
|
827
839
|
const { $from, $to, empty } = current.state.selection;
|
|
828
|
-
const
|
|
840
|
+
const selected =
|
|
829
841
|
!empty && $from.sameParent($to) ? current.state.doc.textBetween($from.pos, $to.pos) : "";
|
|
830
|
-
setSearchPanel((previous) => ({
|
|
842
|
+
setSearchPanel((previous) => ({
|
|
843
|
+
token: (previous?.token ?? 0) + 1,
|
|
844
|
+
replace,
|
|
845
|
+
query: query ?? selected,
|
|
846
|
+
}));
|
|
847
|
+
// Подсветка и переходы — сразу, не дожидаясь ленивой панели.
|
|
848
|
+
if (query !== undefined) current.commands.setSearch(query);
|
|
831
849
|
return true;
|
|
832
850
|
};
|
|
833
851
|
const openSearchRef = useRef(openSearch);
|
|
@@ -849,6 +867,7 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
849
867
|
chain.clearSearch().run();
|
|
850
868
|
}
|
|
851
869
|
setSearchPanel(null);
|
|
870
|
+
return true;
|
|
852
871
|
};
|
|
853
872
|
|
|
854
873
|
// Пункты Меню вставки — из того же замороженного набора, что и Схема.
|
|
@@ -1010,7 +1029,9 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1010
1029
|
фиксированной Панелью: её высота меряется, потому что Панель переносит
|
|
1011
1030
|
ряд кнопок на узком поле. В прокручиваемом поле край — верх области.
|
|
1012
1031
|
*/
|
|
1013
|
-
const
|
|
1032
|
+
const tocOptions = toolOptions(enabledTools, "toc");
|
|
1033
|
+
const tocOn = enabledTools.has("toc") && tocOptions !== undefined;
|
|
1034
|
+
const tocOffsetVar = tocOptions?.offsetVar ?? "--topbar-h";
|
|
1014
1035
|
const [toolbarShell, setToolbarShell] = useState<HTMLDivElement | null>(null);
|
|
1015
1036
|
const [toolbarHeight, setToolbarHeight] = useState(0);
|
|
1016
1037
|
useEffect(() => {
|
|
@@ -1029,8 +1050,8 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1029
1050
|
подсветка и переход считают от верха области и этот край не используют.
|
|
1030
1051
|
*/
|
|
1031
1052
|
const tocStickyTop = useCallback(
|
|
1032
|
-
() => (stickyOffset(
|
|
1033
|
-
[toolbarHeight]
|
|
1053
|
+
() => (stickyOffset(tocOffsetVar) ?? 0) + toolbarHeight,
|
|
1054
|
+
[tocOffsetVar, toolbarHeight]
|
|
1034
1055
|
);
|
|
1035
1056
|
/*
|
|
1036
1057
|
Переход в правке ставит каретку в конец заголовка, как структура документа
|
|
@@ -1351,22 +1372,36 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1351
1372
|
};
|
|
1352
1373
|
}, [throttle, name]);
|
|
1353
1374
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1375
|
+
const toolBridge: ToolBridge = {
|
|
1376
|
+
openSearch,
|
|
1377
|
+
// Фокус и каретку не трогаем: команду зовёт код экрана, а не автор в панели.
|
|
1378
|
+
closeSearch: () => closeSearch(false),
|
|
1379
|
+
sourceOpen: () => sourceTextRef.current !== null,
|
|
1380
|
+
jumpToc: (index) => tocJumpRef.current?.(index) ?? false,
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
useImperativeHandle(ref, () => {
|
|
1384
|
+
const methods = createHandleMethods({
|
|
1357
1385
|
editor,
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1386
|
+
editable,
|
|
1387
|
+
known: () => knownRef.current,
|
|
1388
|
+
initialDocument: () => initialDocumentRef.current,
|
|
1389
|
+
tables: enabledTools.has("table"),
|
|
1390
|
+
sourceOpen: () => sourceTextRef.current !== null,
|
|
1391
|
+
sourceField: () => sourceFieldRef.current,
|
|
1392
|
+
applySource: () => applySourceRef.current(),
|
|
1393
|
+
rewriteSource,
|
|
1394
|
+
fieldEmpty,
|
|
1395
|
+
commands: (name) => {
|
|
1396
|
+
const descriptor = enabledTools.descriptors.find((entry) => entry.name === name);
|
|
1397
|
+
return editor && !editor.isDestroyed && descriptor?.commands
|
|
1398
|
+
? descriptor.commands({ editor, bridge: toolBridge })
|
|
1399
|
+
: null;
|
|
1400
|
+
},
|
|
1401
|
+
});
|
|
1402
|
+
return {
|
|
1403
|
+
editor,
|
|
1404
|
+
...methods,
|
|
1370
1405
|
getHTML: () => {
|
|
1371
1406
|
applySourceRef.current();
|
|
1372
1407
|
return editor ? serialize(editor) : knownRef.current;
|
|
@@ -1400,7 +1435,12 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1400
1435
|
rewriteSource(editor);
|
|
1401
1436
|
return true;
|
|
1402
1437
|
},
|
|
1403
|
-
openSearch: (options) =>
|
|
1438
|
+
openSearch: (options) => {
|
|
1439
|
+
devWarn(
|
|
1440
|
+
'RichTextEditor: handle.openSearch() is deprecated; use handle.tool("search")?.open() instead'
|
|
1441
|
+
);
|
|
1442
|
+
methods.tool("search")?.open(options);
|
|
1443
|
+
},
|
|
1404
1444
|
clear: () => {
|
|
1405
1445
|
if (!editor) return;
|
|
1406
1446
|
throttle.cancel();
|
|
@@ -1413,11 +1453,20 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1413
1453
|
emit("");
|
|
1414
1454
|
rewriteSource(editor);
|
|
1415
1455
|
},
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1456
|
+
};
|
|
1457
|
+
}, [editor, editable, throttle]);
|
|
1458
|
+
/*
|
|
1459
|
+
Хуки `useRichTextEditorState` и `useRichTextToolState` не видят смену
|
|
1460
|
+
движка у `ref` сами — сообщаем. Тем же сообщением идут сигналы кита для
|
|
1461
|
+
Состояния Инструмента: набор Инструментов и открытость панели Поиска.
|
|
1462
|
+
*/
|
|
1463
|
+
const searchOpen = searchPanel !== null;
|
|
1464
|
+
useEffect(
|
|
1465
|
+
() => publishToolSignals(ref, { tools: enabledTools, searchOpen }),
|
|
1466
|
+
[ref, editor, enabledTools, searchOpen]
|
|
1418
1467
|
);
|
|
1419
|
-
//
|
|
1420
|
-
useEffect(() =>
|
|
1468
|
+
// Размонтирование (и смена `ref`) забирает сигналы: хук экрана отдаёт `null`.
|
|
1469
|
+
useEffect(() => () => clearToolSignals(ref), [ref]);
|
|
1421
1470
|
|
|
1422
1471
|
/*
|
|
1423
1472
|
Готовность (спека, словарь): один раз за монтаж, когда движок создан.
|
|
@@ -1588,15 +1637,21 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|
|
1588
1637
|
<Suspense fallback={null}>
|
|
1589
1638
|
<TocCore
|
|
1590
1639
|
target={tocTarget}
|
|
1640
|
+
offsetVar={tocOptions.offsetVar}
|
|
1641
|
+
levels={tocOptions.levels}
|
|
1642
|
+
listWidth={tocOptions.listWidth}
|
|
1591
1643
|
stickyTop={tocStickyTop}
|
|
1592
1644
|
focusHeading={tocFocusHeading}
|
|
1593
1645
|
alignToFirstHeading
|
|
1646
|
+
onCurrentChange={(tocAt) => publishToolSignals(ref, { tocAt })}
|
|
1647
|
+
editor={editor}
|
|
1648
|
+
jumpRef={tocJumpRef}
|
|
1594
1649
|
data-toc-gutter=""
|
|
1595
1650
|
className="sticky mr-3 shrink-0"
|
|
1596
1651
|
style={{
|
|
1597
1652
|
top: scrolls
|
|
1598
1653
|
? "0.5rem"
|
|
1599
|
-
: `calc(var(
|
|
1654
|
+
: `calc(var(${tocOffsetVar}, 0px) + ${toolbarHeight}px + 0.5rem)`,
|
|
1600
1655
|
}}
|
|
1601
1656
|
/>
|
|
1602
1657
|
</Suspense>
|
|
@@ -31,7 +31,8 @@ export interface RichTextViewProps extends Omit<
|
|
|
31
31
|
surface?: RichTextSurface;
|
|
32
32
|
/**
|
|
33
33
|
* Свои Инструменты Документа — тот же массив, что у `RichTextEditor` и
|
|
34
|
-
* `sanitizeRichText
|
|
34
|
+
* `sanitizeRichText`; Настроенный Инструмент (`tool()`) в нём допустим и
|
|
35
|
+
* Просмотром игнорируется. Просмотр читает из него только Острова своих Блоков
|
|
35
36
|
* (`blocks[имя].island`): Блок, у которого он есть и который есть в
|
|
36
37
|
* Документе, оживает клиентским компонентом, а серверный HTML Блока виден до
|
|
37
38
|
* загрузки и без JS. Остальное в описании Просмотру не нужно. Без `tools`, и
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Node as PmNode, NodeType, ResolvedPos } from "@tiptap/pm/model";
|
|
4
4
|
import { TextSelection, type Transaction } from "@tiptap/pm/state";
|
|
5
|
+
import { WRAPPER_NODE } from "./nodeNames.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Общее у контейнерных Блоков — Раскладки (`layout.ts`, ADR-0056) и Обёртки
|
|
@@ -13,12 +14,13 @@ import { TextSelection, type Transaction } from "@tiptap/pm/state";
|
|
|
13
14
|
* арифметика позиций не разъезжались молча.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
/*
|
|
18
|
+
Имя узла Обёртки живёт в `nodeNames.ts`, без Tiptap: его читает и Оглавление.
|
|
19
|
+
Не в `wrapper.ts`: оно же входит в общий список ниже, а `wrapper.ts`
|
|
20
|
+
импортирует этот модуль — определение там дало бы цикл импортов. `wrapper.ts`
|
|
21
|
+
его реэкспортирует.
|
|
22
|
+
*/
|
|
23
|
+
export { WRAPPER_NODE };
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Узлы, внутри которых контейнерного Блока нет ни одного: Обёртка, цитата,
|
|
@@ -3,6 +3,8 @@ import type { Mark } from "@tiptap/pm/model";
|
|
|
3
3
|
import type { EditorState } from "@tiptap/pm/state";
|
|
4
4
|
import type { ComponentType, ReactNode } from "react";
|
|
5
5
|
import type { IconProps } from "../../icons.js";
|
|
6
|
+
import type { ToolDefinition } from "./toolDefinition.js";
|
|
7
|
+
import type { ConfiguredTool } from "./toolOptions.js";
|
|
6
8
|
import type { Tool as ToolName } from "./tools.js";
|
|
7
9
|
|
|
8
10
|
/*
|
|
@@ -428,8 +430,12 @@ export type CustomToolButton = CustomToolButtonFields &
|
|
|
428
430
|
}
|
|
429
431
|
);
|
|
430
432
|
|
|
431
|
-
/**
|
|
432
|
-
|
|
433
|
+
/**
|
|
434
|
+
* Элемент `tools`: имя Инструмента кита, Настроенный Инструмент (`tool()`),
|
|
435
|
+
* определение своего Инструмента (`defineTool()`) или описание своего
|
|
436
|
+
* Инструмента обычным объектом.
|
|
437
|
+
*/
|
|
438
|
+
export type ToolEntry = ToolName | ConfiguredTool<string> | ToolDefinition | CustomTool;
|
|
433
439
|
|
|
434
440
|
/**
|
|
435
441
|
* Годится ли значение атрибута по его виду (`CustomAttribute`). Живёт в
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
type ToolLabel,
|
|
35
35
|
type ToolList,
|
|
36
36
|
} from "./toolDescriptor.js";
|
|
37
|
+
import { resolveDefinition, type ToolDefinition } from "./toolDefinition.js";
|
|
37
38
|
import { markdownFallback } from "./customMarkdown.js";
|
|
38
39
|
import { CUSTOM_ATTRIBUTE_NAME, CUSTOM_NAME, customElement } from "./schema.js";
|
|
39
40
|
import { loaded } from "./toolLoader.js";
|
|
@@ -237,6 +238,22 @@ export function describeCustomTool(tool: CustomTool): ToolDescriptor {
|
|
|
237
238
|
};
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Внутреннее описание Инструмента из определения (`defineTool`): тело из
|
|
243
|
+
* `create` и имя определения собираются в обычный `CustomTool` и идут тем же
|
|
244
|
+
* путём, а Опции этого Редактора — умолчания, слитые с `tool()`, — достаются
|
|
245
|
+
* `create`, `state` и `commands`.
|
|
246
|
+
*/
|
|
247
|
+
export function describeDefinition(definition: ToolDefinition, given: object = {}): ToolDescriptor {
|
|
248
|
+
const { options, tool } = resolveDefinition(definition, given);
|
|
249
|
+
return {
|
|
250
|
+
...describeCustomTool(tool),
|
|
251
|
+
options,
|
|
252
|
+
state: definition.state && ((editor) => definition.state!({ editor, options })),
|
|
253
|
+
commands: definition.commands && (({ editor }) => definition.commands!({ editor, options })),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
240
257
|
/**
|
|
241
258
|
* Набор движка, который `RichTextEditor` собирает при ошибке набора: движок
|
|
242
259
|
* нужен хуку `useEditor` в любом случае, но наружу он не выходит.
|
|
@@ -311,7 +328,7 @@ export interface CustomToolsError {
|
|
|
311
328
|
}
|
|
312
329
|
|
|
313
330
|
/**
|
|
314
|
-
* Ошибка сборки набора
|
|
331
|
+
* Ошибка сборки набора Инструментов или `null` (ADR-0058, п. 1.5, 2.4).
|
|
315
332
|
*
|
|
316
333
|
* Имя без Префикса потребителя, совпадение имён, Node или Mark Части для
|
|
317
334
|
* движка без правил Схемы (и правило без них), атрибут, которого нет в
|
|
@@ -325,6 +342,8 @@ export interface CustomToolsError {
|
|
|
325
342
|
* Исключение движка — отдельно, в `cause`: текст — ключ «один раз» у `devError`.
|
|
326
343
|
*/
|
|
327
344
|
export function customToolsError(tools: ToolList, extensions: Extensions): CustomToolsError | null {
|
|
345
|
+
// Ошибка, найденная при развороте `tools` (две записи `tool()` с одним именем), идёт тем же путём.
|
|
346
|
+
if (tools.error) return { message: tools.error };
|
|
328
347
|
const custom = tools.descriptors.flatMap((descriptor) => descriptor.custom ?? []);
|
|
329
348
|
if (custom.length === 0) return null;
|
|
330
349
|
// Имена Инструментов и имена Блоков и Меток — разные пространства: Инструмент
|
|
@@ -268,7 +268,8 @@ export function createEditorExtensions(options: EditorExtensionOptions): Extensi
|
|
|
268
268
|
(`codeBlockTool.ts`): его расширение здесь, после `ToolbarFocus`, и
|
|
269
269
|
опрашивается раньше.
|
|
270
270
|
*/
|
|
271
|
-
for (const descriptor of described)
|
|
271
|
+
for (const descriptor of described)
|
|
272
|
+
extensions.push(...(descriptor.engine?.(options, descriptor.options) ?? []));
|
|
272
273
|
// Сочетания кнопок описаний вешает кит: поле записи — их единственный источник.
|
|
273
274
|
const buttons = described.flatMap((descriptor) => descriptor.buttons ?? []);
|
|
274
275
|
// Поповер пункта вставки и Действия открывает та же команда, что у кнопки.
|
|
@@ -6,8 +6,15 @@ import type { Fragment } from "@tiptap/pm/model";
|
|
|
6
6
|
import type { Editor } from "@tiptap/react";
|
|
7
7
|
import { textBetween } from "./atomText.js";
|
|
8
8
|
import { isRichTextDocument, type RichTextDocument } from "./jsonDocument.js";
|
|
9
|
+
import type { ToolCommandsOf } from "./toolCommands.js";
|
|
10
|
+
import type { RichTextTools } from "./toolOptions.js";
|
|
9
11
|
|
|
10
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
clearToolSignals,
|
|
14
|
+
notifyHandleEditor,
|
|
15
|
+
publishToolSignals,
|
|
16
|
+
watchHandleEditor,
|
|
17
|
+
} from "./handleWatchers.js";
|
|
11
18
|
|
|
12
19
|
/**
|
|
13
20
|
* Плоские методы Хэндла (ADR-0058, раздел 8): то, что экран делает с Редактором
|
|
@@ -81,6 +88,19 @@ export interface RichTextEditorHandleMethods {
|
|
|
81
88
|
canUndo(): boolean;
|
|
82
89
|
/** Есть ли что повторить. Без Инструмента `history` и до монтажа — `false`. */
|
|
83
90
|
canRedo(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Команды Инструмента: `handle.tool("search")?.open({ query: "TODO" })`,
|
|
93
|
+
* `handle.tool("toc")?.jumpTo(2)`. Тип Команд выводится из имени по карте
|
|
94
|
+
* `RichTextTools`. `null`, если Инструмента нет в наборе `tools`, у него нет
|
|
95
|
+
* Команд или движок ещё не создан, — поэтому Команды выключенного Инструмента
|
|
96
|
+
* недоступны и в типах (`?.`), и во время работы.
|
|
97
|
+
*
|
|
98
|
+
* Каждая Команда возвращает `boolean`: выполнена или нет (HTML-режим, нет
|
|
99
|
+
* совпадений, нет заголовка, поле нельзя править) — как `insertHTML`. Имя вне
|
|
100
|
+
* карты (свой Инструмент без типизации) даёт `unknown`.
|
|
101
|
+
*/
|
|
102
|
+
tool<Name extends keyof RichTextTools>(name: Name): ToolCommandsOf<Name> | null;
|
|
103
|
+
tool(name: string): unknown;
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
/** Что Хэндлу нужно от Редактора: движок и то, что знает только он. */
|
|
@@ -109,6 +129,8 @@ export interface HandleHost {
|
|
|
109
129
|
rewriteSource: (editor: Editor) => void;
|
|
110
130
|
/** Пуст ли Документ по правилу кита, с учётом неприменённой правки поля HTML. */
|
|
111
131
|
fieldEmpty: (editor: Editor) => boolean;
|
|
132
|
+
/** Команды Инструмента набора; `null` — Инструмента или Команд нет (`ToolDescriptor.commands`). */
|
|
133
|
+
commands: (name: string) => object | null;
|
|
112
134
|
}
|
|
113
135
|
|
|
114
136
|
/** Тег целиком; комментарий и содержимое `script`/`style`/`template` разбор Схемой не видит. */
|
|
@@ -286,5 +308,7 @@ export function createHandleMethods(host: HandleHost): RichTextEditorHandleMetho
|
|
|
286
308
|
redo: () => history("redo"),
|
|
287
309
|
canUndo: () => available("undo"),
|
|
288
310
|
canRedo: () => available("redo"),
|
|
311
|
+
// Типы Команд выводятся из карты, а реализация знает только имя.
|
|
312
|
+
tool: host.commands as RichTextEditorHandleMethods["tool"],
|
|
289
313
|
};
|
|
290
314
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RefObject } from "react";
|
|
2
|
+
import type { ToolList } from "./toolDescriptor.js";
|
|
2
3
|
|
|
3
4
|
/*
|
|
4
5
|
Хук-селектор не знает, когда у `ref` появится движок: Редактор создаёт его
|
|
@@ -25,3 +26,47 @@ export function notifyHandleEditor(ref: unknown): void {
|
|
|
25
26
|
if (typeof ref !== "object" || ref === null) return;
|
|
26
27
|
watchers.get(ref)?.forEach((listener) => listener());
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Сигналы кита для `useRichTextToolState`: то, что меняется не транзакцией
|
|
32
|
+
* движка, а интерфейсом Инструмента, и поэтому в движке его не прочитать.
|
|
33
|
+
* Редактор кладёт их по `ref`, как и сообщение о движке, и хук получает
|
|
34
|
+
* уведомление той же подпиской.
|
|
35
|
+
*/
|
|
36
|
+
export interface ToolSignals {
|
|
37
|
+
/** Набор Инструментов этого Редактора: есть ли Инструмент и с какими Опциями. */
|
|
38
|
+
tools?: ToolList;
|
|
39
|
+
/** Панель Поиска открыта (состояние React в `RichTextEditor`). */
|
|
40
|
+
searchOpen?: boolean;
|
|
41
|
+
/** Индекс текущего раздела Оглавления (вычисляет `TocCore` от прокрутки). */
|
|
42
|
+
tocAt?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const signals = new WeakMap<object, ToolSignals>();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Редактор сообщает сигналы Инструментов: слитые с прежними, подписчикам идёт то
|
|
49
|
+
* же уведомление, что и о смене движка.
|
|
50
|
+
*/
|
|
51
|
+
export function publishToolSignals(ref: unknown, next: ToolSignals): void {
|
|
52
|
+
if (typeof ref !== "object" || ref === null) return;
|
|
53
|
+
signals.set(ref, { ...signals.get(ref), ...next });
|
|
54
|
+
notifyHandleEditor(ref);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Редактор размонтирован: его сигналы уходят вместе с ним. Иначе они пережили бы
|
|
59
|
+
* его в таблице по `ref`, и хук отдавал бы снимок уже не существующего Редактора
|
|
60
|
+
* (а новый Редактор под тем же `ref` начинал бы с чужих сигналов). Подписчики
|
|
61
|
+
* получают уведомление: компонент, который из-за размонтирования не
|
|
62
|
+
* перерисовывается сам, узнаёт о нём только так.
|
|
63
|
+
*/
|
|
64
|
+
export function clearToolSignals(ref: unknown): void {
|
|
65
|
+
if (typeof ref !== "object" || ref === null || !signals.delete(ref)) return;
|
|
66
|
+
notifyHandleEditor(ref);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Сигналы Инструментов Редактора, привязанного к `ref`. */
|
|
70
|
+
export function toolSignals(ref: object): ToolSignals {
|
|
71
|
+
return signals.get(ref) ?? {};
|
|
72
|
+
}
|
|
@@ -3,6 +3,7 @@ import { EDITOR_LAYOUTS } from "../features/layout/columnLayouts.js";
|
|
|
3
3
|
import { CUSTOM_COLOR_STYLE_PATTERN } from "../features/color/customColor.js";
|
|
4
4
|
import { devWarn } from "../../lib/dev-warn.js";
|
|
5
5
|
import type { CustomAttribute, PrefixedName, ToolEntry } from "./customTool.js";
|
|
6
|
+
import { customToolOf } from "./toolDefinition.js";
|
|
6
7
|
import {
|
|
7
8
|
EDITOR_ALIGNMENTS,
|
|
8
9
|
EDITOR_MARKER_NAMES,
|
|
@@ -375,8 +376,10 @@ function customAttributes(
|
|
|
375
376
|
export function richTextSchema(tools: readonly ToolEntry[] = []): RichTextSchema {
|
|
376
377
|
const customBlocks: Record<PrefixedName, RichTextCustomBlockRules> = {};
|
|
377
378
|
const customMarks: Record<PrefixedName, RichTextCustomMarkRules> = {};
|
|
378
|
-
for (const
|
|
379
|
-
|
|
379
|
+
for (const entry of tools) {
|
|
380
|
+
// Строка — имя кита, Настроенный встроенный — его опции: Схемы у них нет.
|
|
381
|
+
const tool = customToolOf(entry);
|
|
382
|
+
if (!tool) continue;
|
|
380
383
|
for (const [name, block] of Object.entries(tool.blocks ?? {})) {
|
|
381
384
|
if (!CUSTOM_NAME.test(name)) continue;
|
|
382
385
|
const kind = block.kind === "inline" ? "inline" : "block";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { RichTextTools, SearchToolOptions } from "./toolOptions.js";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Команды Инструмента: типы того, что экран вызывает через `handle.tool(name)`.
|
|
5
|
+
Модуль серверно-безопасен, как `toolOptions.ts`: одни типы, без Tiptap и React.
|
|
6
|
+
Реализации встроенных лежат рядом с их описаниями (`searchTool.ts`, `tocTool.ts`).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Команды Инструмента `search`. Каждая возвращает `boolean`: выполнена или нет
|
|
11
|
+
* (HTML-режим, нет совпадений, поле нельзя править). Команды ведут себя как
|
|
12
|
+
* панель «Поиск и замена»: тот же запрос, те же флажки, та же подсветка.
|
|
13
|
+
* Запрос и флажки живут в движке, поэтому `setQuery`, `next` и `replace` работают
|
|
14
|
+
* и без открытой панели — на них можно собрать свой интерфейс поиска.
|
|
15
|
+
*/
|
|
16
|
+
export interface SearchToolCommands {
|
|
17
|
+
/**
|
|
18
|
+
* Открыть панель — то же, что кнопка «Поиск» и `Mod+F`. `query` сразу ставит
|
|
19
|
+
* запрос в поле и в подсветку документа; без него запросом становится текст,
|
|
20
|
+
* выделенный в одном Блоке. `replace` ставит фокус в поле замены. Флажки на
|
|
21
|
+
* открытой панели не меняются, на закрытой панель стартует с Опций Инструмента.
|
|
22
|
+
* `false` — в HTML-режиме (Документа на экране нет).
|
|
23
|
+
*/
|
|
24
|
+
open(options?: { replace?: boolean; query?: string }): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Закрыть панель и убрать подсветку. Фокус и каретка остаются где были:
|
|
27
|
+
* в отличие от Escape в панели, команда в Документ их не переводит. Панель,
|
|
28
|
+
* уже закрытая, — не ошибка: подсветка всё равно убирается.
|
|
29
|
+
*/
|
|
30
|
+
close(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Задать запрос. Не названные в `flags` флажки (и флажки со значением
|
|
33
|
+
* `undefined`) берутся из открытой панели, а если её нет — из Опций
|
|
34
|
+
* Инструмента. Открытая панель показывает новый запрос
|
|
35
|
+
* и флажки. Непустой запрос прокручивает к первому совпадению после каретки,
|
|
36
|
+
* как `next` (прокрутка приходит чуть позже). `true` — запрос применён, даже
|
|
37
|
+
* если совпадений нет (их число — Состояние Инструмента); `false` — в
|
|
38
|
+
* HTML-режиме.
|
|
39
|
+
*/
|
|
40
|
+
setQuery(query: string, flags?: Partial<SearchToolOptions>): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Следующее совпадение по кругу, выделяет его и прокручивает к нему ближайшую
|
|
43
|
+
* прокручиваемую область поля или окно — как панель, даже когда фокус вне
|
|
44
|
+
* Документа; фокус при этом не забирается. Прокрутка приходит чуть позже
|
|
45
|
+
* вызова (микрозадачей и кадром): её код грузится при первом вызове. `false` —
|
|
46
|
+
* совпадений нет.
|
|
47
|
+
*/
|
|
48
|
+
next(): boolean;
|
|
49
|
+
/** Предыдущее совпадение по кругу; выделяет и прокручивает, как `next`. `false` — совпадений нет. */
|
|
50
|
+
previous(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Заменить текущее совпадение: одна правка, одна отмена. Текущим становится
|
|
53
|
+
* следующее. `false` — совпадений нет или поле нельзя править.
|
|
54
|
+
*/
|
|
55
|
+
replace(text: string): boolean;
|
|
56
|
+
/** Заменить все совпадения: одна правка, одна отмена. Условия — как у `replace`. */
|
|
57
|
+
replaceAll(text: string): boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Команды Инструмента `toc`. */
|
|
61
|
+
export interface TocToolCommands {
|
|
62
|
+
/**
|
|
63
|
+
* Перейти к заголовку списка Оглавления — как клик по пункту. Индекс считается
|
|
64
|
+
* в том же списке, что и пункты: с учётом `levels`, с нуля, пустые заголовки
|
|
65
|
+
* пропускаются. В правке каретка встаёт в конец заголовка, в `readOnly` фокус
|
|
66
|
+
* получает сам заголовок; прокрутка ставит его под липкую шапку (`offsetVar`) и
|
|
67
|
+
* Панель, подсветка заголовка гаснет.
|
|
68
|
+
*
|
|
69
|
+
* `false` — нет заголовка с таким индексом, либо Оглавление ещё не готово: его
|
|
70
|
+
* UI грузится отдельным куском после создания движка, и в HTML-режиме его нет.
|
|
71
|
+
* Заголовков меньше двух — не причина отказа: полоса не рисуется, но список
|
|
72
|
+
* есть, и переход работает.
|
|
73
|
+
*/
|
|
74
|
+
jumpTo(index: number): boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Команды Инструмента по имени из карты `RichTextTools`. Имя вне карты — `unknown`:
|
|
79
|
+
* типизация добровольна, Инструмент работает и без неё.
|
|
80
|
+
*/
|
|
81
|
+
export type ToolCommandsOf<Name extends string> = Name extends keyof RichTextTools
|
|
82
|
+
? RichTextTools[Name] extends { commands: infer Commands }
|
|
83
|
+
? Commands
|
|
84
|
+
: unknown
|
|
85
|
+
: unknown;
|