@drghaliasri/butex 5.4.5 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1133,7 +1133,7 @@ function parseReferences(json) {
1133
1133
  }
1134
1134
  return references;
1135
1135
  }
1136
- function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = []) {
1136
+ function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = [], formats = []) {
1137
1137
  const mode = options.mode ?? "english";
1138
1138
  const spans = detectInlineSpans2(value);
1139
1139
  const mathSpans = spans.filter((span) => span.kind === "math");
@@ -1145,7 +1145,7 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1145
1145
  }
1146
1146
  for (const span of spans) {
1147
1147
  if (span.start > index) {
1148
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index, span.start) });
1148
+ pushFormattedTextTokens(tokens, value.slice(index, span.start), index, formats);
1149
1149
  }
1150
1150
  if (span.kind === "cite") {
1151
1151
  tokens.push({
@@ -1201,9 +1201,81 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1201
1201
  index = span.end;
1202
1202
  }
1203
1203
  if (index < value.length || tokens.length === 0) {
1204
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index) });
1204
+ pushFormattedTextTokens(tokens, value.slice(index), index, formats);
1205
+ }
1206
+ return { id: document2Id("field"), tokens: compactTextTokens(tokens) };
1207
+ }
1208
+ function styleFromFormat(format) {
1209
+ const style = {};
1210
+ if (format.bold === true) {
1211
+ style.bold = true;
1212
+ }
1213
+ if (format.italic === true) {
1214
+ style.italic = true;
1215
+ }
1216
+ if (format.underline === true) {
1217
+ style.underline = true;
1218
+ }
1219
+ return style.bold || style.italic || style.underline ? style : null;
1220
+ }
1221
+ function mergeTextStyle(base, added) {
1222
+ return {
1223
+ ...base?.bold ? { bold: true } : {},
1224
+ ...base?.italic ? { italic: true } : {},
1225
+ ...base?.underline ? { underline: true } : {},
1226
+ ...added.bold ? { bold: true } : {},
1227
+ ...added.italic ? { italic: true } : {},
1228
+ ...added.underline ? { underline: true } : {}
1229
+ };
1230
+ }
1231
+ function textStylesEqual(a, b) {
1232
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
1233
+ }
1234
+ function compactTextTokens(tokens) {
1235
+ const compacted = [];
1236
+ for (const token of tokens) {
1237
+ const previous = compacted[compacted.length - 1];
1238
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual(previous.style, token.style)) {
1239
+ previous.text += token.text;
1240
+ } else {
1241
+ compacted.push(token);
1242
+ }
1243
+ }
1244
+ return compacted;
1245
+ }
1246
+ function styleKey(style) {
1247
+ return `${style?.bold ? "b" : ""}${style?.italic ? "i" : ""}${style?.underline ? "u" : ""}`;
1248
+ }
1249
+ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1250
+ if (text.length === 0) {
1251
+ tokens.push({ id: document2Id("text"), kind: "text", text });
1252
+ return;
1253
+ }
1254
+ const styles = Array.from({ length: text.length });
1255
+ for (const format of formats) {
1256
+ const style = styleFromFormat(format);
1257
+ if (!style || !Number.isFinite(format.start) || !Number.isFinite(format.end)) {
1258
+ continue;
1259
+ }
1260
+ const start = Math.max(0, Math.min(text.length, Math.trunc(format.start) - sourceStart));
1261
+ const end = Math.max(0, Math.min(text.length, Math.trunc(format.end) - sourceStart));
1262
+ if (end <= start) {
1263
+ continue;
1264
+ }
1265
+ for (let index = start; index < end; index += 1) {
1266
+ styles[index] = mergeTextStyle(styles[index], style);
1267
+ }
1268
+ }
1269
+ let chunkStart = 0;
1270
+ for (let index = 1; index <= text.length; index += 1) {
1271
+ if (index < text.length && styleKey(styles[index]) === styleKey(styles[chunkStart])) {
1272
+ continue;
1273
+ }
1274
+ const chunk = text.slice(chunkStart, index);
1275
+ const style = styles[chunkStart];
1276
+ tokens.push({ id: document2Id("text"), kind: "text", text: chunk, ...style ? { style } : {} });
1277
+ chunkStart = index;
1205
1278
  }
1206
- return { id: document2Id("field"), tokens };
1207
1279
  }
1208
1280
  function closingForList(command) {
1209
1281
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
@@ -1214,7 +1286,7 @@ function parseTextBlock(json, options, path, diagnostics) {
1214
1286
  id: document2Id("block"),
1215
1287
  kind: "textBlock",
1216
1288
  command: json.command,
1217
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1289
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1218
1290
  ...json.centered === true ? { centered: true } : {}
1219
1291
  };
1220
1292
  }
@@ -1223,7 +1295,7 @@ function parseListItem(json, options, path, diagnostics) {
1223
1295
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1224
1296
  return {
1225
1297
  id: document2Id("item"),
1226
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1298
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1227
1299
  blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1228
1300
  };
1229
1301
  }
@@ -1256,7 +1328,8 @@ function parseTableBlock(json, options, path, diagnostics) {
1256
1328
  const spanCount = detectMathSpans2(value).length;
1257
1329
  const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
1258
1330
  mathObjectIndex += spanCount;
1259
- return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics);
1331
+ const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
1332
+ return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats);
1260
1333
  });
1261
1334
  });
1262
1335
  if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
@@ -8081,6 +8154,79 @@ function normalizeFieldTokens(tokens) {
8081
8154
  }
8082
8155
  return tokens;
8083
8156
  }
8157
+ function textStylesEqual2(a, b) {
8158
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
8159
+ }
8160
+ function compactAdjacentTextTokens(tokens) {
8161
+ const compacted = [];
8162
+ for (const token of tokens) {
8163
+ const previous = compacted[compacted.length - 1];
8164
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual2(previous.style, token.style)) {
8165
+ previous.text += token.text;
8166
+ } else {
8167
+ compacted.push(token);
8168
+ }
8169
+ }
8170
+ return normalizeFieldTokens(compacted);
8171
+ }
8172
+ function withoutEmptyStyle(style) {
8173
+ const next = {};
8174
+ if (style.bold) {
8175
+ next.bold = true;
8176
+ }
8177
+ if (style.italic) {
8178
+ next.italic = true;
8179
+ }
8180
+ if (style.underline) {
8181
+ next.underline = true;
8182
+ }
8183
+ return next.bold || next.italic || next.underline ? next : void 0;
8184
+ }
8185
+ function textTokenPart(text, style, id = document2Id("text")) {
8186
+ return { id, kind: "text", text, ...style ? { style } : {} };
8187
+ }
8188
+ function toggleTextTokenStyle(document2, fieldId, tokenId, selectionStart, selectionEnd, styleName) {
8189
+ if (selectionEnd <= selectionStart) {
8190
+ return document2;
8191
+ }
8192
+ const next = cloneDocument(document2);
8193
+ visitFields(next.blocks, (field) => {
8194
+ if (field.id !== fieldId) {
8195
+ return false;
8196
+ }
8197
+ const tokenIndex = field.tokens.findIndex((entry) => entry.id === tokenId && entry.kind === "text");
8198
+ if (tokenIndex < 0) {
8199
+ return true;
8200
+ }
8201
+ const token = field.tokens[tokenIndex];
8202
+ const start = Math.max(0, Math.min(token.text.length, selectionStart));
8203
+ const end = Math.max(0, Math.min(token.text.length, selectionEnd));
8204
+ if (end <= start) {
8205
+ return true;
8206
+ }
8207
+ const selectedStyle = token.style ?? {};
8208
+ const enabled = selectedStyle[styleName] === true;
8209
+ const nextStyle = withoutEmptyStyle({ ...selectedStyle, [styleName]: enabled ? void 0 : true });
8210
+ const parts = [];
8211
+ const before = token.text.slice(0, start);
8212
+ const selected = token.text.slice(start, end);
8213
+ const after = token.text.slice(end);
8214
+ if (before.length > 0) {
8215
+ parts.push(textTokenPart(before, token.style, token.id));
8216
+ }
8217
+ parts.push(textTokenPart(selected, nextStyle, before.length > 0 ? document2Id("text") : token.id));
8218
+ if (after.length > 0) {
8219
+ parts.push(textTokenPart(after, token.style));
8220
+ }
8221
+ field.tokens = compactAdjacentTextTokens([
8222
+ ...field.tokens.slice(0, tokenIndex),
8223
+ ...parts,
8224
+ ...field.tokens.slice(tokenIndex + 1)
8225
+ ]);
8226
+ return true;
8227
+ });
8228
+ return next;
8229
+ }
8084
8230
  function stitchTextAroundRemovedToken(tokens, index) {
8085
8231
  const before = tokens[index - 1];
8086
8232
  const after = tokens[index + 1];
@@ -8108,8 +8254,8 @@ function removeMathTokenById(document2, tokenId) {
8108
8254
  function splitTextTokenAt(token, offset) {
8109
8255
  const safeOffset = Math.max(0, Math.min(offset, token.text.length));
8110
8256
  return [
8111
- { id: token.id, kind: "text", text: token.text.slice(0, safeOffset) },
8112
- { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset) }
8257
+ { id: token.id, kind: "text", text: token.text.slice(0, safeOffset), ...token.style ? { style: token.style } : {} },
8258
+ { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset), ...token.style ? { style: token.style } : {} }
8113
8259
  ];
8114
8260
  }
8115
8261
  function insertMathTokenAtCaret(document2, fieldId, textTokenId, caretOffset, session, opening = "$", closing = "$", side = "arabic") {
@@ -8681,7 +8827,7 @@ function arabicXeLatexPreambleCmd() {
8681
8827
  \newcommand{\horzbar}{\rule[.5ex]{2.5ex}{0.5pt}}
8682
8828
 
8683
8829
 
8684
- \newcommand{\butexreflect}[1]{%\tikz[baseline=(n.base)]{\node[inner sep=0pt,outer sep=0pt,xscale=-1] (n) {#1};}%}
8830
+ \newcommand{\butexreflect}[1]{\tikz[baseline=(n.base)]{\node[inner sep=0pt,outer sep=0pt,xscale=-1](n){#1};}}
8685
8831
 
8686
8832
  \newcommand{\arabsqrt}[2]{\butexreflect{\(\sqrt[\butexreflect{\(#1\)}]{\butexreflect{\(#2\)}}\)}}
8687
8833
  \newcommand{\arabvec}[1]{\butexreflect{$\vec{\butexreflect{$#1$}}$}}
@@ -8817,9 +8963,22 @@ function mathBodyLatex(token) {
8817
8963
  const rendered = renderMathNodeLatex(token.math);
8818
8964
  return stripDisplayMathDelimiters(rendered);
8819
8965
  }
8966
+ function styledTextLatex(text, style) {
8967
+ let output = text;
8968
+ if (style?.bold) {
8969
+ output = `\\textbf{${output}}`;
8970
+ }
8971
+ if (style?.italic) {
8972
+ output = `\\textit{${output}}`;
8973
+ }
8974
+ if (style?.underline) {
8975
+ output = `\\underline{${output}}`;
8976
+ }
8977
+ return output;
8978
+ }
8820
8979
  function tokenLatex(token) {
8821
8980
  if (token.kind === "text") {
8822
- return token.text;
8981
+ return styledTextLatex(token.text, token.style);
8823
8982
  }
8824
8983
  if (token.kind === "cite") {
8825
8984
  return citeTokenLatex(token.keys);
@@ -8843,7 +9002,9 @@ function inlineFieldLatex(field) {
8843
9002
  return field.tokens.map((token) => tokenLatex(token)).join("");
8844
9003
  }
8845
9004
  function textBlockLatex(block) {
8846
- const body = `${block.command}{${inlineFieldLatex(block.field)}}`;
9005
+ const content = inlineFieldLatex(block.field);
9006
+ const body = block.command === "\\paragraph" ? `\\par
9007
+ ${content}` : `${block.command}{${content}}`;
8847
9008
  if (block.command === "\\paragraph" && block.centered) {
8848
9009
  return `\\begin{center}
8849
9010
  ${body}
@@ -9095,7 +9256,7 @@ function mathTex(token, equationSide) {
9095
9256
  function previewInlines(field, islands, output, equationSide, document2, previewOptions) {
9096
9257
  return field.tokens.map((token) => {
9097
9258
  if (token.kind === "text") {
9098
- return { kind: "text", text: token.text };
9259
+ return { kind: "text", text: token.text, ...token.style ? { style: token.style } : {} };
9099
9260
  }
9100
9261
  if (token.kind === "cite") {
9101
9262
  return {
@@ -9271,6 +9432,10 @@ var DOCUMENT2_MESSAGES = {
9271
9432
  redo: "\u0625\u0639\u0627\u062F\u0629",
9272
9433
  structure: "\u0647\u064A\u0643\u0644",
9273
9434
  content: "\u0645\u062D\u062A\u0648\u0649",
9435
+ textFormatting: "\u062A\u0646\u0633\u064A\u0642 \u0627\u0644\u0646\u0635",
9436
+ bold: "\u063A\u0627\u0645\u0642",
9437
+ italic: "\u0645\u0627\u0626\u0644",
9438
+ underline: "\u062A\u062D\u062A\u0647 \u062E\u0637",
9274
9439
  equations: "\u0645\u0639\u0627\u062F\u0644\u0627\u062A",
9275
9440
  citations: "\u0627\u0642\u062A\u0628\u0627\u0633\u0627\u062A \u0648\u0645\u0631\u0627\u062C\u0639",
9276
9441
  lists: "\u0642\u0648\u0627\u0626\u0645",
@@ -9410,6 +9575,10 @@ var DOCUMENT2_MESSAGES = {
9410
9575
  redo: "Redo",
9411
9576
  structure: "Structure",
9412
9577
  content: "Content",
9578
+ textFormatting: "Text formatting",
9579
+ bold: "Bold",
9580
+ italic: "Italic",
9581
+ underline: "Underline",
9413
9582
  equations: "Equations",
9414
9583
  citations: "Citations and references",
9415
9584
  lists: "Lists",
@@ -9753,7 +9922,7 @@ function focusArticleMetaPanel(panel) {
9753
9922
  }
9754
9923
 
9755
9924
  // src/react-document2/ButexDocumentEditor2.tsx
9756
- import { useCallback as useCallback2, useEffect as useEffect8, useMemo as useMemo2, useRef as useRef7, useState as useState12 } from "react";
9925
+ import { useCallback as useCallback2, useEffect as useEffect9, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
9757
9926
 
9758
9927
  // src/react-document2/BlockEditor.tsx
9759
9928
  import { useState as useState3 } from "react";
@@ -10448,7 +10617,9 @@ function rememberCaret(element, blockId, fieldId, tokenId, onFieldFocus) {
10448
10617
  if (!blockId || !onFieldFocus) {
10449
10618
  return;
10450
10619
  }
10451
- onFieldFocus(blockId, fieldId, tokenId, element.selectionStart ?? element.value.length);
10620
+ const selectionStart = element.selectionStart ?? element.value.length;
10621
+ const selectionEnd = element.selectionEnd ?? selectionStart;
10622
+ onFieldFocus(blockId, fieldId, tokenId, selectionEnd, selectionStart, selectionEnd);
10452
10623
  }
10453
10624
  function fitTextareaHeight(element) {
10454
10625
  if (!element) {
@@ -10582,6 +10753,9 @@ function InlineField({
10582
10753
  {
10583
10754
  className: "butex-document2-widget__inline-text",
10584
10755
  value: token.text,
10756
+ "data-bold": token.style?.bold === true ? "true" : void 0,
10757
+ "data-italic": token.style?.italic === true ? "true" : void 0,
10758
+ "data-underline": token.style?.underline === true ? "true" : void 0,
10585
10759
  dir: documentDirection,
10586
10760
  "aria-label": messages.text,
10587
10761
  rows: 1,
@@ -11300,7 +11474,7 @@ function CitePickerPopover({
11300
11474
  }
11301
11475
 
11302
11476
  // src/react-document2/DocumentInsertToolbar.tsx
11303
- import { useState as useState6 } from "react";
11477
+ import { useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
11304
11478
 
11305
11479
  // src/react-document2/TableInsertPopover.tsx
11306
11480
  import { useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
@@ -11378,8 +11552,11 @@ function DocumentInsertToolbar({
11378
11552
  selectionCount = 0,
11379
11553
  canMoveSelectionUp = false,
11380
11554
  canMoveSelectionDown = false,
11555
+ canFormatText = false,
11556
+ activeTextStyles = {},
11381
11557
  onUndo,
11382
11558
  onRedo,
11559
+ onToggleTextStyle,
11383
11560
  onMoveSelectionUp,
11384
11561
  onMoveSelectionDown,
11385
11562
  onDeleteSelection,
@@ -11403,6 +11580,56 @@ function DocumentInsertToolbar({
11403
11580
  }) {
11404
11581
  const messages = document2Messages(uiLocale);
11405
11582
  const [digitMenuOpen, setDigitMenuOpen] = useState6(false);
11583
+ const [referencesMenuOpen, setReferencesMenuOpen] = useState6(false);
11584
+ const referencesMenuRef = useRef5(null);
11585
+ const referencesTriggerRef = useRef5(null);
11586
+ useEffect5(() => {
11587
+ if (!referencesMenuOpen) {
11588
+ return;
11589
+ }
11590
+ referencesMenuRef.current?.querySelector('[role="menuitem"]')?.focus();
11591
+ const closeOnOutsideClick = (event) => {
11592
+ if (!referencesMenuRef.current?.contains(event.target)) {
11593
+ setReferencesMenuOpen(false);
11594
+ }
11595
+ };
11596
+ const closeOnEscape = (event) => {
11597
+ if (event.key === "Escape") {
11598
+ setReferencesMenuOpen(false);
11599
+ referencesTriggerRef.current?.focus();
11600
+ }
11601
+ };
11602
+ document.addEventListener("mousedown", closeOnOutsideClick);
11603
+ document.addEventListener("keydown", closeOnEscape);
11604
+ return () => {
11605
+ document.removeEventListener("mousedown", closeOnOutsideClick);
11606
+ document.removeEventListener("keydown", closeOnEscape);
11607
+ };
11608
+ }, [referencesMenuOpen]);
11609
+ function runReferenceAction(action) {
11610
+ setReferencesMenuOpen(false);
11611
+ action();
11612
+ }
11613
+ function moveReferenceMenuFocus(event) {
11614
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
11615
+ return;
11616
+ }
11617
+ const items = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
11618
+ if (items.length === 0) {
11619
+ return;
11620
+ }
11621
+ event.preventDefault();
11622
+ const currentIndex = items.indexOf(document.activeElement);
11623
+ if (event.key === "Home") {
11624
+ items[0]?.focus();
11625
+ } else if (event.key === "End") {
11626
+ items[items.length - 1]?.focus();
11627
+ } else {
11628
+ const change = event.key === "ArrowDown" ? 1 : -1;
11629
+ const nextIndex = (currentIndex + change + items.length) % items.length;
11630
+ items[nextIndex]?.focus();
11631
+ }
11632
+ }
11406
11633
  function digitTitle(id) {
11407
11634
  if (id === "arabicIndic") {
11408
11635
  return messages.arabicIndicDigits;
@@ -11460,6 +11687,50 @@ function DocumentInsertToolbar({
11460
11687
  messages.selectedBlockCount
11461
11688
  ] }) : null
11462
11689
  ] }),
11690
+ /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.textFormatting, children: [
11691
+ /* @__PURE__ */ jsx10(
11692
+ "button",
11693
+ {
11694
+ type: "button",
11695
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11696
+ title: messages.bold,
11697
+ "aria-label": messages.bold,
11698
+ "aria-pressed": activeTextStyles.bold === true,
11699
+ disabled: !canFormatText,
11700
+ onMouseDown: (event) => event.preventDefault(),
11701
+ onClick: () => onToggleTextStyle?.("bold"),
11702
+ children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--bold", "aria-hidden": "true", children: "\u0646\u0635" })
11703
+ }
11704
+ ),
11705
+ /* @__PURE__ */ jsx10(
11706
+ "button",
11707
+ {
11708
+ type: "button",
11709
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11710
+ title: messages.italic,
11711
+ "aria-label": messages.italic,
11712
+ "aria-pressed": activeTextStyles.italic === true,
11713
+ disabled: !canFormatText,
11714
+ onMouseDown: (event) => event.preventDefault(),
11715
+ onClick: () => onToggleTextStyle?.("italic"),
11716
+ children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--italic", "aria-hidden": "true", children: "\u0646\u0635" })
11717
+ }
11718
+ ),
11719
+ /* @__PURE__ */ jsx10(
11720
+ "button",
11721
+ {
11722
+ type: "button",
11723
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11724
+ title: messages.underline,
11725
+ "aria-label": messages.underline,
11726
+ "aria-pressed": activeTextStyles.underline === true,
11727
+ disabled: !canFormatText,
11728
+ onMouseDown: (event) => event.preventDefault(),
11729
+ onClick: () => onToggleTextStyle?.("underline"),
11730
+ children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--underline", "aria-hidden": "true", children: "\u0646\u0635" })
11731
+ }
11732
+ )
11733
+ ] }),
11463
11734
  /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.structure, children: [
11464
11735
  /* @__PURE__ */ jsx10(
11465
11736
  "button",
@@ -11511,35 +11782,57 @@ function DocumentInsertToolbar({
11511
11782
  )) }) : null
11512
11783
  ] })
11513
11784
  ] }),
11514
- /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: [
11515
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertCitation, "aria-label": messages.insertCitation, onClick: onInsertCitation, children: "[]" }),
11516
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertInternalRef, "aria-label": messages.insertInternalRef, onClick: onInsertInternalRef, children: "\xA7" }),
11517
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertBibliography, "aria-label": messages.insertBibliography, onClick: onInsertBibliography, children: /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ jsxs8("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: [
11518
- /* @__PURE__ */ jsx10(
11519
- "path",
11520
- {
11521
- fill: "currentColor",
11522
- d: "M2.2 2.4h7.2c.9 0 1.6.7 1.6 1.6v9.2c0 .5-.6.8-1 .5l-2.2-1.6H2.2c-.9 0-1.6-.7-1.6-1.6V4c0-.9.7-1.6 1.6-1.6zm0 1.2c-.2 0-.4.2-.4.4v6.5c0 .2.2.4.4.4h6.1l1.5 1.1V4c0-.2-.2-.4-.4-.4H2.2z"
11523
- }
11524
- ),
11525
- /* @__PURE__ */ jsx10(
11526
- "path",
11527
- {
11528
- fill: "currentColor",
11529
- d: "M3.4 5.2h5.2v1H3.4zm0 2.1h4.2v1H3.4zm0 2.1h3.4v1H3.4z"
11530
- }
11531
- ),
11532
- /* @__PURE__ */ jsx10(
11533
- "path",
11534
- {
11535
- fill: "currentColor",
11536
- d: "M11.4 4.1h2.2c.8 0 1.4.6 1.4 1.4v7.1c0 .4-.5.7-.8.4l-1.7-1.3h-1.1V4.1zm1.2 1.2v5.4l.7.5V5.5c0-.1-.1-.2-.2-.2h-.5z"
11537
- }
11538
- )
11539
- ] }) }) }),
11540
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageReferences, "aria-label": messages.manageReferences, onClick: onManageReferences, children: "\u2630" }),
11541
- /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageLabels, "aria-label": messages.manageLabels, onClick: onManageLabels, children: "\u2317" })
11542
- ] }),
11785
+ /* @__PURE__ */ jsx10("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__references-menu", ref: referencesMenuRef, children: [
11786
+ /* @__PURE__ */ jsxs8(
11787
+ "button",
11788
+ {
11789
+ ref: referencesTriggerRef,
11790
+ type: "button",
11791
+ className: "butex-document2-widget__icon-btn butex-document2-widget__references-trigger",
11792
+ title: messages.citations,
11793
+ "aria-label": messages.citations,
11794
+ "aria-haspopup": "menu",
11795
+ "aria-expanded": referencesMenuOpen,
11796
+ onClick: () => setReferencesMenuOpen((open) => !open),
11797
+ children: [
11798
+ /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ jsx10("path", { fill: "currentColor", d: "M2 2.2h4.2c.7 0 1.3.3 1.8.8.5-.5 1.1-.8 1.8-.8H14v10.6H9.8c-.6 0-1.1.3-1.4.8h-.8c-.3-.5-.8-.8-1.4-.8H2V2.2zm1.2 1.2v8.2h3c.4 0 .8.1 1.2.3V4.6c-.2-.7-.6-1.2-1.2-1.2h-3zm9.6 0h-3c-.6 0-1 .5-1.2 1.2v7.3c.4-.2.8-.3 1.2-.3h3V3.4z" }) }) }),
11799
+ /* @__PURE__ */ jsx10("span", { children: messages.citations }),
11800
+ /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__menu-chevron", "aria-hidden": "true", children: "\u2304" })
11801
+ ]
11802
+ }
11803
+ ),
11804
+ referencesMenuOpen ? /* @__PURE__ */ jsxs8(
11805
+ "div",
11806
+ {
11807
+ className: "butex-document2-widget__references-menu-options",
11808
+ role: "menu",
11809
+ "aria-label": messages.citations,
11810
+ onKeyDown: moveReferenceMenuFocus,
11811
+ children: [
11812
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.insertCitation, onClick: () => runReferenceAction(onInsertCitation), children: [
11813
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "[]" }),
11814
+ /* @__PURE__ */ jsx10("span", { children: messages.insertCitation })
11815
+ ] }),
11816
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.insertInternalRef, onClick: () => runReferenceAction(onInsertInternalRef), children: [
11817
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\xA7" }),
11818
+ /* @__PURE__ */ jsx10("span", { children: messages.insertInternalRef })
11819
+ ] }),
11820
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.insertBibliography, onClick: () => runReferenceAction(onInsertBibliography), children: [
11821
+ /* @__PURE__ */ jsx10("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ jsx10("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ jsx10("path", { fill: "currentColor", d: "M2.2 2.4h7.2c.9 0 1.6.7 1.6 1.6v9.2c0 .5-.6.8-1 .5l-2.2-1.6H2.2c-.9 0-1.6-.7-1.6-1.6V4c0-.9.7-1.6 1.6-1.6zm0 1.2c-.2 0-.4.2-.4.4v6.5c0 .2.2.4.4.4h6.1l1.5 1.1V4c0-.2-.2-.4-.4-.4H2.2zM3.4 5.2h5.2v1H3.4zm0 2.1h4.2v1H3.4zm0 2.1h3.4v1H3.4zM11.4 4.1h2.2c.8 0 1.4.6 1.4 1.4v7.1c0 .4-.5.7-.8.4l-1.7-1.3h-1.1V4.1zm1.2 1.2v5.4l.7.5V5.5c0-.1-.1-.2-.2-.2h-.5z" }) }) }),
11822
+ /* @__PURE__ */ jsx10("span", { children: messages.insertBibliography })
11823
+ ] }),
11824
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.manageReferences, onClick: () => runReferenceAction(onManageReferences), children: [
11825
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\u2630" }),
11826
+ /* @__PURE__ */ jsx10("span", { children: messages.manageReferences })
11827
+ ] }),
11828
+ /* @__PURE__ */ jsxs8("button", { type: "button", role: "menuitem", title: messages.manageLabels, onClick: () => runReferenceAction(onManageLabels), children: [
11829
+ /* @__PURE__ */ jsx10("span", { "aria-hidden": "true", children: "\u2317" }),
11830
+ /* @__PURE__ */ jsx10("span", { children: messages.manageLabels })
11831
+ ] })
11832
+ ]
11833
+ }
11834
+ ) : null
11835
+ ] }) }),
11543
11836
  /* @__PURE__ */ jsxs8("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.lists, children: [
11544
11837
  /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.bulletedList, "aria-label": messages.bulletedList, onClick: onAddList, children: "\u2022\u2261" }),
11545
11838
  /* @__PURE__ */ jsx10("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.numberedList, "aria-label": messages.numberedList, onClick: onAddEnumerate, children: "1." })
@@ -11554,7 +11847,17 @@ import { Fragment, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
11554
11847
  function PreviewInlines({ inlines, output }) {
11555
11848
  return /* @__PURE__ */ jsx11(Fragment, { children: inlines.map((inline, index) => {
11556
11849
  if (inline.kind === "text") {
11557
- return /* @__PURE__ */ jsx11("span", { children: inline.text }, index);
11850
+ let content = /* @__PURE__ */ jsx11("span", { children: inline.text });
11851
+ if (inline.style?.bold) {
11852
+ content = /* @__PURE__ */ jsx11("strong", { children: content });
11853
+ }
11854
+ if (inline.style?.italic) {
11855
+ content = /* @__PURE__ */ jsx11("em", { children: content });
11856
+ }
11857
+ if (inline.style?.underline) {
11858
+ content = /* @__PURE__ */ jsx11("span", { className: "butex-document2-widget__preview-underline", children: content });
11859
+ }
11860
+ return /* @__PURE__ */ jsx11("span", { children: content }, index);
11558
11861
  }
11559
11862
  if (inline.kind === "cite") {
11560
11863
  return /* @__PURE__ */ jsx11("span", { className: "butex-document2-widget__preview-cite", children: inline.label }, inline.id);
@@ -11703,15 +12006,15 @@ function DocumentPreview({
11703
12006
  }
11704
12007
 
11705
12008
  // src/react-document2/EquationDrawer.tsx
11706
- import { useEffect as useEffect6, useRef as useRef6, useState as useState8 } from "react";
12009
+ import { useEffect as useEffect7, useRef as useRef7, useState as useState8 } from "react";
11707
12010
 
11708
12011
  // src/react/ButexEditor.tsx
11709
12012
  import {
11710
12013
  forwardRef,
11711
12014
  useCallback,
11712
- useEffect as useEffect5,
12015
+ useEffect as useEffect6,
11713
12016
  useImperativeHandle,
11714
- useRef as useRef5,
12017
+ useRef as useRef6,
11715
12018
  useState as useState7
11716
12019
  } from "react";
11717
12020
 
@@ -12786,28 +13089,28 @@ ${latex || messages.empty}`;
12786
13089
  var ButexEditor = forwardRef(
12787
13090
  function ButexEditor2({ className, debug = false, defaultSide, uiLocale = "ar", initialSession, onSessionChange }, ref) {
12788
13091
  const messages = equationEditorMessages(uiLocale);
12789
- const wrapperRef = useRef5(null);
12790
- const surfaceRef = useRef5(null);
12791
- const mathOutputRef = useRef5(null);
12792
- const renderErrorRef = useRef5(null);
12793
- const latexLinesRef = useRef5(null);
12794
- const passivePreviewRef = useRef5(null);
12795
- const debugLogRef = useRef5(null);
12796
- const undoBtnRef = useRef5(null);
12797
- const redoBtnRef = useRef5(null);
12798
- const copyBtnRef = useRef5(null);
12799
- const cutBtnRef = useRef5(null);
12800
- const splitTypingBtnRef = useRef5(null);
12801
- const characterFontBtnRef = useRef5(null);
12802
- const digitFormBtnRef = useRef5(null);
12803
- const runtimeRef = useRef5(null);
12804
- const debugRef = useRef5(debug);
13092
+ const wrapperRef = useRef6(null);
13093
+ const surfaceRef = useRef6(null);
13094
+ const mathOutputRef = useRef6(null);
13095
+ const renderErrorRef = useRef6(null);
13096
+ const latexLinesRef = useRef6(null);
13097
+ const passivePreviewRef = useRef6(null);
13098
+ const debugLogRef = useRef6(null);
13099
+ const undoBtnRef = useRef6(null);
13100
+ const redoBtnRef = useRef6(null);
13101
+ const copyBtnRef = useRef6(null);
13102
+ const cutBtnRef = useRef6(null);
13103
+ const splitTypingBtnRef = useRef6(null);
13104
+ const characterFontBtnRef = useRef6(null);
13105
+ const digitFormBtnRef = useRef6(null);
13106
+ const runtimeRef = useRef6(null);
13107
+ const debugRef = useRef6(debug);
12805
13108
  debugRef.current = debug;
12806
- const uiLocaleRef = useRef5(uiLocale);
13109
+ const uiLocaleRef = useRef6(uiLocale);
12807
13110
  uiLocaleRef.current = uiLocale;
12808
- const onSessionChangeRef = useRef5(onSessionChange);
13111
+ const onSessionChangeRef = useRef6(onSessionChange);
12809
13112
  onSessionChangeRef.current = onSessionChange;
12810
- const debugBodyHiddenRef = useRef5(false);
13113
+ const debugBodyHiddenRef = useRef6(false);
12811
13114
  const [debugBodyHidden, setDebugBodyHidden] = useState7(false);
12812
13115
  debugBodyHiddenRef.current = debugBodyHidden;
12813
13116
  const [digitMenuOpen, setDigitMenuOpen] = useState7(false);
@@ -12888,9 +13191,9 @@ ${arabic || currentMessages.empty}`;
12888
13191
  }
12889
13192
  }
12890
13193
  }, []);
12891
- const updatePreviewRef = useRef5(updatePreview);
13194
+ const updatePreviewRef = useRef6(updatePreview);
12892
13195
  updatePreviewRef.current = updatePreview;
12893
- useEffect5(() => {
13196
+ useEffect6(() => {
12894
13197
  injectWidgetChromeCss();
12895
13198
  injectBuTeXEditorStyles(typeof document !== "undefined" ? document : void 0);
12896
13199
  const surfaceEl = surfaceRef.current;
@@ -12946,7 +13249,7 @@ ${arabic || currentMessages.empty}`;
12946
13249
  runtimeRef.current = null;
12947
13250
  };
12948
13251
  }, []);
12949
- useEffect5(() => {
13252
+ useEffect6(() => {
12950
13253
  runtimeRef.current?.setUiLocale(uiLocale);
12951
13254
  const session = runtimeRef.current?.getSession();
12952
13255
  if (session) {
@@ -14122,7 +14425,7 @@ function EquationDrawer({
14122
14425
  onDelete
14123
14426
  }) {
14124
14427
  const messages = document2Messages(uiLocale);
14125
- const latestSession = useRef6(session);
14428
+ const latestSession = useRef7(session);
14126
14429
  const [labelOverride, setLabelOverride] = useState8(null);
14127
14430
  const displayedLabel = labelOverride ?? label;
14128
14431
  const labelError = labelEnabled && mathMode === "display" ? document2KeyError(displayedLabel, {
@@ -14130,10 +14433,10 @@ function EquationDrawer({
14130
14433
  labels,
14131
14434
  excludeOwnerId: ownerId || void 0
14132
14435
  }) : null;
14133
- useEffect6(() => {
14436
+ useEffect7(() => {
14134
14437
  setLabelOverride(null);
14135
14438
  }, [ownerId, label]);
14136
- useEffect6(() => {
14439
+ useEffect7(() => {
14137
14440
  const onKeyDown = (event) => {
14138
14441
  if (event.key === "Escape") {
14139
14442
  onClose();
@@ -14675,7 +14978,7 @@ function ReferencesPanel({
14675
14978
  }
14676
14979
 
14677
14980
  // src/react-document2/RefPickerPopover.tsx
14678
- import { useEffect as useEffect7, useState as useState11 } from "react";
14981
+ import { useEffect as useEffect8, useState as useState11 } from "react";
14679
14982
  import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
14680
14983
  function RefPickerPopover({
14681
14984
  open,
@@ -14692,7 +14995,7 @@ function RefPickerPopover({
14692
14995
  const documentDirection = uiLocale === "ar" ? "rtl" : "ltr";
14693
14996
  const [selected, setSelected] = useState11(initialKeys);
14694
14997
  const [refCommand, setRefCommand] = useState11(initialRefCommand);
14695
- useEffect7(() => {
14998
+ useEffect8(() => {
14696
14999
  if (open) {
14697
15000
  setSelected(initialKeys);
14698
15001
  setRefCommand(initialRefCommand);
@@ -14757,7 +15060,7 @@ function RefPickerPopover({
14757
15060
 
14758
15061
  // src/react-document2/editorFocus.ts
14759
15062
  function createEmptyDocument2EditorFocus() {
14760
- return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0 };
15063
+ return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 };
14761
15064
  }
14762
15065
  function findBlockIdForField(blocks, fieldId) {
14763
15066
  for (const block of blocks) {
@@ -14965,6 +15268,89 @@ var DOCUMENT2_WIDGET_CSS = `
14965
15268
  display: block;
14966
15269
  }
14967
15270
 
15271
+ .butex-document2-widget__format-btn {
15272
+ font-family: "Amiri", "Noto Serif Arabic", Georgia, serif;
15273
+ min-width: 36px;
15274
+ }
15275
+
15276
+ .butex-document2-widget__format-icon {
15277
+ direction: rtl;
15278
+ display: inline-block;
15279
+ font-size: 0.84rem;
15280
+ font-weight: 500;
15281
+ }
15282
+
15283
+ .butex-document2-widget__format-icon--bold {
15284
+ font-weight: 900;
15285
+ }
15286
+
15287
+ .butex-document2-widget__format-icon--italic {
15288
+ font-style: italic;
15289
+ transform: skewX(-7deg);
15290
+ }
15291
+
15292
+ .butex-document2-widget__format-icon--underline {
15293
+ text-decoration: underline;
15294
+ text-underline-offset: 0.12em;
15295
+ }
15296
+
15297
+ .butex-document2-widget__format-underline,
15298
+ .butex-document2-widget__preview-underline {
15299
+ text-decoration: underline;
15300
+ text-underline-offset: 0.12em;
15301
+ }
15302
+
15303
+ .butex-document2-widget__references-menu {
15304
+ position: relative;
15305
+ }
15306
+
15307
+ .butex-document2-widget__references-trigger {
15308
+ gap: 5px;
15309
+ white-space: nowrap;
15310
+ }
15311
+
15312
+ .butex-document2-widget__menu-chevron {
15313
+ color: var(--butex-document2-muted);
15314
+ font-size: 0.8rem;
15315
+ }
15316
+
15317
+ .butex-document2-widget__references-menu-options {
15318
+ background: var(--butex-document2-panel);
15319
+ border: 1px solid var(--butex-document2-border);
15320
+ border-radius: 8px;
15321
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--butex-document2-fg) 16%, transparent);
15322
+ display: grid;
15323
+ gap: 2px;
15324
+ inset-inline-start: 0;
15325
+ margin-top: 4px;
15326
+ min-width: max-content;
15327
+ padding: 4px;
15328
+ position: absolute;
15329
+ top: 100%;
15330
+ z-index: 30;
15331
+ }
15332
+
15333
+ .butex-document2-widget__references-menu-options button {
15334
+ align-items: center;
15335
+ background: transparent;
15336
+ border: 0;
15337
+ border-radius: 6px;
15338
+ color: var(--butex-document2-fg);
15339
+ cursor: pointer;
15340
+ display: grid;
15341
+ font: inherit;
15342
+ gap: 8px;
15343
+ grid-template-columns: 18px 1fr;
15344
+ padding: 7px 9px;
15345
+ text-align: start;
15346
+ }
15347
+
15348
+ .butex-document2-widget__references-menu-options button:hover,
15349
+ .butex-document2-widget__references-menu-options button:focus-visible {
15350
+ background: var(--butex-document2-accent-bg);
15351
+ outline: none;
15352
+ }
15353
+
14968
15354
  .butex-document2-widget__float-meta {
14969
15355
  display: grid;
14970
15356
  gap: 8px;
@@ -15408,6 +15794,19 @@ var DOCUMENT2_WIDGET_CSS = `
15408
15794
  border-color: color-mix(in srgb, var(--butex-document2-text-token-border) 55%, var(--butex-document2-accent));
15409
15795
  }
15410
15796
 
15797
+ .butex-document2-widget__inline-text[data-bold="true"] {
15798
+ font-weight: 700;
15799
+ }
15800
+
15801
+ .butex-document2-widget__inline-text[data-italic="true"] {
15802
+ font-style: italic;
15803
+ }
15804
+
15805
+ .butex-document2-widget__inline-text[data-underline="true"] {
15806
+ text-decoration: underline;
15807
+ text-underline-offset: 0.12em;
15808
+ }
15809
+
15411
15810
  .butex-document2-widget__inline-field input {
15412
15811
  min-width: 12ch;
15413
15812
  }
@@ -16425,6 +16824,50 @@ function topLevelBlockIdForField(document2, fieldId) {
16425
16824
  function topLevelBlockIdForToken(document2, tokenId) {
16426
16825
  return document2.blocks.find((block) => blockHasToken(block, tokenId))?.id ?? null;
16427
16826
  }
16827
+ function findFormatTextToken(document2, focus) {
16828
+ if (!focus.fieldId || !focus.textTokenId || focus.selectionEnd <= focus.selectionStart) {
16829
+ return null;
16830
+ }
16831
+ function textTokenFromField(field) {
16832
+ if (field.id !== focus.fieldId) {
16833
+ return null;
16834
+ }
16835
+ const token = field.tokens.find((entry) => entry.id === focus.textTokenId && entry.kind === "text");
16836
+ return token ?? null;
16837
+ }
16838
+ function visit(blocks) {
16839
+ for (const block of blocks) {
16840
+ if (block.kind === "textBlock") {
16841
+ const token = block.command === "\\paragraph" ? textTokenFromField(block.field) : null;
16842
+ if (token) {
16843
+ return token;
16844
+ }
16845
+ } else if (block.kind === "list") {
16846
+ for (const item of block.items) {
16847
+ const itemToken = textTokenFromField(item.field);
16848
+ if (itemToken) {
16849
+ return itemToken;
16850
+ }
16851
+ const nestedToken = visit(item.blocks);
16852
+ if (nestedToken) {
16853
+ return nestedToken;
16854
+ }
16855
+ }
16856
+ } else if (block.kind === "table") {
16857
+ for (const row of block.rows) {
16858
+ for (const cell of row) {
16859
+ const token = textTokenFromField(cell);
16860
+ if (token) {
16861
+ return token;
16862
+ }
16863
+ }
16864
+ }
16865
+ }
16866
+ }
16867
+ return null;
16868
+ }
16869
+ return visit(document2.blocks);
16870
+ }
16428
16871
  function formatDocument2Diagnostic(d, messages) {
16429
16872
  if (d.message === "math_objects order mismatch") {
16430
16873
  return `${messages.importOrderMismatch} ${messages.path}: ${d.path}`;
@@ -16510,13 +16953,13 @@ function ButexDocumentEditor2({
16510
16953
  const [blockSelectionState, setBlockSelectionState] = useState12(emptyBlockSelection);
16511
16954
  const [historyTick, setHistoryTick] = useState12(0);
16512
16955
  const digitForm = digitFormProp ?? digitFormState ?? (documentDirection === "rtl" ? "arabicIndic" : "western");
16513
- const historyRef = useRef7(createDocument2History());
16514
- const documentRef = useRef7(documentNode);
16515
- const textSnapshotArmedRef = useRef7(false);
16516
- const textDebounceRef = useRef7(null);
16517
- const widgetRef = useRef7(null);
16518
- const articleMetaPanelRef = useRef7(null);
16519
- const pendingFocusRef = useRef7(null);
16956
+ const historyRef = useRef8(createDocument2History());
16957
+ const documentRef = useRef8(documentNode);
16958
+ const textSnapshotArmedRef = useRef8(false);
16959
+ const textDebounceRef = useRef8(null);
16960
+ const widgetRef = useRef8(null);
16961
+ const articleMetaPanelRef = useRef8(null);
16962
+ const pendingFocusRef = useRef8(null);
16520
16963
  documentRef.current = documentNode;
16521
16964
  const latex = document2Latex(documentNode);
16522
16965
  const documentLabels = useMemo2(() => collectDocument2Labels(documentNode), [documentNode]);
@@ -16543,6 +16986,9 @@ function ButexDocumentEditor2({
16543
16986
  const selectionCount = blockSelection ? blockSelection.to - blockSelection.from + 1 : 0;
16544
16987
  const canMoveSelectionUp = Boolean(blockSelection && blockSelection.from > 0);
16545
16988
  const canMoveSelectionDown = Boolean(blockSelection && blockSelection.to < documentNode.blocks.length - 1);
16989
+ const formatTextToken = useMemo2(() => findFormatTextToken(documentNode, editorFocus), [documentNode, editorFocus]);
16990
+ const canFormatText = formatTextToken !== null;
16991
+ const activeTextStyles = formatTextToken?.style ?? {};
16546
16992
  function clearBlockSelection() {
16547
16993
  setBlockSelectionState(emptyBlockSelection());
16548
16994
  }
@@ -16580,15 +17026,15 @@ function ButexDocumentEditor2({
16580
17026
  applyDocument(removeDocument2BlockRange(documentRef.current, selection.from, selection.to), "immediate");
16581
17027
  clearBlockSelection();
16582
17028
  }
16583
- useEffect8(() => {
17029
+ useEffect9(() => {
16584
17030
  injectBuTeXDocument2Styles();
16585
17031
  }, []);
16586
- useEffect8(() => {
17032
+ useEffect9(() => {
16587
17033
  if (!editableEquations || previewOnly) {
16588
17034
  setSelectedMath(null);
16589
17035
  }
16590
17036
  }, [editableEquations, previewOnly]);
16591
- useEffect8(() => {
17037
+ useEffect9(() => {
16592
17038
  const next = resolveInitialDocument2(initialDocument, uiLocale, documentMeta);
16593
17039
  setDocumentNode(next.document);
16594
17040
  setError(next.error);
@@ -16597,13 +17043,13 @@ function ButexDocumentEditor2({
16597
17043
  historyRef.current = createDocument2History();
16598
17044
  setHistoryTick((tick) => tick + 1);
16599
17045
  }, [initialDocument, documentMeta, uiLocale]);
16600
- useEffect8(() => {
17046
+ useEffect9(() => {
16601
17047
  onDocumentChange?.(documentNode);
16602
17048
  }, [documentNode, onDocumentChange]);
16603
- useEffect8(() => {
17049
+ useEffect9(() => {
16604
17050
  onLatexChange?.(latex);
16605
17051
  }, [latex, onLatexChange]);
16606
- useEffect8(() => {
17052
+ useEffect9(() => {
16607
17053
  const pending = pendingFocusRef.current;
16608
17054
  const root = widgetRef.current;
16609
17055
  if (!pending || !root) {
@@ -16719,7 +17165,7 @@ function ButexDocumentEditor2({
16719
17165
  function openAllBlocks() {
16720
17166
  setCollapsedBlockIds(/* @__PURE__ */ new Set());
16721
17167
  }
16722
- useEffect8(() => {
17168
+ useEffect9(() => {
16723
17169
  const root = widgetRef.current;
16724
17170
  if (!root) {
16725
17171
  return;
@@ -16743,15 +17189,15 @@ function ButexDocumentEditor2({
16743
17189
  root.addEventListener("keydown", onKeyDown);
16744
17190
  return () => root.removeEventListener("keydown", onKeyDown);
16745
17191
  }, [editorOpen]);
16746
- function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset) {
17192
+ function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd) {
16747
17193
  openBlock(blockId);
16748
17194
  clearBlockSelection();
16749
- setEditorFocus({ blockId, fieldId, textTokenId, caretOffset });
17195
+ setEditorFocus({ blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd });
16750
17196
  }
16751
17197
  function rememberMathFocus(blockId, fieldId) {
16752
17198
  openBlock(blockId);
16753
17199
  clearBlockSelection();
16754
- setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0 });
17200
+ setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 });
16755
17201
  }
16756
17202
  function rememberBlockFocus(blockId) {
16757
17203
  openBlock(blockId);
@@ -17014,6 +17460,22 @@ function ButexDocumentEditor2({
17014
17460
  function deleteRefToken(tokenId) {
17015
17461
  applyDocument(removeRefTokenById(documentRef.current, tokenId), "immediate");
17016
17462
  }
17463
+ function toggleInlineTextStyle(styleName) {
17464
+ if (!canFormatText || !editorFocus.fieldId || !editorFocus.textTokenId) {
17465
+ return;
17466
+ }
17467
+ applyDocument(
17468
+ toggleTextTokenStyle(
17469
+ documentRef.current,
17470
+ editorFocus.fieldId,
17471
+ editorFocus.textTokenId,
17472
+ editorFocus.selectionStart,
17473
+ editorFocus.selectionEnd,
17474
+ styleName
17475
+ ),
17476
+ "immediate"
17477
+ );
17478
+ }
17017
17479
  const showEditorPanel = !previewOnly && editorOpen;
17018
17480
  const showPreviewPanel = previewOnly || previewOpen;
17019
17481
  return /* @__PURE__ */ jsx17(
@@ -17036,8 +17498,11 @@ function ButexDocumentEditor2({
17036
17498
  selectionCount,
17037
17499
  canMoveSelectionUp,
17038
17500
  canMoveSelectionDown,
17501
+ canFormatText,
17502
+ activeTextStyles,
17039
17503
  onUndo: undoDocument,
17040
17504
  onRedo: redoDocument,
17505
+ onToggleTextStyle: toggleInlineTextStyle,
17041
17506
  onMoveSelectionUp: () => moveSelectedBlocks(-1),
17042
17507
  onMoveSelectionDown: () => moveSelectedBlocks(1),
17043
17508
  onDeleteSelection: deleteSelectedBlocks,