@olddevs/ui 4.3.0 → 4.4.0

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/editor/RichTextEditor.d.ts.map +1 -1
  3. package/dist/editor/RichTextEditor.js +38 -4
  4. package/dist/editor/RichTextEditor.js.map +1 -1
  5. package/dist/editor/core/handle.d.ts +1 -5
  6. package/dist/editor/core/handle.d.ts.map +1 -1
  7. package/dist/editor/core/handle.js +1 -13
  8. package/dist/editor/core/handle.js.map +1 -1
  9. package/dist/editor/core/handleWatchers.d.ts +6 -0
  10. package/dist/editor/core/handleWatchers.d.ts.map +1 -0
  11. package/dist/editor/core/handleWatchers.js +14 -0
  12. package/dist/editor/core/handleWatchers.js.map +1 -0
  13. package/dist/editor/core/tools.d.ts +7 -2
  14. package/dist/editor/core/tools.d.ts.map +1 -1
  15. package/dist/editor/core/tools.js +2 -1
  16. package/dist/editor/core/tools.js.map +1 -1
  17. package/dist/editor/features/toc/RichTextToc.d.ts +63 -0
  18. package/dist/editor/features/toc/RichTextToc.d.ts.map +1 -0
  19. package/dist/editor/features/toc/RichTextToc.js +372 -0
  20. package/dist/editor/features/toc/RichTextToc.js.map +1 -0
  21. package/dist/editor/features/toc/headings.d.ts +31 -0
  22. package/dist/editor/features/toc/headings.d.ts.map +1 -0
  23. package/dist/editor/features/toc/headings.js +25 -0
  24. package/dist/editor/features/toc/headings.js.map +1 -0
  25. package/dist/editor/features/toc/stickyOffset.d.ts +3 -0
  26. package/dist/editor/features/toc/stickyOffset.d.ts.map +1 -0
  27. package/dist/editor/features/toc/stickyOffset.js +14 -0
  28. package/dist/editor/features/toc/stickyOffset.js.map +1 -0
  29. package/dist/editor/index.d.ts +2 -0
  30. package/dist/editor/index.d.ts.map +1 -1
  31. package/dist/editor/index.js +1 -0
  32. package/dist/editor/index.js.map +1 -1
  33. package/dist/i18n/defaults.d.ts.map +1 -1
  34. package/dist/i18n/defaults.js +1 -0
  35. package/dist/i18n/defaults.js.map +1 -1
  36. package/dist/i18n/messages.d.ts +5 -0
  37. package/dist/i18n/messages.d.ts.map +1 -1
  38. package/dist/locales/ru.d.ts.map +1 -1
  39. package/dist/locales/ru.js +1 -0
  40. package/dist/locales/ru.js.map +1 -1
  41. package/docs/api/messages.en.md +743 -0
  42. package/docs/api/messages.md +1 -0
  43. package/docs/api/utilities.en.md +233 -0
  44. package/docs/components/content/rich-text-toc.en.md +130 -0
  45. package/docs/components/content/rich-text-toc.md +129 -0
  46. package/docs/components/index.en.md +2 -1
  47. package/docs/components/index.md +1 -0
  48. package/docs/components/inputs/rich-text-editor.en.md +52 -34
  49. package/docs/components/inputs/rich-text-editor.md +20 -2
  50. package/docs/conventions.en.md +318 -0
  51. package/docs/getting-started.en.md +4 -4
  52. package/docs/getting-started.md +3 -3
  53. package/docs/glossary.en.md +2 -1
  54. package/docs/glossary.md +1 -0
  55. package/docs/index.en.md +53 -0
  56. package/llms.txt +1 -0
  57. package/package.json +1 -1
  58. package/src/editor/RichTextEditor.tsx +107 -3
  59. package/src/editor/core/handle.ts +2 -23
  60. package/src/editor/core/handleWatchers.ts +27 -0
  61. package/src/editor/core/tools.ts +9 -2
  62. package/src/editor/features/toc/RichTextToc.tsx +712 -0
  63. package/src/editor/features/toc/headings.ts +70 -0
  64. package/src/editor/features/toc/stickyOffset.ts +21 -0
  65. package/src/editor/index.ts +2 -0
  66. package/src/i18n/defaults.ts +1 -0
  67. package/src/i18n/messages.ts +5 -0
  68. package/src/locales/ru.ts +1 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Заголовки Документа для Оглавления (`RichTextToc`, ADR-0066).
3
+ *
4
+ * Читаются из DOM цели, а не из строки `html`: один путь для Просмотра,
5
+ * Редактора и JSON-конверта, и переходу всё равно нужен живой узел.
6
+ *
7
+ * Модуль без React и без движка: только обход DOM.
8
+ */
9
+
10
+ /** Заголовок в Оглавлении. */
11
+ export interface TocHeading {
12
+ /** Узел заголовка в Документе: к нему ведёт переход. */
13
+ element: HTMLElement;
14
+ /** Текст пункта — текст заголовка без разметки. */
15
+ text: string;
16
+ /** Уровень по тегу: 1 для `h1` … 4 для `h4`. */
17
+ level: number;
18
+ /**
19
+ * Глубина относительно самого крупного заголовка Документа: у него 0. Отступ
20
+ * пункта и длина штриха считаются от неё, а не от уровня, — иначе Документ,
21
+ * где старший заголовок H2, начинался бы с пустой ступени.
22
+ */
23
+ depth: number;
24
+ }
25
+
26
+ const HEADING = "h1, h2, h3, h4";
27
+
28
+ /**
29
+ * Обёртка — единственный контейнер, чьи заголовки идут в Оглавление.
30
+ *
31
+ * Колонки Раскладки — нет: два заголовка на одной высоте в соседних колонках
32
+ * путают подсветку текущего раздела, и порядок «левая колонка, затем правая»
33
+ * читателю ничего не говорит. Острова и свои Блоки — тоже нет: их содержимое —
34
+ * часть Блока, а не структура Документа.
35
+ */
36
+ const WRAPPER = 'div[data-block="wrapper"]';
37
+
38
+ /**
39
+ * Корень Документа внутри цели. У Просмотра это сам контейнер; у Редактора
40
+ * Документ лежит глубже, в узле движка — Панель и строка статуса снаружи него.
41
+ */
42
+ export function documentRoot(target: HTMLElement): HTMLElement {
43
+ return target.querySelector<HTMLElement>(".ProseMirror") ?? target;
44
+ }
45
+
46
+ /** Заголовок лежит на верхнем уровне Документа или только в Обёртках. */
47
+ function isStructural(heading: Element, root: HTMLElement): boolean {
48
+ for (let node = heading.parentElement; node && node !== root; node = node.parentElement) {
49
+ if (!node.matches(WRAPPER)) return false;
50
+ }
51
+ return true;
52
+ }
53
+
54
+ /** Заголовки Документа в порядке чтения. */
55
+ export function collectHeadings(target: HTMLElement): TocHeading[] {
56
+ const root = documentRoot(target);
57
+ // Пустой заголовок (автор только начал его в Редакторе) — не пункт: у
58
+ // кнопки без текста не было бы имени, а у полосы — лишний штрих.
59
+ const found = [...root.querySelectorAll<HTMLElement>(HEADING)].filter(
60
+ (heading) => isStructural(heading, root) && heading.textContent?.trim()
61
+ );
62
+ const levels = found.map((heading) => Number(heading.tagName[1]));
63
+ const top = Math.min(...levels);
64
+ return found.map((element, index) => ({
65
+ element,
66
+ text: element.textContent?.trim() ?? "",
67
+ level: levels[index]!,
68
+ depth: levels[index]! - top,
69
+ }));
70
+ }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import { toPixels } from "../../../hooks/useStickyOffset.js";
4
+
5
+ /**
6
+ * Последний замер шапки. Подсветка читает отступ на каждом кадре прокрутки, а
7
+ * `toPixels` для значения не в пикселях (`4rem`) меряет зонд — две раскладки.
8
+ * Замер повторяется только при смене сырого значения, как в `useStickyOffset`.
9
+ */
10
+ let measured: { raw: string; px: number | null } = { raw: "", px: null };
11
+
12
+ /** Высота липкой шапки в пикселях; переменной нет — `null`. */
13
+ export function stickyOffset(offsetVar: string): number | null {
14
+ const raw = getComputedStyle(document.documentElement).getPropertyValue(offsetVar).trim();
15
+ if (!raw) return null;
16
+ if (raw !== measured.raw) {
17
+ const px = parseFloat(toPixels(raw, "0px"));
18
+ measured = { raw, px: Number.isFinite(px) && px > 0 ? px : null };
19
+ }
20
+ return measured.px;
21
+ }
@@ -21,6 +21,8 @@ export type { RichTextDocument } from "./core/jsonDocument.js";
21
21
  export { useRichTextEditorState } from "./hooks/useRichTextEditorState.js";
22
22
  export { RichTextView } from "./RichTextView.js";
23
23
  export type { RichTextViewProps } from "./RichTextView.js";
24
+ export { RichTextToc } from "./features/toc/RichTextToc.js";
25
+ export type { RichTextTocProps } from "./features/toc/RichTextToc.js";
24
26
  export type { FieldNotice, FieldNoticeTone } from "./core/fieldNotice.js";
25
27
  export type { RichTextSurface } from "./styles/surface.js";
26
28
  export { richTextSanitizeConfig, richTextSchema, sanitizeRichText } from "./core/schema.js";
@@ -654,6 +654,7 @@ export const DEFAULT_MESSAGES: UiMessages = {
654
654
  blockMoveDown: "Move down",
655
655
  blockDuplicate: "Duplicate",
656
656
  blockDelete: "Delete",
657
+ toc: "Table of contents",
657
658
  },
658
659
  };
659
660
 
@@ -1080,6 +1080,11 @@ export interface UiMessages {
1080
1080
  blockMoveDown: string;
1081
1081
  blockDuplicate: string;
1082
1082
  blockDelete: string;
1083
+ /**
1084
+ * Оглавление Документа (`RichTextToc`): имя кнопки-полосы и подпись
1085
+ * `nav` со списком заголовков — одно слово на оба, это одна вещь.
1086
+ */
1087
+ toc: string;
1083
1088
  };
1084
1089
  }
1085
1090
 
package/src/locales/ru.ts CHANGED
@@ -693,6 +693,7 @@ export const messages: UiMessages = {
693
693
  blockMoveDown: "Вниз",
694
694
  blockDuplicate: "Дублировать",
695
695
  blockDelete: "Удалить",
696
+ toc: "Оглавление",
696
697
  },
697
698
  };
698
699