@dxos/react-ui-editor 0.3.11-main.5cbcf4e → 0.3.11-main.5e05862

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.
@@ -8,10 +8,10 @@ import { tags as tags2 } from "@lezer/highlight";
8
8
 
9
9
  // packages/ui/react-ui-editor/src/components/TextEditor/TextEditor.tsx
10
10
  import { EditorState as EditorState2 } from "@codemirror/state";
11
- import { EditorView as EditorView13 } from "@codemirror/view";
11
+ import { EditorView as EditorView14 } from "@codemirror/view";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- 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";
15
15
  import { log as log2 } from "@dxos/log";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
17
  import { focusRing, mx as mx3 } from "@dxos/react-ui-theme";
@@ -1833,57 +1833,279 @@ var code2 = () => {
1833
1833
  // packages/ui/react-ui-editor/src/extensions/markdown/formatting.ts
1834
1834
  import { snippet } from "@codemirror/autocomplete";
1835
1835
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1836
- import { RangeSetBuilder as RangeSetBuilder2 } from "@codemirror/state";
1837
- import { Decoration as Decoration4, keymap as keymap5, ViewPlugin as ViewPlugin4 } from "@codemirror/view";
1838
- var setHeading = (level) => (view) => {
1839
- 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;
1840
1855
  const changes = [];
1856
+ let prevBlock = -1;
1841
1857
  for (const range of ranges) {
1842
- const { number } = doc.lineAt(range.anchor);
1843
- const { from, to } = doc.line(number);
1844
- const line = doc.sliceString(from, to);
1845
- const [_, marks, spaces] = line.match(/(#+)(\s+)/) ?? [];
1846
- const current = marks?.length ?? 0;
1847
- if (level !== current) {
1848
- changes.push({
1849
- from,
1850
- to: from + current + (spaces?.length ?? 0),
1851
- insert: "#".repeat(level) + (level > 0 ? " " : "")
1852
- });
1853
- }
1854
- }
1855
- if (changes.length) {
1856
- view.dispatch({
1857
- 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
+ }
1858
1908
  });
1859
1909
  }
1910
+ if (!changes.length) {
1911
+ return false;
1912
+ }
1913
+ dispatch(state.update({
1914
+ changes,
1915
+ userEvent: "format.setHeading",
1916
+ scrollIntoView: true
1917
+ }));
1860
1918
  return true;
1861
1919
  };
1862
- var toggleStyle = (mark2) => (view) => {
1863
- const { ranges } = view.state.selection;
1864
- for (const range of ranges) {
1865
- if (range.from === range.to) {
1866
- 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
+ }
1867
1946
  }
1868
- view.dispatch({
1869
- changes: [
1870
- {
1871
- from: range.from,
1872
- insert: mark2
1873
- },
1874
- {
1875
- from: range.to,
1876
- 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
+ }
1877
2018
  }
1878
- ]
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
+ }
1879
2063
  });
1880
- }
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
+ }));
1881
2074
  return true;
1882
2075
  };
2076
+ var blockContentStart = (node) => {
2077
+ const atx = /^ATXHeading(\d)/.exec(node.name);
2078
+ if (atx) {
2079
+ return Math.min(node.to, node.from + +atx[1] + 1);
2080
+ }
2081
+ return node.from;
2082
+ };
2083
+ var blockContentEnd = (node, doc) => {
2084
+ const setext = /^SetextHeading(\d)/.exec(node.name);
2085
+ const lastLine = doc.lineAt(node.to);
2086
+ if (setext || /^[\s>]*$/.exec(lastLine.text)) {
2087
+ return lastLine.from - 1;
2088
+ }
2089
+ return node.to;
2090
+ };
2091
+ var inlineMarkerText = (type) => type === 0 ? "**" : type === 2 ? "~~" : type === 1 ? "*" : "`";
2092
+ var skipSpaces = (pos, doc, dir, limit) => {
2093
+ const line = doc.lineAt(pos);
2094
+ while (pos !== limit && line.text[pos - line.from - (dir < 0 ? 1 : 0)] === " ") {
2095
+ pos += dir;
2096
+ }
2097
+ return pos;
2098
+ };
2099
+ var addStyle = (style) => setStyle(style, true);
2100
+ var removeStyle = (style) => setStyle(style, false);
2101
+ var toggleStyle = (style) => (arg) => {
2102
+ const form = getFormatting(arg.state);
2103
+ return setStyle(style, style === 0 ? !form.strong : style === 1 ? !form.emphasis : style === 2 ? !form.strikethrough : !form.code)(arg);
2104
+ };
1883
2105
  var snippets = {
1884
2106
  codeblock: snippet([
1885
2107
  "```#{lang}",
1886
- " #{}",
2108
+ "#{}",
1887
2109
  "```"
1888
2110
  ].join("\n")),
1889
2111
  table: snippet([
@@ -1893,29 +2115,537 @@ var snippets = {
1893
2115
  "| #{val3} | #{val4} |"
1894
2116
  ].join("\n"))
1895
2117
  };
1896
- var insertCodeblock = (view) => {
1897
- const { selection: { main }, doc } = view.state;
1898
- const { number } = doc.lineAt(main.anchor);
1899
- const { from } = doc.line(number);
1900
- snippets.codeblock(view, null, from, from);
1901
- };
1902
2118
  var insertTable = (view) => {
1903
2119
  const { selection: { main }, doc } = view.state;
1904
2120
  const { number } = doc.lineAt(main.anchor);
1905
2121
  const { from } = doc.line(number);
1906
2122
  snippets.table(view, null, from, from);
1907
2123
  };
1908
- var toggleBold = toggleStyle("**");
1909
- var toggleItalic = toggleStyle("_");
1910
- var toggleStrikethrough = toggleStyle("~~");
1911
- var toggleList = (view) => {
2124
+ var toggleStrong = toggleStyle(0);
2125
+ var toggleEmphasis = toggleStyle(1);
2126
+ var toggleStrikethrough = toggleStyle(2);
2127
+ var toggleInlineCode = toggleStyle(3);
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);
1912
2642
  };
1913
2643
  var formatting = (options = {}) => {
1914
2644
  return [
1915
2645
  keymap5.of([
1916
2646
  {
1917
2647
  key: "meta-b",
1918
- run: toggleBold
2648
+ run: toggleStrong
1919
2649
  }
1920
2650
  ]),
1921
2651
  styling()
@@ -1969,6 +2699,206 @@ var styling = () => {
1969
2699
  })
1970
2700
  ];
1971
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
+ };
1972
2902
 
1973
2903
  // packages/ui/react-ui-editor/src/extensions/markdown/heading.ts
1974
2904
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
@@ -2021,8 +2951,8 @@ var heading2 = () => {
2021
2951
  // packages/ui/react-ui-editor/src/extensions/markdown/hr.ts
2022
2952
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
2023
2953
  import { RangeSetBuilder as RangeSetBuilder4 } from "@codemirror/state";
2024
- import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
2025
- 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({
2026
2956
  "& .cm-hr": {
2027
2957
  // Note that block-level decorations should not have vertical margins,
2028
2958
  borderTop: `1px solid ${getToken("extend.colors.neutral.200")}`
@@ -2076,7 +3006,7 @@ var hr = () => {
2076
3006
  // packages/ui/react-ui-editor/src/extensions/markdown/image.ts
2077
3007
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
2078
3008
  import { StateField as StateField3 } from "@codemirror/state";
2079
- 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";
2080
3010
  var image = (options = {}) => {
2081
3011
  return StateField3.define({
2082
3012
  create: (state) => {
@@ -2103,7 +3033,7 @@ var image = (options = {}) => {
2103
3033
  add: buildDecorations4(from, to, tr.state)
2104
3034
  });
2105
3035
  },
2106
- provide: (field) => EditorView10.decorations.from(field)
3036
+ provide: (field) => EditorView11.decorations.from(field)
2107
3037
  });
2108
3038
  };
2109
3039
  var buildDecorations4 = (from, to, state) => {
@@ -2285,12 +3215,12 @@ var buildDecorations5 = (view, options) => {
2285
3215
  // packages/ui/react-ui-editor/src/extensions/markdown/table.ts
2286
3216
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
2287
3217
  import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField4 } from "@codemirror/state";
2288
- 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";
2289
3219
  var table = (options = {}) => {
2290
3220
  return StateField4.define({
2291
3221
  create: (state) => update(state, options),
2292
3222
  update: (_, tr) => update(tr.state, options),
2293
- provide: (field) => EditorView11.decorations.from(field)
3223
+ provide: (field) => EditorView12.decorations.from(field)
2294
3224
  });
2295
3225
  };
2296
3226
  var update = (state, options) => {
@@ -2383,8 +3313,8 @@ var TableWidget = class extends WidgetType6 {
2383
3313
  // packages/ui/react-ui-editor/src/extensions/markdown/tasklist.ts
2384
3314
  import { syntaxTree as syntaxTree8 } from "@codemirror/language";
2385
3315
  import { RangeSetBuilder as RangeSetBuilder7 } from "@codemirror/state";
2386
- import { EditorView as EditorView12, Decoration as Decoration10, WidgetType as WidgetType7, ViewPlugin as ViewPlugin8 } from "@codemirror/view";
2387
- 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({
2388
3318
  "& .cm-task": {
2389
3319
  color: getToken("extend.colors.blue.500")
2390
3320
  },
@@ -2817,7 +3747,7 @@ var EditorModes = [
2817
3747
  var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, scrollTo, selection, editorMode, theme, slots = defaultSlots, extensions = [], debug }, forwardedRef) => {
2818
3748
  const { themeMode } = useThemeContext();
2819
3749
  const rootRef = useRef(null);
2820
- const [view, setView] = useState(null);
3750
+ const [view, setView] = useState2(null);
2821
3751
  useImperativeHandle(forwardedRef, () => view, [
2822
3752
  view
2823
3753
  ]);
@@ -2846,7 +3776,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2846
3776
  selection,
2847
3777
  extensions: [
2848
3778
  // TODO(burdon): Doesn't catch errors in keymap functions.
2849
- EditorView13.exceptionSink.of((err) => {
3779
+ EditorView14.exceptionSink.of((err) => {
2850
3780
  log2.catch(err, void 0, {
2851
3781
  F: __dxlog_file3,
2852
3782
  L: 134,
@@ -2856,17 +3786,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2856
3786
  }),
2857
3787
  // Theme.
2858
3788
  // TODO(burdon): Make configurable.
2859
- EditorView13.baseTheme(defaultTheme),
2860
- EditorView13.theme(theme ?? {}),
2861
- EditorView13.darkTheme.of(themeMode === "dark"),
2862
- EditorView13.editorAttributes.of({
3789
+ EditorView14.baseTheme(defaultTheme),
3790
+ EditorView14.theme(theme ?? {}),
3791
+ EditorView14.darkTheme.of(themeMode === "dark"),
3792
+ EditorView14.editorAttributes.of({
2863
3793
  class: slots.editor?.className ?? ""
2864
3794
  }),
2865
- EditorView13.contentAttributes.of({
3795
+ EditorView14.contentAttributes.of({
2866
3796
  class: slots.content?.className ?? ""
2867
3797
  }),
2868
3798
  // State.
2869
- EditorView13.editable.of(!readonly),
3799
+ EditorView14.editable.of(!readonly),
2870
3800
  EditorState2.readOnly.of(!!readonly),
2871
3801
  // Storage and replication.
2872
3802
  // NOTE: This must come before user extensions.
@@ -2877,7 +3807,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, autoFocus, s
2877
3807
  ...extensions
2878
3808
  ].filter(isNotFalsy3)
2879
3809
  });
2880
- const newView = new EditorView13({
3810
+ const newView = new EditorView14({
2881
3811
  state,
2882
3812
  parent: rootRef.current,
2883
3813
  scrollTo,
@@ -2988,11 +3918,12 @@ import { At, CaretRight, ChatText, Code, Image, Link, ListBullets, ListChecks, L
2988
3918
  import { createContext } from "@radix-ui/react-context";
2989
3919
  import React2 from "react";
2990
3920
  import { Button, DensityProvider, Select } from "@dxos/react-ui";
2991
- import { getSize } from "@dxos/react-ui-theme";
3921
+ import { getSize, mx as mx4 } from "@dxos/react-ui-theme";
2992
3922
  var [ToolbarContextProvider, useToolbarContext] = createContext("Toolbar");
2993
- var ToolbarRoot = ({ children, onAction }) => {
3923
+ var ToolbarRoot = ({ children, onAction, state }) => {
2994
3924
  return /* @__PURE__ */ React2.createElement(ToolbarContextProvider, {
2995
- onAction
3925
+ onAction,
3926
+ state
2996
3927
  }, /* @__PURE__ */ React2.createElement(DensityProvider, {
2997
3928
  density: "fine"
2998
3929
  }, /* @__PURE__ */ React2.createElement("div", {
@@ -3000,19 +3931,23 @@ var ToolbarRoot = ({ children, onAction }) => {
3000
3931
  className: "flex w-full shrink-0 p-2 gap-4 items-center whitespace-nowrap overflow-hidden"
3001
3932
  }, children)));
3002
3933
  };
3003
- var ToolbarButton = ({ Icon, onClick, title }) => {
3004
- const { onAction } = useToolbarContext("ToolbarButton");
3005
- const handleClick = () => {
3006
- const action = onClick();
3934
+ var ToolbarButton = ({ Icon, onClick, title, getState, disable }) => {
3935
+ const { onAction, state } = useToolbarContext("ToolbarButton");
3936
+ const handleClick = (event) => {
3937
+ const action = onClick(state);
3007
3938
  if (action) {
3008
3939
  onAction?.(action);
3940
+ event.preventDefault();
3009
3941
  }
3010
3942
  };
3943
+ const active = getState && state ? getState(state) : false;
3944
+ const disabled = disable && state ? disable(state) : false;
3011
3945
  return /* @__PURE__ */ React2.createElement(Button, {
3012
3946
  variant: "ghost",
3013
- classNames: "p-2",
3014
- onClick: handleClick,
3015
- title
3947
+ classNames: mx4("p-2", active && "ring"),
3948
+ onMouseDown: handleClick,
3949
+ title,
3950
+ disabled
3016
3951
  }, /* @__PURE__ */ React2.createElement(Icon, {
3017
3952
  className: getSize(5)
3018
3953
  }));
@@ -3021,12 +3956,16 @@ var ToolbarSeparator = () => /* @__PURE__ */ React2.createElement("div", {
3021
3956
  className: "grow"
3022
3957
  });
3023
3958
  var MarkdownHeading = () => {
3024
- 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;
3025
3963
  return /* @__PURE__ */ React2.createElement(Select.Root, {
3026
- defaultValue: "0",
3027
- onValueChange: (value) => onAction?.({
3964
+ disabled: value === null,
3965
+ value: value ?? "0",
3966
+ onValueChange: (value2) => onAction?.({
3028
3967
  type: "heading",
3029
- data: parseInt(value)
3968
+ data: parseInt(value2)
3030
3969
  })
3031
3970
  }, /* @__PURE__ */ React2.createElement(Select.TriggerButton, {
3032
3971
  classNames: "w-[8rem]"
@@ -3048,58 +3987,86 @@ var MarkdownStyles = () => /* @__PURE__ */ React2.createElement("div", {
3048
3987
  role: "none"
3049
3988
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3050
3989
  Icon: TextB,
3051
- title: "Bold",
3052
- onClick: () => ({
3053
- type: "bold"
3054
- })
3990
+ title: "String",
3991
+ onClick: (s) => ({
3992
+ type: "strong",
3993
+ data: s ? !s.strong : null
3994
+ }),
3995
+ getState: (s) => s.strong,
3996
+ disable: (s) => s.blockType === "codeblock"
3055
3997
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3056
3998
  Icon: TextItalic,
3057
- title: "Italic",
3058
- onClick: () => ({
3059
- type: "italic"
3060
- })
3999
+ title: "Emphasis",
4000
+ onClick: (s) => ({
4001
+ type: "emphasis",
4002
+ data: s ? !s.emphasis : null
4003
+ }),
4004
+ getState: (s) => s.emphasis,
4005
+ disable: (s) => s.blockType === "codeblock"
3061
4006
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3062
4007
  Icon: TextStrikethrough,
3063
4008
  title: "Strike-through",
3064
- onClick: () => ({
3065
- type: "strikethrough"
3066
- })
4009
+ onClick: (s) => ({
4010
+ type: "strikethrough",
4011
+ data: s ? !s.strikethrough : null
4012
+ }),
4013
+ getState: (s) => s.strikethrough,
4014
+ disable: (s) => s.blockType === "codeblock"
4015
+ }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
4016
+ Icon: Code,
4017
+ title: "Inline code",
4018
+ onClick: (s) => ({
4019
+ type: "code",
4020
+ data: s ? !s.code : null
4021
+ }),
4022
+ getState: (s) => s.code,
4023
+ disable: (s) => s.blockType === "codeblock"
3067
4024
  }));
3068
4025
  var MarkdownLists = () => /* @__PURE__ */ React2.createElement("div", {
3069
4026
  role: "none"
3070
4027
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3071
4028
  Icon: ListBullets,
3072
4029
  title: "Bullet list",
3073
- onClick: () => ({
3074
- type: "list"
3075
- })
4030
+ onClick: (s) => ({
4031
+ type: "list-bullet",
4032
+ data: s ? s.listStyle !== "bullet" : null
4033
+ }),
4034
+ getState: (s) => s.listStyle === "bullet"
3076
4035
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3077
4036
  Icon: ListNumbers,
3078
4037
  title: "Numbered list",
3079
- onClick: () => ({
3080
- type: "list-ordered"
3081
- })
4038
+ onClick: (s) => ({
4039
+ type: "list-ordered",
4040
+ data: s ? s.listStyle !== "ordered" : null
4041
+ }),
4042
+ getState: (s) => s.listStyle === "ordered"
3082
4043
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3083
4044
  Icon: ListChecks,
3084
4045
  title: "Task list",
3085
- onClick: () => ({
3086
- type: "list-tasks"
3087
- })
4046
+ onClick: (s) => ({
4047
+ type: "list-tasks",
4048
+ data: s ? s.listStyle !== "task" : null
4049
+ }),
4050
+ getState: (s) => s.listStyle === "task"
3088
4051
  }));
3089
4052
  var MarkdownBlocks = () => /* @__PURE__ */ React2.createElement("div", {
3090
4053
  role: "none"
3091
4054
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3092
4055
  Icon: CaretRight,
3093
- title: "Block quite",
3094
- onClick: () => ({
3095
- type: "blockquote"
3096
- })
4056
+ title: "Block quote",
4057
+ onClick: (s) => ({
4058
+ type: "blockquote",
4059
+ data: s ? !s.blockquote : null
4060
+ }),
4061
+ getState: (s) => s.blockquote
3097
4062
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3098
4063
  Icon: Code,
3099
4064
  title: "Code block",
3100
- onClick: () => ({
3101
- type: "codeblock"
3102
- })
4065
+ onClick: (s) => ({
4066
+ type: "codeblock",
4067
+ data: s ? s.blockType !== "codeblock" : null
4068
+ }),
4069
+ getState: (s) => s.blockType === "codeblock"
3103
4070
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3104
4071
  Icon: Table2,
3105
4072
  title: "Table",
@@ -3112,9 +4079,11 @@ var MarkdownLinks = () => /* @__PURE__ */ React2.createElement("div", {
3112
4079
  }, /* @__PURE__ */ React2.createElement(ToolbarButton, {
3113
4080
  Icon: Link,
3114
4081
  title: "Link",
3115
- onClick: () => ({
3116
- type: "link"
3117
- })
4082
+ onClick: (s) => ({
4083
+ type: "link",
4084
+ data: s ? !s.link : null
4085
+ }),
4086
+ getState: (s) => s.link
3118
4087
  }), /* @__PURE__ */ React2.createElement(ToolbarButton, {
3119
4088
  Icon: At,
3120
4089
  title: "Mention",
@@ -3261,24 +4230,32 @@ var useActionHandler = (view) => {
3261
4230
  if (!view) {
3262
4231
  return;
3263
4232
  }
4233
+ let inlineType, listType;
3264
4234
  switch (action.type) {
3265
4235
  case "heading":
3266
4236
  setHeading(parseInt(action.data))(view);
3267
4237
  break;
3268
- case "bold":
3269
- toggleBold(view);
4238
+ case "strong":
4239
+ case "emphasis":
4240
+ case "strikethrough":
4241
+ case "code":
4242
+ inlineType = action.type === "strong" ? Inline.Strong : action.type === "emphasis" ? Inline.Emphasis : action.type === "strikethrough" ? Inline.Strikethrough : Inline.Code;
4243
+ (typeof action.data === "boolean" ? setStyle(inlineType, action.data) : toggleStyle(inlineType))(view);
3270
4244
  break;
3271
- case "italic":
3272
- toggleItalic(view);
4245
+ case "link":
4246
+ (action.data === false ? removeLink : addLink)(view);
3273
4247
  break;
3274
- case "strikethrough":
3275
- toggleStrikethrough(view);
4248
+ case "list-ordered":
4249
+ case "list-bullet":
4250
+ case "list-tasks":
4251
+ listType = action.type === "list-ordered" ? List.Ordered : action.type === "list-bullet" ? List.Bullet : List.Task;
4252
+ (action.data === false ? removeList(listType) : action.data === true ? addList(listType) : toggleList(listType))(view);
3276
4253
  break;
3277
- case "list":
3278
- toggleList(view);
4254
+ case "blockquote":
4255
+ (action.data === false ? removeBlockquote : action.data === true ? addBlockquote : toggleBlockquote)(view);
3279
4256
  break;
3280
4257
  case "codeblock":
3281
- insertCodeblock(view);
4258
+ (action.data === false ? removeCodeblock : addCodeblock)(view);
3282
4259
  break;
3283
4260
  case "table":
3284
4261
  insertTable(view);
@@ -3288,18 +4265,17 @@ var useActionHandler = (view) => {
3288
4265
  break;
3289
4266
  }
3290
4267
  setTimeout(() => {
3291
- view.focus();
3292
- view.dispatch({
3293
- selection: view.state.selection
3294
- });
4268
+ if (!view.hasFocus) {
4269
+ view.focus();
4270
+ }
3295
4271
  });
3296
4272
  };
3297
4273
  };
3298
4274
 
3299
4275
  // packages/ui/react-ui-editor/src/hooks/useEditorView.ts
3300
- import { useState as useState2 } from "react";
4276
+ import { useState as useState3 } from "react";
3301
4277
  var useEditorView = () => {
3302
- const [view, setView] = useState2(null);
4278
+ const [view, setView] = useState3(null);
3303
4279
  return [
3304
4280
  (ref) => {
3305
4281
  setView(ref);
@@ -3309,15 +4285,15 @@ var useEditorView = () => {
3309
4285
  };
3310
4286
 
3311
4287
  // packages/ui/react-ui-editor/src/hooks/useTextEditor.ts
3312
- import { EditorSelection, EditorState as EditorState3 } from "@codemirror/state";
3313
- import { EditorView as EditorView14 } from "@codemirror/view";
3314
- import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
4288
+ import { EditorSelection as EditorSelection2, EditorState as EditorState3 } from "@codemirror/state";
4289
+ import { EditorView as EditorView15 } from "@codemirror/view";
4290
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
3315
4291
  import { log as log4 } from "@dxos/log";
3316
4292
  import { isNotFalsy as isNotFalsy4 } from "@dxos/util";
3317
4293
  var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
3318
4294
  var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions } = {}) => {
3319
4295
  const onUpdate = useRef2();
3320
- const [view, setView] = useState3();
4296
+ const [view, setView] = useState4();
3321
4297
  const parentRef = useRef2(null);
3322
4298
  useEffect3(() => {
3323
4299
  if (parentRef.current) {
@@ -3326,7 +4302,7 @@ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions }
3326
4302
  selection,
3327
4303
  extensions: [
3328
4304
  // TODO(burdon): Doesn't catch errors in keymap functions.
3329
- EditorView14.exceptionSink.of((err) => {
4305
+ EditorView15.exceptionSink.of((err) => {
3330
4306
  log4.catch(err, void 0, {
3331
4307
  F: __dxlog_file5,
3332
4308
  L: 57,
@@ -3335,12 +4311,12 @@ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions }
3335
4311
  });
3336
4312
  }),
3337
4313
  extensions,
3338
- EditorView14.updateListener.of(() => {
4314
+ EditorView15.updateListener.of(() => {
3339
4315
  onUpdate.current?.();
3340
4316
  })
3341
4317
  ].filter(isNotFalsy4)
3342
4318
  });
3343
- const view2 = new EditorView14({
4319
+ const view2 = new EditorView15({
3344
4320
  parent: parentRef.current,
3345
4321
  scrollTo,
3346
4322
  state,
@@ -3364,7 +4340,7 @@ var useTextEditor = ({ autoFocus, scrollTo, debug, doc, selection, extensions }
3364
4340
  useEffect3(() => {
3365
4341
  if (view) {
3366
4342
  if (!selection && !view.state.selection.main.anchor) {
3367
- selection = EditorSelection.single(view.state.doc.line(1).to);
4343
+ selection = EditorSelection2.single(view.state.doc.line(1).to);
3368
4344
  }
3369
4345
  if (selection || scrollTo) {
3370
4346
  onUpdate.current = () => {
@@ -3397,19 +4373,19 @@ var createDataExtensions = ({ readonly = false } = {}) => {
3397
4373
  return [
3398
4374
  //
3399
4375
  EditorState3.readOnly.of(readonly),
3400
- EditorView14.editable.of(!readonly)
4376
+ EditorView15.editable.of(!readonly)
3401
4377
  ];
3402
4378
  };
3403
4379
  var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3404
4380
  return [
3405
4381
  //
3406
- EditorView14.baseTheme(defaultTheme),
3407
- theme && EditorView14.theme(theme),
3408
- EditorView14.darkTheme.of(themeMode === "dark"),
3409
- EditorView14.editorAttributes.of({
4382
+ EditorView15.baseTheme(defaultTheme),
4383
+ theme && EditorView15.theme(theme),
4384
+ EditorView15.darkTheme.of(themeMode === "dark"),
4385
+ EditorView15.editorAttributes.of({
3410
4386
  class: slots?.editor?.className ?? ""
3411
4387
  }),
3412
- EditorView14.contentAttributes.of({
4388
+ EditorView15.contentAttributes.of({
3413
4389
  class: slots?.content?.className ?? ""
3414
4390
  })
3415
4391
  ].filter(isNotFalsy4);
@@ -3417,7 +4393,7 @@ var createThemeExtensions = ({ theme, themeMode, slots } = {}) => {
3417
4393
 
3418
4394
  // packages/ui/react-ui-editor/src/hooks/useTextModel.ts
3419
4395
  import get4 from "lodash.get";
3420
- import { useEffect as useEffect4, useState as useState4 } from "react";
4396
+ import { useEffect as useEffect4, useState as useState5 } from "react";
3421
4397
  import { generateName } from "@dxos/display-name";
3422
4398
  import { getRawDoc } from "@dxos/echo-schema";
3423
4399
  import { invariant as invariant3 } from "@dxos/invariant";
@@ -3425,7 +4401,7 @@ import { isAutomergeObject } from "@dxos/react-client/echo";
3425
4401
  var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextModel.ts";
3426
4402
  var useTextModel = (props) => {
3427
4403
  const { identity, space, text } = props;
3428
- const [model, setModel] = useState4();
4404
+ const [model, setModel] = useState5();
3429
4405
  useEffect4(() => setModel(createModel(props)), [
3430
4406
  identity,
3431
4407
  space,
@@ -3434,7 +4410,7 @@ var useTextModel = (props) => {
3434
4410
  return model;
3435
4411
  };
3436
4412
  var useInMemoryTextModel = ({ id, defaultContent }) => {
3437
- const [content, setContent] = useState4(defaultContent ?? "");
4413
+ const [content, setContent] = useState5(defaultContent ?? "");
3438
4414
  return {
3439
4415
  id,
3440
4416
  content,
@@ -3494,6 +4470,8 @@ export {
3494
4470
  Cursor,
3495
4471
  Doc,
3496
4472
  EditorModes,
4473
+ Inline,
4474
+ List,
3497
4475
  MarkdownEditor,
3498
4476
  RemoteSelectionsDecorator,
3499
4477
  SpaceAwarenessProvider,
@@ -3502,6 +4480,11 @@ export {
3502
4480
  Toolbar,
3503
4481
  YText,
3504
4482
  YXmlFragment,
4483
+ addBlockquote,
4484
+ addCodeblock,
4485
+ addLink,
4486
+ addList,
4487
+ addStyle,
3505
4488
  autocomplete,
3506
4489
  automerge,
3507
4490
  awareness,
@@ -3511,6 +4494,7 @@ export {
3511
4494
  code2 as code,
3512
4495
  codeTheme,
3513
4496
  comments,
4497
+ compareFormatting,
3514
4498
  createBasicBundle,
3515
4499
  createComment,
3516
4500
  createDataExtensions,
@@ -3523,11 +4507,11 @@ export {
3523
4507
  defaultTheme,
3524
4508
  focusComment,
3525
4509
  formatting,
4510
+ getFormatting,
3526
4511
  getToken,
3527
4512
  heading2 as heading,
3528
4513
  hr,
3529
4514
  image,
3530
- insertCodeblock,
3531
4515
  insertTable,
3532
4516
  keymap7 as keymap,
3533
4517
  link,
@@ -3540,22 +4524,33 @@ export {
3540
4524
  mention,
3541
4525
  modelState,
3542
4526
  outliner,
4527
+ removeBlockquote,
4528
+ removeCodeblock,
4529
+ removeLink,
4530
+ removeList,
4531
+ removeStyle,
4532
+ setBlockquote,
3543
4533
  setComments,
3544
4534
  setHeading,
3545
4535
  setSelection,
4536
+ setStyle,
3546
4537
  table,
3547
4538
  tags2 as tags,
3548
4539
  tasklist,
3549
4540
  textTheme,
3550
- toggleBold,
3551
- toggleItalic,
4541
+ toggleBlockquote,
4542
+ toggleCodeblock,
4543
+ toggleEmphasis,
4544
+ toggleInlineCode,
3552
4545
  toggleList,
3553
4546
  toggleStrikethrough,
4547
+ toggleStrong,
3554
4548
  toggleStyle,
3555
4549
  typewriter,
3556
4550
  useActionHandler,
3557
4551
  useComments,
3558
4552
  useEditorView,
4553
+ useFormattingState,
3559
4554
  useInMemoryTextModel,
3560
4555
  useTextEditor,
3561
4556
  useTextModel,