@dxos/react-ui-editor 0.3.11-main.69484a6 → 0.3.11-main.69d4f1c

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 (40) hide show
  1. package/dist/lib/browser/index.mjs +1183 -168
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  5. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/Toolbar/Toolbar.d.ts +8 -4
  7. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +5 -3
  9. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  10. package/dist/types/src/extensions/comments.d.ts +5 -1
  11. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  12. package/dist/types/src/extensions/markdown/formatting.d.ts +54 -10
  13. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  14. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  15. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  16. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  17. package/dist/types/src/hooks/useTextModel.d.ts +12 -0
  18. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  19. package/dist/types/src/index.d.ts +1 -1
  20. package/dist/types/src/index.d.ts.map +1 -1
  21. package/dist/types/src/themes/default.d.ts.map +1 -1
  22. package/package.json +24 -24
  23. package/src/components/TextEditor/MarkdownEditor.stories.tsx +2 -2
  24. package/src/components/TextEditor/TextEditor.tsx +3 -4
  25. package/src/components/TextEditor/hooks.stories.tsx +6 -1
  26. package/src/components/Toolbar/Toolbar.stories.tsx +15 -3
  27. package/src/components/Toolbar/Toolbar.tsx +108 -29
  28. package/src/extensions/awareness.ts +4 -1
  29. package/src/extensions/blast.ts +4 -1
  30. package/src/extensions/comments.ts +14 -4
  31. package/src/extensions/markdown/formatting.test.ts +481 -0
  32. package/src/extensions/markdown/formatting.ts +1080 -70
  33. package/src/extensions/markdown/link.ts +8 -2
  34. package/src/hooks/useActionHandler.ts +47 -16
  35. package/src/hooks/useTextModel.ts +17 -1
  36. package/src/index.ts +1 -1
  37. package/src/themes/default.ts +7 -4
  38. package/dist/types/src/components/TextEditor/comments.stories.d.ts +0 -26
  39. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +0 -1
  40. package/src/components/TextEditor/comments.stories.tsx +0 -357
@@ -1,19 +1,20 @@
1
1
  // packages/ui/react-ui-editor/src/index.ts
2
2
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
3
3
  import { Doc, YText, YXmlFragment } from "@dxos/text-model";
4
+ import { keymap as keymap7 } from "@codemirror/view";
4
5
 
5
6
  // packages/ui/react-ui-editor/src/components/TextEditor/index.ts
6
7
  import { tags as tags2 } from "@lezer/highlight";
7
8
 
8
9
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
9
10
  import { EditorState as EditorState2 } from "@codemirror/state";
10
- import { EditorView as EditorView13 } from "@codemirror/view";
11
+ import { EditorView as EditorView14 } from "@codemirror/view";
11
12
  import { vim } from "@replit/codemirror-vim";
12
13
  import defaultsDeep2 from "lodash.defaultsdeep";
13
- import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState } from "react";
14
+ import React, { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef, useState as useState2 } from "react";
14
15
  import { log as log2 } from "@dxos/log";
15
16
  import { useThemeContext } from "@dxos/react-ui";
16
- import { attentionSurface, mx as mx3 } from "@dxos/react-ui-theme";
17
+ import { focusRing, mx as mx3 } from "@dxos/react-ui-theme";
17
18
  import { isNotFalsy as isNotFalsy3 } from "@dxos/util";
18
19
 
19
20
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
@@ -782,7 +783,7 @@ var Blaster = class {
782
783
  initialize() {
783
784
  invariant(!this._canvas && !this._ctx, void 0, {
784
785
  F: __dxlog_file,
785
- L: 138,
786
+ L: 141,
786
787
  S: this,
787
788
  A: [
788
789
  "!this._canvas && !this._ctx",
@@ -819,7 +820,7 @@ var Blaster = class {
819
820
  start() {
820
821
  invariant(this._canvas && this._ctx, void 0, {
821
822
  F: __dxlog_file,
822
- L: 177,
823
+ L: 180,
823
824
  S: this,
824
825
  A: [
825
826
  "this._canvas && this._ctx",
@@ -1073,11 +1074,25 @@ var logChanges = (trs) => {
1073
1074
 
1074
1075
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1075
1076
  var styles2 = EditorView5.baseTheme({
1076
- "& .cm-comment": {
1077
+ "&light .cm-comment, &light .cm-comment-current": {
1078
+ mixBlendMode: "darken"
1079
+ },
1080
+ "&dark .cm-comment, &dark .cm-comment-current": {
1081
+ mixBlendMode: "plus-lighter"
1082
+ },
1083
+ "&light .cm-comment": {
1077
1084
  backgroundColor: getToken("extend.colors.yellow.50")
1078
1085
  },
1079
- "& .cm-comment-current": {
1086
+ "&light .cm-comment-current": {
1080
1087
  backgroundColor: getToken("extend.colors.yellow.100")
1088
+ },
1089
+ "&dark .cm-comment": {
1090
+ color: getToken("extend.colors.yellow.50"),
1091
+ backgroundColor: getToken("extend.colors.yellow.900")
1092
+ },
1093
+ "&dark .cm-comment-current": {
1094
+ color: getToken("extend.colors.yellow.100"),
1095
+ backgroundColor: getToken("extend.colors.yellow.950")
1081
1096
  }
1082
1097
  });
1083
1098
  var commentMark = Decoration2.mark({
@@ -1286,7 +1301,11 @@ var createComment = (view) => {
1286
1301
  to
1287
1302
  });
1288
1303
  if (cursor) {
1289
- const id = options.onCreate?.(cursor, view.coordsAtPos(from));
1304
+ const id = options.onCreate?.({
1305
+ cursor,
1306
+ from,
1307
+ location: view.coordsAtPos(from)
1308
+ });
1290
1309
  if (id) {
1291
1310
  view.dispatch({
1292
1311
  effects: setSelection.of({
@@ -1814,57 +1833,283 @@ var code2 = () => {
1814
1833
  // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1815
1834
  import { snippet } from "@codemirror/autocomplete";
1816
1835
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1817
- import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1818
- import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1819
- var setHeading = (level) => (view) => {
1820
- const { selection: { ranges }, doc } = view.state;
1836
+ import { RangeSetBuilder as RangeSetBuilder2, EditorSelection } from "@codemirror/state";
1837
+ import { Decoration as Decoration4, EditorView as EditorView9, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1838
+ import { useState, useMemo } from "react";
1839
+ var compareFormatting = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockquote === b.blockquote;
1840
+ var Inline;
1841
+ (function(Inline2) {
1842
+ Inline2[Inline2["Strong"] = 0] = "Strong";
1843
+ Inline2[Inline2["Emphasis"] = 1] = "Emphasis";
1844
+ Inline2[Inline2["Strikethrough"] = 2] = "Strikethrough";
1845
+ Inline2[Inline2["Code"] = 3] = "Code";
1846
+ })(Inline || (Inline = {}));
1847
+ var List;
1848
+ (function(List2) {
1849
+ List2[List2["Ordered"] = 0] = "Ordered";
1850
+ List2[List2["Bullet"] = 1] = "Bullet";
1851
+ List2[List2["Task"] = 2] = "Task";
1852
+ })(List || (List = {}));
1853
+ var setHeading = (level) => ({ state, dispatch }) => {
1854
+ const { selection: { ranges }, doc } = state;
1821
1855
  const changes = [];
1856
+ let prevBlock = -1;
1822
1857
  for (const range of ranges) {
1823
- const { number } = doc.lineAt(range.anchor);
1824
- const { from, to } = doc.line(number);
1825
- const line = doc.sliceString(from, to);
1826
- const [_, marks, spaces] = line.match(/(#+)(\s+)/) ?? [];
1827
- const current = marks?.length ?? 0;
1828
- if (level !== current) {
1829
- changes.push({
1830
- from,
1831
- to: from + current + (spaces?.length ?? 0),
1832
- insert: "#".repeat(level) + (level > 0 ? " " : "")
1833
- });
1834
- }
1835
- }
1836
- if (changes.length) {
1837
- view.dispatch({
1838
- changes
1858
+ syntaxTree2(state).iterate({
1859
+ from: range.from,
1860
+ to: range.to,
1861
+ enter: (node) => {
1862
+ if (!Object.hasOwn(Textblocks, node.name) || prevBlock === node.from) {
1863
+ return;
1864
+ }
1865
+ prevBlock = node.from;
1866
+ const blockType = Textblocks[node.name];
1867
+ const isHeading = /heading(\d)/.exec(blockType);
1868
+ const curLevel = isHeading ? +isHeading[1] : node.name === "Paragraph" ? 0 : -1;
1869
+ if (curLevel < 0 || curLevel === level) {
1870
+ return;
1871
+ }
1872
+ if (curLevel === 0) {
1873
+ changes.push({
1874
+ from: node.from,
1875
+ insert: "#".repeat(level) + " "
1876
+ });
1877
+ } else if (node.name === "SetextHeading1" || node.name === "SetextHeading2") {
1878
+ const nextLine = doc.lineAt(node.to);
1879
+ if (level) {
1880
+ changes.push({
1881
+ from: node.from,
1882
+ insert: "#".repeat(level) + " "
1883
+ });
1884
+ }
1885
+ changes.push({
1886
+ from: nextLine.from - 1,
1887
+ to: nextLine.to
1888
+ });
1889
+ } else {
1890
+ if (level === 0) {
1891
+ changes.push({
1892
+ from: node.from,
1893
+ to: Math.min(node.to, node.from + curLevel + 1)
1894
+ });
1895
+ } else if (level < curLevel) {
1896
+ changes.push({
1897
+ from: node.from,
1898
+ to: node.from + (curLevel - level)
1899
+ });
1900
+ } else {
1901
+ changes.push({
1902
+ from: node.from,
1903
+ insert: "#".repeat(level - curLevel)
1904
+ });
1905
+ }
1906
+ }
1907
+ }
1839
1908
  });
1840
1909
  }
1910
+ if (!changes.length) {
1911
+ return false;
1912
+ }
1913
+ dispatch(state.update({
1914
+ changes,
1915
+ userEvent: "format.setHeading",
1916
+ scrollIntoView: true
1917
+ }));
1841
1918
  return true;
1842
1919
  };
1843
- var toggleStyle = (mark2) => (view) => {
1844
- const { ranges } = view.state.selection;
1845
- for (const range of ranges) {
1846
- if (range.from === range.to) {
1847
- return false;
1920
+ var setStyle = (type, enable) => ({ state, dispatch }) => {
1921
+ const marker = inlineMarkerText(type);
1922
+ const changes = state.changeByRange((range) => {
1923
+ if (!enable && range.empty) {
1924
+ const after = state.doc.sliceString(range.head, range.head + 6);
1925
+ const found = after.indexOf(marker);
1926
+ if (found >= 0 && /^[*~`]*$/.test(after.slice(0, found))) {
1927
+ const before = state.doc.sliceString(range.head - 6, range.head);
1928
+ if (before.slice(before.length - found - marker.length, before.length - found) === marker && [
1929
+ ...before.slice(before.length - found)
1930
+ ].reverse().join("") === after.slice(0, found)) {
1931
+ return {
1932
+ changes: [
1933
+ {
1934
+ from: range.head - marker.length - found,
1935
+ to: range.head - found
1936
+ },
1937
+ {
1938
+ from: range.head + found,
1939
+ to: range.head + found + marker.length
1940
+ }
1941
+ ],
1942
+ range: EditorSelection.cursor(range.from - marker.length)
1943
+ };
1944
+ }
1945
+ }
1848
1946
  }
1849
- view.dispatch({
1850
- changes: [
1851
- {
1852
- from: range.from,
1853
- insert: mark2
1854
- },
1855
- {
1856
- from: range.to,
1857
- insert: mark2
1947
+ const changes2 = [];
1948
+ const changesAtEnd = [];
1949
+ let blockStart = -1;
1950
+ let blockEnd = -1;
1951
+ let startCovered = false;
1952
+ let endCovered = false;
1953
+ let { from, to } = range;
1954
+ syntaxTree2(state).iterate({
1955
+ from,
1956
+ to,
1957
+ enter: (node) => {
1958
+ const { name } = node;
1959
+ if (Object.hasOwn(Textblocks, name) && Textblocks[name] !== "codeblock") {
1960
+ blockStart = blockContentStart(node);
1961
+ blockEnd = blockContentEnd(node, state.doc);
1962
+ startCovered = endCovered = false;
1963
+ } else if (name === "Link" || name === "Image" && enable) {
1964
+ if (from < node.from && to > node.from && to <= node.to) {
1965
+ to = node.to;
1966
+ } else if (to > node.to && from >= node.from && from < node.to) {
1967
+ from = node.from;
1968
+ }
1969
+ } else if (IgnoreInline.has(name) && enable) {
1970
+ if (node.from < from && node.to > from) {
1971
+ if (to === from) {
1972
+ to = node.to;
1973
+ }
1974
+ from = node.to;
1975
+ }
1976
+ if (node.from < to && node.to > to) {
1977
+ to = node.from;
1978
+ }
1979
+ } else if (Object.hasOwn(InlineMarker, name)) {
1980
+ const markType = InlineMarker[name];
1981
+ const size = inlineMarkerText(markType).length;
1982
+ const openEnd = node.from + size;
1983
+ const closeStart = node.to - size;
1984
+ if (markType === type) {
1985
+ if (openEnd <= from && closeStart >= from) {
1986
+ startCovered = openEnd === from ? "adjacent" : true;
1987
+ }
1988
+ if (openEnd <= to && closeStart >= to) {
1989
+ endCovered = closeStart === to ? "adjacent" : true;
1990
+ }
1991
+ }
1992
+ if (markType === type || type === 3 && enable) {
1993
+ if (node.from >= from && openEnd <= to) {
1994
+ changes2.push({
1995
+ from: node.from,
1996
+ to: openEnd
1997
+ });
1998
+ if (markType !== type && closeStart >= to) {
1999
+ changesAtEnd.push({
2000
+ from: skipSpaces(Math.min(to, blockEnd), state.doc, 1, blockEnd),
2001
+ insert: inlineMarkerText(markType)
2002
+ });
2003
+ }
2004
+ }
2005
+ if (closeStart >= from && node.to <= to) {
2006
+ changes2.push({
2007
+ from: closeStart,
2008
+ to: node.to
2009
+ });
2010
+ if (markType !== type && openEnd <= from) {
2011
+ changes2.push({
2012
+ from: skipSpaces(Math.max(from, blockStart), state.doc, -1, blockStart),
2013
+ insert: inlineMarkerText(markType)
2014
+ });
2015
+ }
2016
+ }
2017
+ }
1858
2018
  }
1859
- ]
2019
+ },
2020
+ leave: (node) => {
2021
+ if (Object.hasOwn(Textblocks, node.name) && Textblocks[node.name] !== "codeblock") {
2022
+ const rangeStart = Math.max(from, blockStart);
2023
+ const rangeEnd = Math.min(to, blockEnd);
2024
+ if (enable) {
2025
+ if (!startCovered) {
2026
+ changes2.push({
2027
+ from: rangeStart,
2028
+ insert: marker
2029
+ });
2030
+ }
2031
+ if (!endCovered) {
2032
+ changes2.push({
2033
+ from: rangeEnd,
2034
+ insert: marker
2035
+ });
2036
+ }
2037
+ } else {
2038
+ if (startCovered === "adjacent") {
2039
+ changes2.push({
2040
+ from: from - marker.length,
2041
+ to: from
2042
+ });
2043
+ } else if (startCovered) {
2044
+ changes2.push({
2045
+ from: skipSpaces(rangeStart, state.doc, -1, blockStart),
2046
+ insert: marker
2047
+ });
2048
+ }
2049
+ if (endCovered === "adjacent") {
2050
+ changes2.push({
2051
+ from: to,
2052
+ to: to + marker.length
2053
+ });
2054
+ } else if (endCovered) {
2055
+ changes2.push({
2056
+ from: skipSpaces(rangeEnd, state.doc, 1, blockEnd),
2057
+ insert: marker
2058
+ });
2059
+ }
2060
+ }
2061
+ }
2062
+ }
1860
2063
  });
1861
- }
2064
+ const changeSet = state.changes(changes2.concat(changesAtEnd));
2065
+ return {
2066
+ changes: changeSet,
2067
+ range: range.empty && !changeSet.empty ? EditorSelection.cursor(range.head + marker.length) : EditorSelection.range(changeSet.mapPos(range.from, 1), changeSet.mapPos(range.to, -1))
2068
+ };
2069
+ });
2070
+ dispatch(state.update(changes, {
2071
+ userEvent: enable ? "format.style.add" : "format.style.remove",
2072
+ scrollIntoView: true
2073
+ }));
1862
2074
  return true;
1863
2075
  };
2076
+ var addStyle = (style) => setStyle(style, true);
2077
+ var removeStyle = (style) => setStyle(style, false);
2078
+ var toggleStyle = (style) => (arg) => {
2079
+ const form = getFormatting(arg.state);
2080
+ return setStyle(style, style === 0 ? !form.strong : style === 1 ? !form.emphasis : style === 2 ? !form.strikethrough : !form.code)(arg);
2081
+ };
2082
+ var toggleStrong = toggleStyle(0);
2083
+ var toggleEmphasis = toggleStyle(1);
2084
+ var toggleStrikethrough = toggleStyle(2);
2085
+ var toggleInlineCode = toggleStyle(3);
2086
+ var inlineMarkerText = (type) => type === 0 ? "**" : type === 2 ? "~~" : type === 1 ? "*" : "`";
2087
+ var blockContentStart = (node) => {
2088
+ const atx = /^ATXHeading(\d)/.exec(node.name);
2089
+ if (atx) {
2090
+ return Math.min(node.to, node.from + +atx[1] + 1);
2091
+ }
2092
+ return node.from;
2093
+ };
2094
+ var blockContentEnd = (node, doc) => {
2095
+ const setext = /^SetextHeading(\d)/.exec(node.name);
2096
+ const lastLine = doc.lineAt(node.to);
2097
+ if (setext || /^[\s>]*$/.exec(lastLine.text)) {
2098
+ return lastLine.from - 1;
2099
+ }
2100
+ return node.to;
2101
+ };
2102
+ var skipSpaces = (pos, doc, dir, limit) => {
2103
+ const line = doc.lineAt(pos);
2104
+ while (pos !== limit && line.text[pos - line.from - (dir < 0 ? 1 : 0)] === " ") {
2105
+ pos += dir;
2106
+ }
2107
+ return pos;
2108
+ };
1864
2109
  var snippets = {
1865
2110
  codeblock: snippet([
1866
2111
  "```#{lang}",
1867
- " #{}",
2112
+ "#{}",
1868
2113
  "```"
1869
2114
  ].join("\n")),
1870
2115
  table: snippet([
@@ -1874,29 +2119,533 @@ var snippets = {
1874
2119
  "| #{val3} | #{val4} |"
1875
2120
  ].join("\n"))
1876
2121
  };
1877
- var insertCodeblock = (view) => {
1878
- const { selection: { main }, doc } = view.state;
1879
- const { number } = doc.lineAt(main.anchor);
1880
- const { from } = doc.line(number);
1881
- snippets.codeblock(view, null, from, from);
1882
- };
1883
2122
  var insertTable = (view) => {
1884
2123
  const { selection: { main }, doc } = view.state;
1885
2124
  const { number } = doc.lineAt(main.anchor);
1886
2125
  const { from } = doc.line(number);
1887
2126
  snippets.table(view, null, from, from);
1888
2127
  };
1889
- var toggleBold = toggleStyle("**");
1890
- var toggleItalic = toggleStyle("_");
1891
- var toggleStrikethrough = toggleStyle("~~");
1892
- var toggleList = (view) => {
2128
+ var removeLinkInner = (from, to, changes, state) => {
2129
+ syntaxTree2(state).iterate({
2130
+ from,
2131
+ to,
2132
+ enter: (node) => {
2133
+ if (node.name === "Link" && node.from < to && node.to > from) {
2134
+ node.node.cursor().iterate((node2) => {
2135
+ const { name } = node2;
2136
+ if (name === "LinkMark" || name === "LinkLabel") {
2137
+ changes.push({
2138
+ from: node2.from,
2139
+ to: node2.to
2140
+ });
2141
+ } else if (name === "LinkTitle" || name === "URL") {
2142
+ changes.push({
2143
+ from: skipSpaces(node2.from, state.doc, -1),
2144
+ to: skipSpaces(node2.to, state.doc, 1)
2145
+ });
2146
+ }
2147
+ });
2148
+ return false;
2149
+ }
2150
+ }
2151
+ });
2152
+ };
2153
+ var removeLink = ({ state, dispatch }) => {
2154
+ const changes = [];
2155
+ for (const { from, to } of state.selection.ranges) {
2156
+ removeLinkInner(from, to, changes, state);
2157
+ }
2158
+ if (!changes) {
2159
+ return false;
2160
+ }
2161
+ dispatch(state.update({
2162
+ changes,
2163
+ userEvent: "format.link.remove",
2164
+ scrollIntoView: true
2165
+ }));
2166
+ return true;
2167
+ };
2168
+ var addLink = ({ state, dispatch }) => {
2169
+ const changes = state.changeByRange((range) => {
2170
+ let { from, to } = range;
2171
+ const cutStyles = [];
2172
+ let okay = null;
2173
+ syntaxTree2(state).iterate({
2174
+ from,
2175
+ to,
2176
+ enter: (node) => {
2177
+ if (Object.hasOwn(Textblocks, node.name)) {
2178
+ okay = Textblocks[node.name] !== "codeblock" && from >= blockContentStart(node) && to <= blockContentEnd(node, state.doc);
2179
+ } else if (Object.hasOwn(InlineMarker, node.name)) {
2180
+ const sNode = node.node;
2181
+ if (node.from < from && node.to <= to) {
2182
+ if (sNode.firstChild.to === from) {
2183
+ from = node.from;
2184
+ } else {
2185
+ cutStyles.push(sNode);
2186
+ }
2187
+ } else if (node.from >= from && node.to > to) {
2188
+ if (sNode.lastChild.from === to) {
2189
+ to = node.to;
2190
+ } else {
2191
+ cutStyles.push(sNode);
2192
+ }
2193
+ }
2194
+ }
2195
+ }
2196
+ });
2197
+ if (okay === null) {
2198
+ const line = state.doc.lineAt(from);
2199
+ okay = to <= line.to && !/\S/.test(line.text.slice(from - line.from));
2200
+ }
2201
+ if (!okay) {
2202
+ return {
2203
+ range
2204
+ };
2205
+ }
2206
+ const changes2 = [];
2207
+ const changesAfter = [];
2208
+ removeLinkInner(from, to, changesAfter, state);
2209
+ let cursorOffset = 1;
2210
+ for (const style of cutStyles) {
2211
+ const type = InlineMarker[style.name];
2212
+ const mark2 = inlineMarkerText(type);
2213
+ if (style.from < from) {
2214
+ changes2.push({
2215
+ from: skipSpaces(from, state.doc, -1),
2216
+ insert: mark2
2217
+ });
2218
+ changesAfter.push({
2219
+ from: skipSpaces(from, state.doc, 1, to),
2220
+ insert: mark2
2221
+ });
2222
+ } else {
2223
+ changes2.push({
2224
+ from: skipSpaces(to, state.doc, -1, from),
2225
+ insert: mark2
2226
+ });
2227
+ const after = skipSpaces(to, state.doc, 1);
2228
+ if (after === to) {
2229
+ cursorOffset += mark2.length;
2230
+ }
2231
+ changesAfter.push({
2232
+ from: after,
2233
+ insert: mark2
2234
+ });
2235
+ }
2236
+ }
2237
+ changes2.push({
2238
+ from,
2239
+ insert: "["
2240
+ }, {
2241
+ from: to,
2242
+ insert: "]()"
2243
+ });
2244
+ const changeSet = state.changes(changes2.concat(changesAfter));
2245
+ return {
2246
+ changes: changeSet,
2247
+ range: EditorSelection.cursor(changeSet.mapPos(to, 1) - cursorOffset)
2248
+ };
2249
+ });
2250
+ if (changes.changes.empty) {
2251
+ return false;
2252
+ }
2253
+ dispatch(state.update(changes, {
2254
+ userEvent: "format.link.add",
2255
+ scrollIntoView: true
2256
+ }));
2257
+ return true;
2258
+ };
2259
+ var addList = (type) => ({ state, dispatch }) => {
2260
+ let lastBlock = -1;
2261
+ let counter = 1;
2262
+ let first = true;
2263
+ let parentColumn = null;
2264
+ const blocks = [];
2265
+ for (const { from, to } of state.selection.ranges) {
2266
+ syntaxTree2(state).iterate({
2267
+ from,
2268
+ to,
2269
+ enter: (node) => {
2270
+ if (Object.hasOwn(Textblocks, node.name) && node.name !== "TableCell" || node.name === "Table") {
2271
+ if (first) {
2272
+ let before = node.node.prevSibling;
2273
+ while (before && /Mark$/.test(before.name)) {
2274
+ before = before.prevSibling;
2275
+ }
2276
+ if (before?.name === (type === 0 ? "OrderedList" : "BulletList")) {
2277
+ const item = before.lastChild;
2278
+ const itemLine = state.doc.lineAt(item.from);
2279
+ const itemText = itemLine.text.slice(item.from - itemLine.from);
2280
+ parentColumn = item.from - itemLine.from + /^\s*/.exec(itemText)[0].length;
2281
+ if (type === 0) {
2282
+ const mark2 = /^\s*(\d+)[.)]/.exec(itemText);
2283
+ if (mark2) {
2284
+ parentColumn += mark2[1].length;
2285
+ counter = +mark2[1] + 1;
2286
+ }
2287
+ }
2288
+ }
2289
+ first = false;
2290
+ }
2291
+ if (node.from === lastBlock) {
2292
+ return;
2293
+ }
2294
+ lastBlock = node.from;
2295
+ blocks.push({
2296
+ node: node.node,
2297
+ counter,
2298
+ parentColumn
2299
+ });
2300
+ counter++;
2301
+ return false;
2302
+ }
2303
+ },
2304
+ leave: (node) => {
2305
+ if (node.name === "BulletList" || node.name === "OrderedList" || node.name === "Blockquote") {
2306
+ counter = 1;
2307
+ parentColumn = null;
2308
+ }
2309
+ }
2310
+ });
2311
+ }
2312
+ if (!blocks.length) {
2313
+ return false;
2314
+ }
2315
+ const changes = [];
2316
+ for (let i = 0; i < blocks.length; i++) {
2317
+ const { node, counter: counter2, parentColumn: parentColumn2 } = blocks[i];
2318
+ const nodeFrom = node.name === "CodeBlock" ? node.from - 4 : node.from;
2319
+ let padding = nodeFrom > 0 && !/\s/.test(state.doc.sliceString(nodeFrom - 1, nodeFrom)) ? 1 : 0;
2320
+ if (type === 0) {
2321
+ padding += String(counter2).length;
2322
+ }
2323
+ let line = state.doc.lineAt(nodeFrom);
2324
+ const column = nodeFrom - line.from;
2325
+ if (parentColumn2 !== null && parentColumn2 > column) {
2326
+ padding = Math.max(padding, parentColumn2 - column);
2327
+ }
2328
+ let mark2;
2329
+ if (type === 0) {
2330
+ let max = counter2;
2331
+ for (let j = i + 1; j < blocks.length; j++) {
2332
+ if (blocks[j].counter !== max + 1) {
2333
+ break;
2334
+ }
2335
+ max++;
2336
+ }
2337
+ const num = String(counter2);
2338
+ padding = Math.max(String(max).length, padding);
2339
+ mark2 = " ".repeat(Math.max(0, padding - num.length)) + num + ". ";
2340
+ } else {
2341
+ mark2 = " ".repeat(padding) + "- " + (type === 2 ? "[ ] " : "");
2342
+ }
2343
+ changes.push({
2344
+ from: nodeFrom,
2345
+ insert: mark2
2346
+ });
2347
+ while (line.to < node.to) {
2348
+ line = state.doc.lineAt(line.to + 1);
2349
+ const open = /^[\s>]*/.exec(line.text)[0].length;
2350
+ changes.push({
2351
+ from: line.from + Math.min(open, column),
2352
+ insert: " ".repeat(mark2.length)
2353
+ });
2354
+ }
2355
+ }
2356
+ if (type === 0) {
2357
+ const last = blocks[blocks.length - 1];
2358
+ let next = last.node.nextSibling;
2359
+ while (next && /Mark$/.test(next.name)) {
2360
+ next = next.nextSibling;
2361
+ }
2362
+ if (next?.name === "OrderedList") {
2363
+ renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
2364
+ }
2365
+ }
2366
+ dispatch(state.update({
2367
+ changes,
2368
+ userEvent: "format.list.add",
2369
+ scrollIntoView: true
2370
+ }));
2371
+ return true;
2372
+ };
2373
+ var removeList = (type) => ({ state, dispatch }) => {
2374
+ let lastBlock = -1;
2375
+ const changes = [];
2376
+ const stack = [];
2377
+ const targetNodeType = type === 0 ? "OrderedList" : type === 1 ? "BulletList" : "TaskList";
2378
+ for (const { from, to } of state.selection.ranges) {
2379
+ syntaxTree2(state).iterate({
2380
+ from,
2381
+ to,
2382
+ enter: (node) => {
2383
+ const { name } = node;
2384
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2385
+ stack.push(name);
2386
+ } else if (name === "Task" && stack[stack.length - 1] === "BulletList") {
2387
+ stack[stack.length - 1] = "TaskList";
2388
+ }
2389
+ },
2390
+ leave: (node) => {
2391
+ const { name } = node;
2392
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2393
+ stack.pop();
2394
+ } else if (name === "ListItem" && stack[stack.length - 1] === targetNodeType && node.from !== lastBlock) {
2395
+ lastBlock = node.from;
2396
+ let line = state.doc.lineAt(node.from);
2397
+ const mark2 = /^\s*(\d+[.)] |[-*+] (\[[ x]\] )?)/.exec(line.text.slice(node.from - line.from));
2398
+ if (!mark2) {
2399
+ return false;
2400
+ }
2401
+ const column = node.from - line.from;
2402
+ changes.push({
2403
+ from: node.from,
2404
+ to: node.from + mark2[0].length
2405
+ });
2406
+ while (line.to < node.to) {
2407
+ line = state.doc.lineAt(line.to + 1);
2408
+ const open = /^[\s>]*/.exec(line.text)[0].length;
2409
+ if (open > column) {
2410
+ changes.push({
2411
+ from: line.from + column,
2412
+ to: line.from + Math.min(column + mark2[0].length, open)
2413
+ });
2414
+ }
2415
+ }
2416
+ if (node.to >= to) {
2417
+ renumberListItems(node.node.nextSibling, 1, changes, state.doc);
2418
+ }
2419
+ return false;
2420
+ }
2421
+ }
2422
+ });
2423
+ }
2424
+ if (!changes.length) {
2425
+ return false;
2426
+ }
2427
+ dispatch(state.update({
2428
+ changes,
2429
+ userEvent: "format.list.remove",
2430
+ scrollIntoView: true
2431
+ }));
2432
+ return true;
2433
+ };
2434
+ var toggleList = (type) => (target) => {
2435
+ const formatting2 = getFormatting(target.state);
2436
+ const active = formatting2.listStyle === (type === 1 ? "bullet" : type === 0 ? "ordered" : "task");
2437
+ return (active ? removeList(type) : addList(type))(target);
2438
+ };
2439
+ var renumberListItems = (item, counter, changes, doc) => {
2440
+ for (; item; item = item.nextSibling) {
2441
+ if (item.name === "ListItem") {
2442
+ const number = /(\s*)(\d+)[.)]/.exec(doc.sliceString(item.from, item.from + 10));
2443
+ if (!number || +number[2] === counter) {
2444
+ break;
2445
+ }
2446
+ const size = number[1].length + number[2].length;
2447
+ const newNum = String(counter);
2448
+ changes.push({
2449
+ from: item.from + Math.max(0, size - newNum.length),
2450
+ to: item.from + size,
2451
+ insert: newNum
2452
+ });
2453
+ counter++;
2454
+ }
2455
+ }
2456
+ };
2457
+ var setBlockquote = (enable) => ({ state, dispatch }) => {
2458
+ const lines = [];
2459
+ let lastBlock = -1;
2460
+ for (const { from, to } of state.selection.ranges) {
2461
+ syntaxTree2(state).iterate({
2462
+ from,
2463
+ to,
2464
+ enter: (node) => {
2465
+ if (Object.hasOwn(Textblocks, node.name) || node.name === "Table") {
2466
+ if (node.from === lastBlock) {
2467
+ return false;
2468
+ }
2469
+ lastBlock = node.from;
2470
+ let line = state.doc.lineAt(node.from);
2471
+ if (line.number > 1) {
2472
+ const prevLine = state.doc.line(line.number - 1);
2473
+ if (/^[>\s]*$/.test(prevLine.text)) {
2474
+ if (!enable || lines.length && lines[lines.length - 1].number === prevLine.number - 1) {
2475
+ lines.push(prevLine);
2476
+ }
2477
+ }
2478
+ }
2479
+ for (; ; ) {
2480
+ lines.push(line);
2481
+ if (line.to >= node.to) {
2482
+ break;
2483
+ }
2484
+ line = state.doc.line(line.number + 1);
2485
+ }
2486
+ if (!enable && line.number < state.doc.lines) {
2487
+ const nextLine = state.doc.line(line.number + 1);
2488
+ if (/^[>\s]*$/.test(nextLine.text)) {
2489
+ lines.push(nextLine);
2490
+ }
2491
+ }
2492
+ return false;
2493
+ }
2494
+ }
2495
+ });
2496
+ }
2497
+ const changes = [];
2498
+ for (const line of lines) {
2499
+ if (enable) {
2500
+ changes.push({
2501
+ from: line.from,
2502
+ insert: /\S/.test(line.text) ? "> " : ">"
2503
+ });
2504
+ } else {
2505
+ const quote = /((?:[\s>\-+*]|\d+[.)])*?)> ?/.exec(line.text);
2506
+ if (quote) {
2507
+ changes.push({
2508
+ from: line.from + quote[1].length,
2509
+ to: line.from + quote[0].length
2510
+ });
2511
+ }
2512
+ }
2513
+ }
2514
+ if (!changes.length) {
2515
+ return false;
2516
+ }
2517
+ dispatch(state.update({
2518
+ changes,
2519
+ userEvent: enable ? "format.blockquote.add" : "format.blockquote.remove",
2520
+ scrollIntoView: true
2521
+ }));
2522
+ return true;
2523
+ };
2524
+ var addBlockquote = setBlockquote(true);
2525
+ var removeBlockquote = setBlockquote(false);
2526
+ var toggleBlockquote = (target) => {
2527
+ return (getFormatting(target.state).blockquote ? removeBlockquote : addBlockquote)(target);
2528
+ };
2529
+ var addCodeblock = (target) => {
2530
+ const { state, dispatch } = target;
2531
+ const { selection } = state;
2532
+ if (selection.ranges.length === 1 && selection.main.empty) {
2533
+ const { head } = selection.main;
2534
+ const line = state.doc.lineAt(head);
2535
+ if (!/\S/.test(line.text) && head === line.from) {
2536
+ snippets.codeblock(target, null, line.from, line.to);
2537
+ return true;
2538
+ }
2539
+ }
2540
+ const ranges = [];
2541
+ for (const { from, to } of selection.ranges) {
2542
+ let blockFrom = from;
2543
+ let blockTo = to;
2544
+ syntaxTree2(state).iterate({
2545
+ from,
2546
+ to,
2547
+ enter: (node) => {
2548
+ if (Object.hasOwn(Textblocks, node.name)) {
2549
+ if (from >= node.from && to <= node.to) {
2550
+ blockFrom = node.from;
2551
+ blockTo = node.to;
2552
+ } else {
2553
+ blockFrom = Math.min(blockFrom, state.doc.lineAt(node.from).from);
2554
+ blockTo = Math.max(blockTo, state.doc.lineAt(node.to).to);
2555
+ }
2556
+ }
2557
+ }
2558
+ });
2559
+ if (ranges.length && ranges[ranges.length - 1].to >= blockFrom - 1) {
2560
+ ranges[ranges.length - 1].to = blockTo;
2561
+ } else {
2562
+ ranges.push({
2563
+ from: blockFrom,
2564
+ to: blockTo
2565
+ });
2566
+ }
2567
+ }
2568
+ if (!ranges.length) {
2569
+ return false;
2570
+ }
2571
+ const changes = ranges.map(({ from, to }) => {
2572
+ const column = from - state.doc.lineAt(from).from;
2573
+ return [
2574
+ {
2575
+ from,
2576
+ insert: "```\n" + " ".repeat(column)
2577
+ },
2578
+ {
2579
+ from: to,
2580
+ insert: "\n" + " ".repeat(column) + "```"
2581
+ }
2582
+ ];
2583
+ });
2584
+ dispatch(state.update({
2585
+ changes,
2586
+ userEvent: "format.codeblock.add",
2587
+ scrollIntoView: true
2588
+ }));
2589
+ return true;
2590
+ };
2591
+ var removeCodeblock = ({ state, dispatch }) => {
2592
+ const changes = [];
2593
+ let lastBlock = -1;
2594
+ for (const { from, to } of state.selection.ranges) {
2595
+ syntaxTree2(state).iterate({
2596
+ from,
2597
+ to,
2598
+ enter: (node) => {
2599
+ if (Textblocks[node.name] === "codeblock" && lastBlock !== node.from) {
2600
+ lastBlock = node.from;
2601
+ const firstLine = state.doc.lineAt(node.from);
2602
+ if (node.name === "FencedCode") {
2603
+ changes.push({
2604
+ from: node.from,
2605
+ to: firstLine.to + 1 + node.from - firstLine.from
2606
+ });
2607
+ const lastLine = state.doc.lineAt(node.to);
2608
+ if (/^([\s>]|[-*+] |\d+[).])*`+$/.test(lastLine.text)) {
2609
+ changes.push({
2610
+ from: lastLine.from - (lastLine.number === firstLine.number + 1 ? 0 : 1),
2611
+ to: lastLine.to
2612
+ });
2613
+ }
2614
+ } else {
2615
+ const column = node.from - firstLine.from;
2616
+ for (let line = firstLine; ; line = state.doc.line(line.number + 1)) {
2617
+ changes.push({
2618
+ from: line.from + column - 4,
2619
+ to: line.from + column
2620
+ });
2621
+ if (line.to >= node.to) {
2622
+ break;
2623
+ }
2624
+ }
2625
+ }
2626
+ }
2627
+ }
2628
+ });
2629
+ }
2630
+ if (!changes.length) {
2631
+ return false;
2632
+ }
2633
+ dispatch(state.update({
2634
+ changes,
2635
+ userEvent: "format.codeblock.remove",
2636
+ scrollIntoView: true
2637
+ }));
2638
+ return true;
2639
+ };
2640
+ var toggleCodeblock = (target) => {
2641
+ return (getFormatting(target.state).blockType === "codeblock" ? removeCodeblock : addCodeblock)(target);
1893
2642
  };
1894
2643
  var formatting = (options = {}) => {
1895
2644
  return [
1896
2645
  keymap5.of([
1897
2646
  {
1898
2647
  key: "meta-b",
1899
- run: toggleBold
2648
+ run: toggleStrong
1900
2649
  }
1901
2650
  ]),
1902
2651
  styling()
@@ -1950,6 +2699,206 @@ var styling = () => {
1950
2699
  })
1951
2700
  ];
1952
2701
  };
2702
+ var InlineMarker = {
2703
+ Emphasis: 1,
2704
+ StrongEmphasis: 0,
2705
+ InlineCode: 3,
2706
+ Strikethrough: 2
2707
+ };
2708
+ var IgnoreInline = /* @__PURE__ */ new Set([
2709
+ "Hardbreak",
2710
+ "HTMLTag",
2711
+ "Comment",
2712
+ "ProcessingInstruction",
2713
+ "Autolink",
2714
+ "HeaderMark",
2715
+ "QuoteMark",
2716
+ "ListMark",
2717
+ "LinkMark",
2718
+ "EmphasisMark",
2719
+ "CodeMark",
2720
+ "CodeText",
2721
+ "StrikethroughMark",
2722
+ "TaskMarker",
2723
+ "SuperscriptMark",
2724
+ "SubscriptMark"
2725
+ ]);
2726
+ var Textblocks = {
2727
+ Paragraph: "paragraph",
2728
+ Task: "paragraph",
2729
+ CodeBlock: "codeblock",
2730
+ FencedCode: "codeblock",
2731
+ ATXHeading1: "heading1",
2732
+ ATXHeading2: "heading2",
2733
+ ATXHeading3: "heading3",
2734
+ ATXHeading4: "heading4",
2735
+ ATXHeading5: "heading5",
2736
+ ATXHeading6: "heading6",
2737
+ SetextHeading1: "heading1",
2738
+ SetextHeading2: "heading2",
2739
+ TableCell: "tablecell"
2740
+ };
2741
+ var getFormatting = (state) => {
2742
+ let blockType = null;
2743
+ const inline = [
2744
+ null,
2745
+ null,
2746
+ null,
2747
+ null
2748
+ ];
2749
+ let link2 = false;
2750
+ let blockquote2 = null;
2751
+ let listStyle = null;
2752
+ const stack = [];
2753
+ let currentBlock = null;
2754
+ const advanceInline = (upto) => {
2755
+ if (!currentBlock) {
2756
+ return;
2757
+ }
2758
+ upto = Math.min(upto, currentBlock.end);
2759
+ if (upto <= currentBlock.pos) {
2760
+ return;
2761
+ }
2762
+ for (let i = 0; i < currentBlock.active.length; i++) {
2763
+ if (inline[i] === false) {
2764
+ continue;
2765
+ } else if (currentBlock.active[i]) {
2766
+ inline[i] = true;
2767
+ } else if (/\S/.test(state.doc.sliceString(currentBlock.pos, upto))) {
2768
+ inline[i] = false;
2769
+ }
2770
+ }
2771
+ currentBlock.pos = upto;
2772
+ };
2773
+ const skipInline = (upto) => {
2774
+ if (currentBlock && upto > currentBlock.pos) {
2775
+ currentBlock.pos = Math.min(upto, currentBlock.end);
2776
+ }
2777
+ };
2778
+ const { selection } = state;
2779
+ for (const range of selection.ranges) {
2780
+ if (range.empty && inline.some((v) => v === null)) {
2781
+ const contextSize = Math.min(range.head, 6);
2782
+ const contextBefore = state.doc.sliceString(range.head - contextSize, range.head);
2783
+ let contextAfter = state.doc.sliceString(range.head, range.head + contextSize);
2784
+ for (let i = 0; i < contextSize; i++) {
2785
+ const ch = contextAfter[i];
2786
+ if (ch !== contextBefore[contextBefore.length - 1 - i] || !/[~`*]/.test(ch)) {
2787
+ contextAfter = contextAfter.slice(0, i);
2788
+ break;
2789
+ }
2790
+ }
2791
+ for (let i = 0; i < inline.length; i++) {
2792
+ const mark2 = inlineMarkerText(i);
2793
+ const found = contextAfter.indexOf(mark2);
2794
+ if (found > -1) {
2795
+ contextAfter = contextAfter.slice(0, found) + contextAfter.slice(found + mark2.length);
2796
+ if (inline[i] === null) {
2797
+ inline[i] = true;
2798
+ }
2799
+ }
2800
+ }
2801
+ }
2802
+ syntaxTree2(state).iterate({
2803
+ from: range.from,
2804
+ to: range.to,
2805
+ enter: (node) => {
2806
+ advanceInline(node.from);
2807
+ const { name } = node;
2808
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2809
+ stack.push(name);
2810
+ } else if (name === "Link") {
2811
+ link2 = true;
2812
+ } else if (Object.hasOwn(Textblocks, name) && (range.empty || node.to > range.from || node.from < range.to)) {
2813
+ if (name === "Task" && stack[stack.length - 1] === "BulletList") {
2814
+ stack[stack.length - 1] = "TaskList";
2815
+ }
2816
+ const blockCode = Textblocks[name];
2817
+ if (blockType === null) {
2818
+ blockType = blockCode;
2819
+ } else if (blockType !== blockCode) {
2820
+ blockType = false;
2821
+ }
2822
+ if (blockCode !== "codeblock" && inline.some((i) => i !== false)) {
2823
+ currentBlock = {
2824
+ pos: Math.max(range.from, node.from),
2825
+ end: Math.min(range.to, node.to),
2826
+ active: [
2827
+ false,
2828
+ false,
2829
+ false,
2830
+ false
2831
+ ]
2832
+ };
2833
+ }
2834
+ } else if (Object.hasOwn(InlineMarker, name) && currentBlock) {
2835
+ const index = InlineMarker[name];
2836
+ if (range.empty && inline[index] === null) {
2837
+ inline[index] = true;
2838
+ }
2839
+ currentBlock.active[index] = true;
2840
+ } else if (IgnoreInline.has(name)) {
2841
+ skipInline(node.to);
2842
+ }
2843
+ },
2844
+ leave: (node) => {
2845
+ advanceInline(node.to);
2846
+ const { name } = node;
2847
+ if (name === "BulletList" || name === "OrderedList" || name === "Blockquote") {
2848
+ stack.pop();
2849
+ } else if (Object.hasOwn(Textblocks, name)) {
2850
+ let hasList = false;
2851
+ let hasQuote = false;
2852
+ for (let i = stack.length - 1; i >= 0; i--) {
2853
+ if (stack[i] === "Blockquote") {
2854
+ hasQuote = true;
2855
+ } else if (!hasList) {
2856
+ hasList = stack[i] === "TaskList" ? "task" : stack[i] === "BulletList" ? "bullet" : "ordered";
2857
+ }
2858
+ }
2859
+ if (blockquote2 === null) {
2860
+ blockquote2 = hasQuote;
2861
+ } else if (!hasQuote && blockquote2) {
2862
+ blockquote2 = false;
2863
+ }
2864
+ if (listStyle === null) {
2865
+ listStyle = hasList;
2866
+ } else if (listStyle !== hasList) {
2867
+ listStyle = false;
2868
+ }
2869
+ currentBlock = null;
2870
+ } else if (Object.hasOwn(InlineMarker, name) && currentBlock) {
2871
+ currentBlock.active[InlineMarker[name]] = false;
2872
+ }
2873
+ }
2874
+ });
2875
+ }
2876
+ return {
2877
+ blockType: blockType || null,
2878
+ strong: inline[0] ?? false,
2879
+ emphasis: inline[1] ?? false,
2880
+ code: inline[3] ?? false,
2881
+ strikethrough: inline[2] ?? false,
2882
+ link: link2,
2883
+ blockquote: blockquote2 ?? false,
2884
+ listStyle: listStyle || null
2885
+ };
2886
+ };
2887
+ var useFormattingState = () => {
2888
+ const [state, setState] = useState(null);
2889
+ const observer = useMemo(() => EditorView9.updateListener.of((update2) => {
2890
+ if (update2.docChanged || update2.selectionSet) {
2891
+ const newState = getFormatting(update2.state);
2892
+ if (!state || !compareFormatting(state, newState)) {
2893
+ setState(newState);
2894
+ }
2895
+ }
2896
+ }), []);
2897
+ return [
2898
+ state,
2899
+ observer
2900
+ ];
2901
+ };
1953
2902
 
1954
2903
  // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1955
2904
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
@@ -2002,8 +2951,8 @@ var heading2 = () => {
2002
2951
  // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
2003
2952
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2004
2953
  import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2005
- import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2006
- var styles4 = EditorView9.baseTheme({
2954
+ import { Decoration as Decoration6, EditorView as EditorView10, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2955
+ var styles4 = EditorView10.baseTheme({
2007
2956
  "& .cm-hr": {
2008
2957
  // Note that block-level decorations should not have vertical margins,
2009
2958
  borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
@@ -2057,7 +3006,7 @@ var hr = () => {
2057
3006
  // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
2058
3007
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2059
3008
  import { StateField as StateField3 } from "@codemirror/state";
2060
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType4 } from "@codemirror/view";
3009
+ import { Decoration as Decoration7, EditorView as EditorView11, WidgetType as WidgetType4 } from "@codemirror/view";
2061
3010
  var image = (options = {}) => {
2062
3011
  return StateField3.define({
2063
3012
  create: (state) => {
@@ -2084,7 +3033,7 @@ var image = (options = {}) => {
2084
3033
  add: buildDecorations4(from, to, tr.state)
2085
3034
  });
2086
3035
  },
2087
- provide: (field) => EditorView10.decorations.from(field)
3036
+ provide: (field) => EditorView11.decorations.from(field)
2088
3037
  });
2089
3038
  };
2090
3039
  var buildDecorations4 = (from, to, state) => {
@@ -2266,12 +3215,12 @@ var buildDecorations5 = (view, options) => {
2266
3215
  // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2267
3216
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2268
3217
  import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2269
- import { Decoration as Decoration9, EditorView as EditorView11, WidgetType as WidgetType6 } from "@codemirror/view";
3218
+ import { Decoration as Decoration9, EditorView as EditorView12, WidgetType as WidgetType6 } from "@codemirror/view";
2270
3219
  var table = (options = {}) => {
2271
3220
  return StateField4.define({
2272
3221
  create: (state) => update(state, options),
2273
3222
  update: (_, tr) => update(tr.state, options),
2274
- provide: (field) => EditorView11.decorations.from(field)
3223
+ provide: (field) => EditorView12.decorations.from(field)
2275
3224
  });
2276
3225
  };
2277
3226
  var update = (state, options) => {
@@ -2364,8 +3313,8 @@ var TableWidget = class extends WidgetType6 {
2364
3313
  // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2365
3314
  import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2366
3315
  import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2367
- import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2368
- var styles5 = EditorView12.baseTheme({
3316
+ import { EditorView as EditorView13, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
3317
+ var styles5 = EditorView13.baseTheme({
2369
3318
  "& .cm-task": {
2370
3319
  color: getToken("extend.colors.blue.500")
2371
3320
  },
@@ -2590,11 +3539,11 @@ var defaultTheme = {
2590
3539
  fontSize: "16px"
2591
3540
  },
2592
3541
  "&light .cm-content": {
2593
- color: get3(tokens, "extend.colors.neutral.900", "black"),
3542
+ color: get3(tokens, "extend.semanticColors.base.fg.light", "black"),
2594
3543
  caretColor: "black"
2595
3544
  },
2596
3545
  "&dark .cm-content": {
2597
- color: get3(tokens, "extend.colors.neutral.100", "white"),
3546
+ color: get3(tokens, "extend.semanticColors.base.fg.dark", "white"),
2598
3547
  caretColor: "white"
2599
3548
  },
2600
3549
  //
@@ -2606,8 +3555,11 @@ var defaultTheme = {
2606
3555
  "&dark .cm-cursor, &dark .cm-dropCursor": {
2607
3556
  borderLeft: "2px solid white"
2608
3557
  },
2609
- ".cm-placeholder": {
2610
- fontWeight: 100
3558
+ "&light .cm-placeholder": {
3559
+ color: get3(tokens, "extend.semanticColors.description.light", "rgba(0,0,0,.2)")
3560
+ },
3561
+ "&dark .cm-placeholder": {
3562
+ color: get3(tokens, "extend.semanticColors.description.dark", "rgba(255,255,255,.2)")
2611
3563
  },
2612
3564
  //
2613
3565
  // line
@@ -2795,7 +3747,7 @@ var EditorModes = [
2795
3747
  var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2796
3748
  const { themeMode } = useThemeContext();
2797
3749
  const rootRef = useRef(null);
2798
- const [view, setView] = useState(null);
3750
+ const [view, setView] = useState2(null);
2799
3751
  useImperativeHandle(forwardedRef, () => view, [
2800
3752
  view
2801
3753
  ]);
@@ -2824,7 +3776,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2824
3776
  selection,
2825
3777
  extensions: [
2826
3778
  // TODO(burdon): Doesn't catch errors in keymap functions.
2827
- EditorView13.exceptionSink.of((err) => {
3779
+ EditorView14.exceptionSink.of((err) => {
2828
3780
  log2.catch(err, void 0, {
2829
3781
  F: __dxlog_file3,
2830
3782
  L: 134,
@@ -2834,17 +3786,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2834
3786
  }),
2835
3787
  // Theme.
2836
3788
  // TODO(burdon): Make configurable.
2837
- EditorView13.baseTheme(defaultTheme),
2838
- EditorView13.theme(theme ?? {}),
2839
- EditorView13.darkTheme.of(themeMode === "dark"),
2840
- EditorView13.editorAttributes.of({
3789
+ EditorView14.baseTheme(defaultTheme),
3790
+ EditorView14.theme(theme ?? {}),
3791
+ EditorView14.darkTheme.of(themeMode === "dark"),
3792
+ EditorView14.editorAttributes.of({
2841
3793
  class: slots.editor?.className ?? ""
2842
3794
  }),
2843
- EditorView13.contentAttributes.of({
3795
+ EditorView14.contentAttributes.of({
2844
3796
  class: slots.content?.className ?? ""
2845
3797
  }),
2846
3798
  // State.
2847
- EditorView13.editable.of(!readonly),
3799
+ EditorView14.editable.of(!readonly),
2848
3800
  EditorState2.readOnly.of(!!readonly),
2849
3801
  // Storage and replication.
2850
3802
  // NOTE: This must come before user extensions.
@@ -2855,7 +3807,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2855
3807
  ...extensions
2856
3808
  ].filter(isNotFalsy3)
2857
3809
  });
2858
- const newView = new EditorView13({
3810
+ const newView = new EditorView14({
2859
3811
  state,
2860
3812
  parent: rootRef.current,
2861
3813
  scrollTo,
@@ -2948,11 +3900,10 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeh
2948
3900
  });
2949
3901
  var defaultSlots = {
2950
3902
  root: {
2951
- // TODO(burdon): Add focusRing by default/as property?
2952
- className: mx3("flex flex-col grow overflow-y-auto", attentionSurface)
3903
+ className: mx3("grow", focusRing)
2953
3904
  },
2954
3905
  editor: {
2955
- className: "h-full p-2"
3906
+ className: "bs-full"
2956
3907
  }
2957
3908
  };
2958
3909
  var defaultTextSlots = {
@@ -2963,35 +3914,40 @@ var defaultMarkdownSlots = {
2963
3914
  };
2964
3915
 
2965
3916
  // packages/ui/react-ui-editor/src/components/Toolbar/Toolbar.tsx
2966
- import { At, CaretRight, ChatText, Code, Image, Link, ListBullets, ListChecks, ListNumbers, TextStrikethrough, Table as Table2, TextB, TextItalic } from "@phosphor-icons/react";
3917
+ import { CaretRight, ChatText, Code, Link, ListBullets, ListChecks, ListNumbers, Paragraph, TextStrikethrough, Table as Table2, TextB, TextItalic } from "@phosphor-icons/react";
2967
3918
  import { createContext } from "@radix-ui/react-context";
2968
3919
  import React2 from "react";
2969
3920
  import { Button, DensityProvider, Select } from "@dxos/react-ui";
2970
- import { getSize } from "@dxos/react-ui-theme";
3921
+ import { getSize, mx as mx4 } from "@dxos/react-ui-theme";
2971
3922
  var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
2972
- var ToolbarRoot = ({ children, onAction }) => {
3923
+ var ToolbarRoot = ({ children, onAction, state }) => {
2973
3924
  return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
2974
- onAction
3925
+ onAction,
3926
+ state
2975
3927
  }, /* @__PURE__ */ React2.createElement(DensityProvider, {
2976
3928
  density: "fine"
2977
3929
  }, /* @__PURE__ */ React2.createElement("div", {
2978
3930
  role: "toolbar",
2979
- className: "flex w-full shrink-0 p-2 gap-4 items-center whitespace-nowrap overflow-hidden"
3931
+ className: "flex w-full shrink-0 p-1 gap-2 items-center whitespace-nowrap overflow-hidden"
2980
3932
  }, children)));
2981
3933
  };
2982
- var ToolbarButton = ({ Icon, onClick, title }) => {
2983
- const { onAction } = useToolbarContext("ToolbarButton");
2984
- const handleClick = () => {
2985
- const action = onClick();
3934
+ var ToolbarButton = ({ Icon, onClick, title, getState, disable }) => {
3935
+ const { onAction, state } = useToolbarContext("ToolbarButton");
3936
+ const active = getState && state ? getState(state) : false;
3937
+ const disabled = disable && state ? disable(state) : false;
3938
+ const handleClick = (event) => {
3939
+ const action = onClick(state);
2986
3940
  if (action) {
2987
3941
  onAction?.(action);
3942
+ event.preventDefault();
2988
3943
  }
2989
3944
  };
2990
3945
  return /* @__PURE__ */ React2.createElement(Button, {
2991
3946
  variant: "ghost",
2992
- classNames: "p-2",
2993
- onClick: handleClick,
2994
- title
3947
+ classNames: mx4("p-2", active && "ring-[1px]"),
3948
+ onMouseDown: handleClick,
3949
+ title,
3950
+ disabled
2995
3951
  }, /* @__PURE__ */ React2.createElement(Icon, {
2996
3952
  className: getSize(5)
2997
3953
  }));
@@ -3000,16 +3956,20 @@ var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
3000
3956
  className: "grow"
3001
3957
  });
3002
3958
  var MarkdownHeading = () => {
3003
- const { onAction } = useToolbarContext("MarkdownFormatting");
3959
+ const { onAction, state } = useToolbarContext("MarkdownFormatting");
3960
+ const blockType = state ? state.blockType : "paragraph";
3961
+ const header = blockType && /heading(\d)/.exec(blockType);
3962
+ const value = header ? header[1] : blockType === "paragraph" || !blockType ? "0" : null;
3004
3963
  return /* @__PURE__ */ React2.createElement(Select.Root, {
3005
- defaultValue: "0",
3006
- onValueChange: (value) => onAction?.({
3964
+ disabled: value === null,
3965
+ value: value ?? "0",
3966
+ onValueChange: (value2) => onAction?.({
3007
3967
  type: "heading",
3008
- data: parseInt(value)
3968
+ data: parseInt(value2)
3009
3969
  })
3010
- }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, {
3011
- classNames: "w-[8rem]"
3012
- }), /* @__PURE__ */ React2.createElement(Select.Portal, null, /* @__PURE__ */ React2.createElement(Select.Content, null, /* @__PURE__ */ React2.createElement(Select.Viewport, null, /* @__PURE__ */ React2.createElement(Select.Option, {
3970
+ }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, null, /* @__PURE__ */ React2.createElement(Paragraph, {
3971
+ className: getSize(5)
3972
+ })), /* @__PURE__ */ React2.createElement(Select.Portal, null, /* @__PURE__ */ React2.createElement(Select.Content, null, /* @__PURE__ */ React2.createElement(Select.Viewport, null, /* @__PURE__ */ React2.createElement(Select.Option, {
3013
3973
  value: "0"
3014
3974
  }, "Paragraph"), [
3015
3975
  1,
@@ -3027,21 +3987,39 @@ var MarkdownStyles = () => /* @__PURE__ */ React2.createElement("div", {
3027
3987
  role: "none"
3028
3988
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3029
3989
  Icon: TextB,
3030
- title: "Bold",
3031
- onClick: () => ({
3032
- type: "bold"
3990
+ title: "String",
3991
+ disable: (s) => s.blockType === "codeblock",
3992
+ getState: (s) => s.strong,
3993
+ onClick: (s) => ({
3994
+ type: "strong",
3995
+ data: s ? !s.strong : null
3033
3996
  })
3034
3997
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3035
3998
  Icon: TextItalic,
3036
- title: "Italic",
3037
- onClick: () => ({
3038
- type: "italic"
3999
+ title: "Emphasis",
4000
+ disable: (s) => s.blockType === "codeblock",
4001
+ getState: (s) => s.emphasis,
4002
+ onClick: (s) => ({
4003
+ type: "emphasis",
4004
+ data: s ? !s.emphasis : null
3039
4005
  })
3040
4006
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3041
4007
  Icon: TextStrikethrough,
3042
4008
  title: "Strike-through",
3043
- onClick: () => ({
3044
- type: "strikethrough"
4009
+ disable: (s) => s.blockType === "codeblock",
4010
+ getState: (s) => s.strikethrough,
4011
+ onClick: (s) => ({
4012
+ type: "strikethrough",
4013
+ data: s ? !s.strikethrough : null
4014
+ })
4015
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4016
+ Icon: Code,
4017
+ title: "Inline code",
4018
+ disable: (s) => s.blockType === "codeblock",
4019
+ getState: (s) => s.code,
4020
+ onClick: (s) => ({
4021
+ type: "code",
4022
+ data: s ? !s.code : null
3045
4023
  })
3046
4024
  }));
3047
4025
  var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
@@ -3049,35 +4027,45 @@ var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
3049
4027
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3050
4028
  Icon: ListBullets,
3051
4029
  title: "Bullet list",
3052
- onClick: () => ({
3053
- type: "list"
4030
+ getState: (s) => s.listStyle === "bullet",
4031
+ onClick: (s) => ({
4032
+ type: "list-bullet",
4033
+ data: s ? s.listStyle !== "bullet" : null
3054
4034
  })
3055
4035
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3056
4036
  Icon: ListNumbers,
3057
4037
  title: "Numbered list",
3058
- onClick: () => ({
3059
- type: "list-ordered"
4038
+ getState: (s) => s.listStyle === "ordered",
4039
+ onClick: (s) => ({
4040
+ type: "list-ordered",
4041
+ data: s ? s.listStyle !== "ordered" : null
3060
4042
  })
3061
4043
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3062
4044
  Icon: ListChecks,
3063
4045
  title: "Task list",
3064
- onClick: () => ({
3065
- type: "list-tasks"
4046
+ getState: (s) => s.listStyle === "task",
4047
+ onClick: (s) => ({
4048
+ type: "list-tasks",
4049
+ data: s ? s.listStyle !== "task" : null
3066
4050
  })
3067
4051
  }));
3068
4052
  var MarkdownBlocks = () => /* @__PURE__ */ React2.createElement("div", {
3069
4053
  role: "none"
3070
4054
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3071
4055
  Icon: CaretRight,
3072
- title: "Block quite",
3073
- onClick: () => ({
3074
- type: "blockquote"
4056
+ title: "Block quote",
4057
+ getState: (s) => s.blockquote,
4058
+ onClick: (s) => ({
4059
+ type: "blockquote",
4060
+ data: s ? !s.blockquote : null
3075
4061
  })
3076
4062
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3077
4063
  Icon: Code,
3078
4064
  title: "Code block",
3079
- onClick: () => ({
3080
- type: "codeblock"
4065
+ getState: (s) => s.blockType === "codeblock",
4066
+ onClick: (s) => ({
4067
+ type: "codeblock",
4068
+ data: s ? s.blockType !== "codeblock" : null
3081
4069
  })
3082
4070
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3083
4071
  Icon: Table2,
@@ -3091,20 +4079,10 @@ var MarkdownLinks = () => /* @__PURE__ */ React2.createElement("div", {
3091
4079
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3092
4080
  Icon: Link,
3093
4081
  title: "Link",
3094
- onClick: () => ({
3095
- type: "link"
3096
- })
3097
- }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3098
- Icon: At,
3099
- title: "Mention",
3100
- onClick: () => ({
3101
- type: "mention"
3102
- })
3103
- }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3104
- Icon: Image,
3105
- title: "Image",
3106
- onClick: () => ({
3107
- type: "image"
4082
+ getState: (s) => s.link,
4083
+ onClick: (s) => ({
4084
+ type: "link",
4085
+ data: s ? !s.link : null
3108
4086
  })
3109
4087
  }));
3110
4088
  var MarkdownStandard = () => /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(MarkdownHeading, null), /* @__PURE__ */ React2.createElement(MarkdownStyles, null), /* @__PURE__ */ React2.createElement(MarkdownLists, null), /* @__PURE__ */ React2.createElement(MarkdownBlocks, null), /* @__PURE__ */ React2.createElement(MarkdownLinks, null));
@@ -3240,45 +4218,52 @@ var useActionHandler = (view) => {
3240
4218
  if (!view) {
3241
4219
  return;
3242
4220
  }
4221
+ let inlineType, listType;
3243
4222
  switch (action.type) {
3244
4223
  case "heading":
3245
4224
  setHeading(parseInt(action.data))(view);
3246
4225
  break;
3247
- case "bold":
3248
- toggleBold(view);
3249
- break;
3250
- case "italic":
3251
- toggleItalic(view);
3252
- break;
4226
+ case "strong":
4227
+ case "emphasis":
3253
4228
  case "strikethrough":
3254
- toggleStrikethrough(view);
4229
+ case "code":
4230
+ inlineType = action.type === "strong" ? Inline.Strong : action.type === "emphasis" ? Inline.Emphasis : action.type === "strikethrough" ? Inline.Strikethrough : Inline.Code;
4231
+ (typeof action.data === "boolean" ? setStyle(inlineType, action.data) : toggleStyle(inlineType))(view);
3255
4232
  break;
3256
- case "list":
3257
- toggleList(view);
4233
+ case "list-ordered":
4234
+ case "list-bullet":
4235
+ case "list-tasks":
4236
+ listType = action.type === "list-ordered" ? List.Ordered : action.type === "list-bullet" ? List.Bullet : List.Task;
4237
+ (action.data === false ? removeList(listType) : action.data === true ? addList(listType) : toggleList(listType))(view);
4238
+ break;
4239
+ case "blockquote":
4240
+ (action.data === false ? removeBlockquote : action.data === true ? addBlockquote : toggleBlockquote)(view);
3258
4241
  break;
3259
4242
  case "codeblock":
3260
- insertCodeblock(view);
4243
+ (action.data === false ? removeCodeblock : addCodeblock)(view);
3261
4244
  break;
3262
4245
  case "table":
3263
4246
  insertTable(view);
3264
4247
  break;
4248
+ case "link":
4249
+ (action.data === false ? removeLink : addLink)(view);
4250
+ break;
3265
4251
  case "comment":
3266
4252
  createComment(view);
3267
4253
  break;
3268
4254
  }
3269
4255
  setTimeout(() => {
3270
- view.focus();
3271
- view.dispatch({
3272
- selection: view.state.selection
3273
- });
4256
+ if (!view.hasFocus) {
4257
+ view.focus();
4258
+ }
3274
4259
  });
3275
4260
  };
3276
4261
  };
3277
4262
 
3278
4263
  // packages/ui/react-ui-editor/src/hooks/useEditorView.ts
3279
- import { useState as useState2 } from "react";
4264
+ import { useState as useState3 } from "react";
3280
4265
  var useEditorView = () => {
3281
- const [view, setView] = useState2(null);
4266
+ const [view, setView] = useState3(null);
3282
4267
  return [
3283
4268
  (ref) => {
3284
4269
  setView(ref);
@@ -3288,15 +4273,15 @@ var useEditorView = () => {
3288
4273
  };
3289
4274
 
3290
4275
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3291
- import { EditorSelection, EditorState as EditorState3 } from "@codemirror/state";
3292
- import { EditorView as EditorView14 } from "@codemirror/view";
3293
- import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
4276
+ import { EditorSelection as EditorSelection2, EditorState as EditorState3 } from "@codemirror/state";
4277
+ import { EditorView as EditorView15 } from "@codemirror/view";
4278
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
3294
4279
  import { log as log4 } from "@dxos/log";
3295
4280
  import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
3296
4281
  var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
3297
4282
  var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions } = {}) => {
3298
4283
  const onUpdate = useRef2();
3299
- const [view, setView] = useState3();
4284
+ const [view, setView] = useState4();
3300
4285
  const parentRef = useRef2(null);
3301
4286
  useEffect3(() => {
3302
4287
  if (parentRef.current) {
@@ -3305,7 +4290,7 @@ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions }
3305
4290
  selection,
3306
4291
  extensions: [
3307
4292
  // TODO(burdon): Doesn't catch errors in keymap functions.
3308
- EditorView14.exceptionSink.of((err) => {
4293
+ EditorView15.exceptionSink.of((err) => {
3309
4294
  log4.catch(err, void 0, {
3310
4295
  F: __dxlog_file5,
3311
4296
  L: 57,
@@ -3314,12 +4299,12 @@ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions }
3314
4299
  });
3315
4300
  }),
3316
4301
  extensions,
3317
- EditorView14.updateListener.of(() => {
4302
+ EditorView15.updateListener.of(() => {
3318
4303
  onUpdate.current?.();
3319
4304
  })
3320
4305
  ].filter(isNotFalsy4)
3321
4306
  });
3322
- const view2 = new EditorView14({
4307
+ const view2 = new EditorView15({
3323
4308
  parent: parentRef.current,
3324
4309
  scrollTo,
3325
4310
  state,
@@ -3343,7 +4328,7 @@ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions }
3343
4328
  useEffect3(() => {
3344
4329
  if (view) {
3345
4330
  if (!selection && !view.state.selection.main.anchor) {
3346
- selection = EditorSelection.single(view.state.doc.line(1).to);
4331
+ selection = EditorSelection2.single(view.state.doc.line(1).to);
3347
4332
  }
3348
4333
  if (selection || scrollTo) {
3349
4334
  onUpdate.current = () => {
@@ -3376,19 +4361,19 @@ var createDataExtensions = ({ readonly = false } = {}) => {
3376
4361
  return [
3377
4362
  //
3378
4363
  EditorState3.readOnly.of(readonly),
3379
- EditorView14.editable.of(!readonly)
4364
+ EditorView15.editable.of(!readonly)
3380
4365
  ];
3381
4366
  };
3382
4367
  var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3383
4368
  return [
3384
4369
  //
3385
- EditorView14.baseTheme(defaultTheme),
3386
- theme && EditorView14.theme(theme),
3387
- EditorView14.darkTheme.of(themeMode === "dark"),
3388
- EditorView14.editorAttributes.of({
4370
+ EditorView15.baseTheme(defaultTheme),
4371
+ theme && EditorView15.theme(theme),
4372
+ EditorView15.darkTheme.of(themeMode === "dark"),
4373
+ EditorView15.editorAttributes.of({
3389
4374
  class: slots?.editor?.className ?? ""
3390
4375
  }),
3391
- EditorView14.contentAttributes.of({
4376
+ EditorView15.contentAttributes.of({
3392
4377
  class: slots?.content?.className ?? ""
3393
4378
  })
3394
4379
  ].filter(isNotFalsy4);
@@ -3396,7 +4381,7 @@ var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3396
4381
 
3397
4382
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
3398
4383
  import get4 from "lodash.get";
3399
- import { useEffect as useEffect4, useState as useState4 } from "react";
4384
+ import { useEffect as useEffect4, useState as useState5 } from "react";
3400
4385
  import { generateName } from "@dxos/display-name";
3401
4386
  import { getRawDoc } from "@dxos/echo-schema";
3402
4387
  import { invariant as invariant3 } from "@dxos/invariant";
@@ -3404,7 +4389,7 @@ import { isAutomergeObject } from "@dxos/react-client/echo";
3404
4389
  var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
3405
4390
  var useTextModel = (props) => {
3406
4391
  const { identity, space, text } = props;
3407
- const [model, setModel] = useState4();
4392
+ const [model, setModel] = useState5();
3408
4393
  useEffect4(() => setModel(createModel(props)), [
3409
4394
  identity,
3410
4395
  space,
@@ -3412,10 +4397,19 @@ var useTextModel = (props) => {
3412
4397
  ]);
3413
4398
  return model;
3414
4399
  };
4400
+ var useInMemoryTextModel = ({ id, defaultContent }) => {
4401
+ const [content, setContent] = useState5(defaultContent ?? "");
4402
+ return {
4403
+ id,
4404
+ content,
4405
+ setContent,
4406
+ text: () => content
4407
+ };
4408
+ };
3415
4409
  var createModel = ({ space, identity, text }) => {
3416
4410
  invariant3(isAutomergeObject(text), void 0, {
3417
4411
  F: __dxlog_file6,
3418
- L: 34,
4412
+ L: 50,
3419
4413
  S: void 0,
3420
4414
  A: [
3421
4415
  "isAutomergeObject(text)",
@@ -3464,6 +4458,8 @@ export {
3464
4458
  Cursor,
3465
4459
  Doc,
3466
4460
  EditorModes,
4461
+ Inline,
4462
+ List,
3467
4463
  MarkdownEditor,
3468
4464
  RemoteSelectionsDecorator,
3469
4465
  SpaceAwarenessProvider,
@@ -3472,6 +4468,11 @@ export {
3472
4468
  Toolbar,
3473
4469
  YText,
3474
4470
  YXmlFragment,
4471
+ addBlockquote,
4472
+ addCodeblock,
4473
+ addLink,
4474
+ addList,
4475
+ addStyle,
3475
4476
  autocomplete,
3476
4477
  automerge,
3477
4478
  awareness,
@@ -3481,6 +4482,7 @@ export {
3481
4482
  code2 as code,
3482
4483
  codeTheme,
3483
4484
  comments,
4485
+ compareFormatting,
3484
4486
  createBasicBundle,
3485
4487
  createComment,
3486
4488
  createDataExtensions,
@@ -3493,12 +4495,13 @@ export {
3493
4495
  defaultTheme,
3494
4496
  focusComment,
3495
4497
  formatting,
4498
+ getFormatting,
3496
4499
  getToken,
3497
4500
  heading2 as heading,
3498
4501
  hr,
3499
4502
  image,
3500
- insertCodeblock,
3501
4503
  insertTable,
4504
+ keymap7 as keymap,
3502
4505
  link,
3503
4506
  listener,
3504
4507
  logChanges,
@@ -3509,22 +4512,34 @@ export {
3509
4512
  mention,
3510
4513
  modelState,
3511
4514
  outliner,
4515
+ removeBlockquote,
4516
+ removeCodeblock,
4517
+ removeLink,
4518
+ removeList,
4519
+ removeStyle,
4520
+ setBlockquote,
3512
4521
  setComments,
3513
4522
  setHeading,
3514
4523
  setSelection,
4524
+ setStyle,
3515
4525
  table,
3516
4526
  tags2 as tags,
3517
4527
  tasklist,
3518
4528
  textTheme,
3519
- toggleBold,
3520
- toggleItalic,
4529
+ toggleBlockquote,
4530
+ toggleCodeblock,
4531
+ toggleEmphasis,
4532
+ toggleInlineCode,
3521
4533
  toggleList,
3522
4534
  toggleStrikethrough,
4535
+ toggleStrong,
3523
4536
  toggleStyle,
3524
4537
  typewriter,
3525
4538
  useActionHandler,
3526
4539
  useComments,
3527
4540
  useEditorView,
4541
+ useFormattingState,
4542
+ useInMemoryTextModel,
3528
4543
  useTextEditor,
3529
4544
  useTextModel,
3530
4545
  useToolbarContext