@drghaliasri/butex 5.4.6 → 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.
@@ -1168,7 +1168,7 @@ function parseReferences(json) {
1168
1168
  }
1169
1169
  return references;
1170
1170
  }
1171
- function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = []) {
1171
+ function createInlineField2(value = "", mathObjects = [], options = {}, path = "$", diagnostics = [], formats = []) {
1172
1172
  const mode = options.mode ?? "english";
1173
1173
  const spans = detectInlineSpans2(value);
1174
1174
  const mathSpans = spans.filter((span) => span.kind === "math");
@@ -1180,7 +1180,7 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1180
1180
  }
1181
1181
  for (const span of spans) {
1182
1182
  if (span.start > index) {
1183
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index, span.start) });
1183
+ pushFormattedTextTokens(tokens, value.slice(index, span.start), index, formats);
1184
1184
  }
1185
1185
  if (span.kind === "cite") {
1186
1186
  tokens.push({
@@ -1236,9 +1236,81 @@ function createInlineField2(value = "", mathObjects = [], options = {}, path = "
1236
1236
  index = span.end;
1237
1237
  }
1238
1238
  if (index < value.length || tokens.length === 0) {
1239
- tokens.push({ id: document2Id("text"), kind: "text", text: value.slice(index) });
1239
+ pushFormattedTextTokens(tokens, value.slice(index), index, formats);
1240
+ }
1241
+ return { id: document2Id("field"), tokens: compactTextTokens(tokens) };
1242
+ }
1243
+ function styleFromFormat(format) {
1244
+ const style = {};
1245
+ if (format.bold === true) {
1246
+ style.bold = true;
1247
+ }
1248
+ if (format.italic === true) {
1249
+ style.italic = true;
1250
+ }
1251
+ if (format.underline === true) {
1252
+ style.underline = true;
1253
+ }
1254
+ return style.bold || style.italic || style.underline ? style : null;
1255
+ }
1256
+ function mergeTextStyle(base, added) {
1257
+ return {
1258
+ ...base?.bold ? { bold: true } : {},
1259
+ ...base?.italic ? { italic: true } : {},
1260
+ ...base?.underline ? { underline: true } : {},
1261
+ ...added.bold ? { bold: true } : {},
1262
+ ...added.italic ? { italic: true } : {},
1263
+ ...added.underline ? { underline: true } : {}
1264
+ };
1265
+ }
1266
+ function textStylesEqual(a, b) {
1267
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
1268
+ }
1269
+ function compactTextTokens(tokens) {
1270
+ const compacted = [];
1271
+ for (const token of tokens) {
1272
+ const previous = compacted[compacted.length - 1];
1273
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual(previous.style, token.style)) {
1274
+ previous.text += token.text;
1275
+ } else {
1276
+ compacted.push(token);
1277
+ }
1278
+ }
1279
+ return compacted;
1280
+ }
1281
+ function styleKey(style) {
1282
+ return `${style?.bold ? "b" : ""}${style?.italic ? "i" : ""}${style?.underline ? "u" : ""}`;
1283
+ }
1284
+ function pushFormattedTextTokens(tokens, text, sourceStart, formats) {
1285
+ if (text.length === 0) {
1286
+ tokens.push({ id: document2Id("text"), kind: "text", text });
1287
+ return;
1288
+ }
1289
+ const styles = Array.from({ length: text.length });
1290
+ for (const format of formats) {
1291
+ const style = styleFromFormat(format);
1292
+ if (!style || !Number.isFinite(format.start) || !Number.isFinite(format.end)) {
1293
+ continue;
1294
+ }
1295
+ const start = Math.max(0, Math.min(text.length, Math.trunc(format.start) - sourceStart));
1296
+ const end = Math.max(0, Math.min(text.length, Math.trunc(format.end) - sourceStart));
1297
+ if (end <= start) {
1298
+ continue;
1299
+ }
1300
+ for (let index = start; index < end; index += 1) {
1301
+ styles[index] = mergeTextStyle(styles[index], style);
1302
+ }
1303
+ }
1304
+ let chunkStart = 0;
1305
+ for (let index = 1; index <= text.length; index += 1) {
1306
+ if (index < text.length && styleKey(styles[index]) === styleKey(styles[chunkStart])) {
1307
+ continue;
1308
+ }
1309
+ const chunk = text.slice(chunkStart, index);
1310
+ const style = styles[chunkStart];
1311
+ tokens.push({ id: document2Id("text"), kind: "text", text: chunk, ...style ? { style } : {} });
1312
+ chunkStart = index;
1240
1313
  }
1241
- return { id: document2Id("field"), tokens };
1242
1314
  }
1243
1315
  function closingForList(command) {
1244
1316
  return command === "\\begin{itemize}" ? "\\end{itemize}" : "\\end{enumerate}";
@@ -1249,7 +1321,7 @@ function parseTextBlock(json, options, path, diagnostics) {
1249
1321
  id: document2Id("block"),
1250
1322
  kind: "textBlock",
1251
1323
  command: json.command,
1252
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1324
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.command === "\\paragraph" ? json.formats ?? [] : []),
1253
1325
  ...json.centered === true ? { centered: true } : {}
1254
1326
  };
1255
1327
  }
@@ -1258,7 +1330,7 @@ function parseListItem(json, options, path, diagnostics) {
1258
1330
  const blocksJson = Array.isArray(json.blocks) ? json.blocks : [];
1259
1331
  return {
1260
1332
  id: document2Id("item"),
1261
- field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics),
1333
+ field: createInlineField2(value, json.math_objects ?? [], options, `${path}.value`, diagnostics, json.formats ?? []),
1262
1334
  blocks: blocksJson.map((block, index) => parseBlock(asBlockJson(block), options, `${path}.blocks[${String(index)}]`, diagnostics))
1263
1335
  };
1264
1336
  }
@@ -1291,7 +1363,8 @@ function parseTableBlock(json, options, path, diagnostics) {
1291
1363
  const spanCount = detectMathSpans2(value).length;
1292
1364
  const cellMathObjects = mathObjects.slice(mathObjectIndex, mathObjectIndex + spanCount);
1293
1365
  mathObjectIndex += spanCount;
1294
- return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics);
1366
+ const cellFormats = json.cell_formats?.[rowIndex]?.[columnIndex] ?? [];
1367
+ return createInlineField2(value, cellMathObjects, options, `${path}.rows[${String(rowIndex)}][${String(columnIndex)}]`, diagnostics, cellFormats);
1295
1368
  });
1296
1369
  });
1297
1370
  if (mathObjects.length > 0 && mathObjects.length !== mathObjectIndex) {
@@ -8116,6 +8189,79 @@ function normalizeFieldTokens(tokens) {
8116
8189
  }
8117
8190
  return tokens;
8118
8191
  }
8192
+ function textStylesEqual2(a, b) {
8193
+ return Boolean(a?.bold) === Boolean(b?.bold) && Boolean(a?.italic) === Boolean(b?.italic) && Boolean(a?.underline) === Boolean(b?.underline);
8194
+ }
8195
+ function compactAdjacentTextTokens(tokens) {
8196
+ const compacted = [];
8197
+ for (const token of tokens) {
8198
+ const previous = compacted[compacted.length - 1];
8199
+ if (previous?.kind === "text" && token.kind === "text" && textStylesEqual2(previous.style, token.style)) {
8200
+ previous.text += token.text;
8201
+ } else {
8202
+ compacted.push(token);
8203
+ }
8204
+ }
8205
+ return normalizeFieldTokens(compacted);
8206
+ }
8207
+ function withoutEmptyStyle(style) {
8208
+ const next = {};
8209
+ if (style.bold) {
8210
+ next.bold = true;
8211
+ }
8212
+ if (style.italic) {
8213
+ next.italic = true;
8214
+ }
8215
+ if (style.underline) {
8216
+ next.underline = true;
8217
+ }
8218
+ return next.bold || next.italic || next.underline ? next : void 0;
8219
+ }
8220
+ function textTokenPart(text, style, id = document2Id("text")) {
8221
+ return { id, kind: "text", text, ...style ? { style } : {} };
8222
+ }
8223
+ function toggleTextTokenStyle(document2, fieldId, tokenId, selectionStart, selectionEnd, styleName) {
8224
+ if (selectionEnd <= selectionStart) {
8225
+ return document2;
8226
+ }
8227
+ const next = cloneDocument(document2);
8228
+ visitFields(next.blocks, (field) => {
8229
+ if (field.id !== fieldId) {
8230
+ return false;
8231
+ }
8232
+ const tokenIndex = field.tokens.findIndex((entry) => entry.id === tokenId && entry.kind === "text");
8233
+ if (tokenIndex < 0) {
8234
+ return true;
8235
+ }
8236
+ const token = field.tokens[tokenIndex];
8237
+ const start = Math.max(0, Math.min(token.text.length, selectionStart));
8238
+ const end = Math.max(0, Math.min(token.text.length, selectionEnd));
8239
+ if (end <= start) {
8240
+ return true;
8241
+ }
8242
+ const selectedStyle = token.style ?? {};
8243
+ const enabled = selectedStyle[styleName] === true;
8244
+ const nextStyle = withoutEmptyStyle({ ...selectedStyle, [styleName]: enabled ? void 0 : true });
8245
+ const parts = [];
8246
+ const before = token.text.slice(0, start);
8247
+ const selected = token.text.slice(start, end);
8248
+ const after = token.text.slice(end);
8249
+ if (before.length > 0) {
8250
+ parts.push(textTokenPart(before, token.style, token.id));
8251
+ }
8252
+ parts.push(textTokenPart(selected, nextStyle, before.length > 0 ? document2Id("text") : token.id));
8253
+ if (after.length > 0) {
8254
+ parts.push(textTokenPart(after, token.style));
8255
+ }
8256
+ field.tokens = compactAdjacentTextTokens([
8257
+ ...field.tokens.slice(0, tokenIndex),
8258
+ ...parts,
8259
+ ...field.tokens.slice(tokenIndex + 1)
8260
+ ]);
8261
+ return true;
8262
+ });
8263
+ return next;
8264
+ }
8119
8265
  function stitchTextAroundRemovedToken(tokens, index) {
8120
8266
  const before = tokens[index - 1];
8121
8267
  const after = tokens[index + 1];
@@ -8143,8 +8289,8 @@ function removeMathTokenById(document2, tokenId) {
8143
8289
  function splitTextTokenAt(token, offset) {
8144
8290
  const safeOffset = Math.max(0, Math.min(offset, token.text.length));
8145
8291
  return [
8146
- { id: token.id, kind: "text", text: token.text.slice(0, safeOffset) },
8147
- { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset) }
8292
+ { id: token.id, kind: "text", text: token.text.slice(0, safeOffset), ...token.style ? { style: token.style } : {} },
8293
+ { id: document2Id("text"), kind: "text", text: token.text.slice(safeOffset), ...token.style ? { style: token.style } : {} }
8148
8294
  ];
8149
8295
  }
8150
8296
  function insertMathTokenAtCaret(document2, fieldId, textTokenId, caretOffset, session, opening = "$", closing = "$", side = "arabic") {
@@ -8852,9 +8998,22 @@ function mathBodyLatex(token) {
8852
8998
  const rendered = renderMathNodeLatex(token.math);
8853
8999
  return stripDisplayMathDelimiters(rendered);
8854
9000
  }
9001
+ function styledTextLatex(text, style) {
9002
+ let output = text;
9003
+ if (style?.bold) {
9004
+ output = `\\textbf{${output}}`;
9005
+ }
9006
+ if (style?.italic) {
9007
+ output = `\\textit{${output}}`;
9008
+ }
9009
+ if (style?.underline) {
9010
+ output = `\\underline{${output}}`;
9011
+ }
9012
+ return output;
9013
+ }
8855
9014
  function tokenLatex(token) {
8856
9015
  if (token.kind === "text") {
8857
- return token.text;
9016
+ return styledTextLatex(token.text, token.style);
8858
9017
  }
8859
9018
  if (token.kind === "cite") {
8860
9019
  return citeTokenLatex(token.keys);
@@ -8878,7 +9037,9 @@ function inlineFieldLatex(field) {
8878
9037
  return field.tokens.map((token) => tokenLatex(token)).join("");
8879
9038
  }
8880
9039
  function textBlockLatex(block) {
8881
- const body = `${block.command}{${inlineFieldLatex(block.field)}}`;
9040
+ const content = inlineFieldLatex(block.field);
9041
+ const body = block.command === "\\paragraph" ? `\\par
9042
+ ${content}` : `${block.command}{${content}}`;
8882
9043
  if (block.command === "\\paragraph" && block.centered) {
8883
9044
  return `\\begin{center}
8884
9045
  ${body}
@@ -9130,7 +9291,7 @@ function mathTex(token, equationSide) {
9130
9291
  function previewInlines(field, islands, output, equationSide, document2, previewOptions) {
9131
9292
  return field.tokens.map((token) => {
9132
9293
  if (token.kind === "text") {
9133
- return { kind: "text", text: token.text };
9294
+ return { kind: "text", text: token.text, ...token.style ? { style: token.style } : {} };
9134
9295
  }
9135
9296
  if (token.kind === "cite") {
9136
9297
  return {
@@ -9306,6 +9467,10 @@ var DOCUMENT2_MESSAGES = {
9306
9467
  redo: "\u0625\u0639\u0627\u062F\u0629",
9307
9468
  structure: "\u0647\u064A\u0643\u0644",
9308
9469
  content: "\u0645\u062D\u062A\u0648\u0649",
9470
+ textFormatting: "\u062A\u0646\u0633\u064A\u0642 \u0627\u0644\u0646\u0635",
9471
+ bold: "\u063A\u0627\u0645\u0642",
9472
+ italic: "\u0645\u0627\u0626\u0644",
9473
+ underline: "\u062A\u062D\u062A\u0647 \u062E\u0637",
9309
9474
  equations: "\u0645\u0639\u0627\u062F\u0644\u0627\u062A",
9310
9475
  citations: "\u0627\u0642\u062A\u0628\u0627\u0633\u0627\u062A \u0648\u0645\u0631\u0627\u062C\u0639",
9311
9476
  lists: "\u0642\u0648\u0627\u0626\u0645",
@@ -9445,6 +9610,10 @@ var DOCUMENT2_MESSAGES = {
9445
9610
  redo: "Redo",
9446
9611
  structure: "Structure",
9447
9612
  content: "Content",
9613
+ textFormatting: "Text formatting",
9614
+ bold: "Bold",
9615
+ italic: "Italic",
9616
+ underline: "Underline",
9448
9617
  equations: "Equations",
9449
9618
  citations: "Citations and references",
9450
9619
  lists: "Lists",
@@ -10483,7 +10652,9 @@ function rememberCaret(element, blockId, fieldId, tokenId, onFieldFocus) {
10483
10652
  if (!blockId || !onFieldFocus) {
10484
10653
  return;
10485
10654
  }
10486
- onFieldFocus(blockId, fieldId, tokenId, element.selectionStart ?? element.value.length);
10655
+ const selectionStart = element.selectionStart ?? element.value.length;
10656
+ const selectionEnd = element.selectionEnd ?? selectionStart;
10657
+ onFieldFocus(blockId, fieldId, tokenId, selectionEnd, selectionStart, selectionEnd);
10487
10658
  }
10488
10659
  function fitTextareaHeight(element) {
10489
10660
  if (!element) {
@@ -10617,6 +10788,9 @@ function InlineField({
10617
10788
  {
10618
10789
  className: "butex-document2-widget__inline-text",
10619
10790
  value: token.text,
10791
+ "data-bold": token.style?.bold === true ? "true" : void 0,
10792
+ "data-italic": token.style?.italic === true ? "true" : void 0,
10793
+ "data-underline": token.style?.underline === true ? "true" : void 0,
10620
10794
  dir: documentDirection,
10621
10795
  "aria-label": messages.text,
10622
10796
  rows: 1,
@@ -11413,8 +11587,11 @@ function DocumentInsertToolbar({
11413
11587
  selectionCount = 0,
11414
11588
  canMoveSelectionUp = false,
11415
11589
  canMoveSelectionDown = false,
11590
+ canFormatText = false,
11591
+ activeTextStyles = {},
11416
11592
  onUndo,
11417
11593
  onRedo,
11594
+ onToggleTextStyle,
11418
11595
  onMoveSelectionUp,
11419
11596
  onMoveSelectionDown,
11420
11597
  onDeleteSelection,
@@ -11438,6 +11615,56 @@ function DocumentInsertToolbar({
11438
11615
  }) {
11439
11616
  const messages = document2Messages(uiLocale);
11440
11617
  const [digitMenuOpen, setDigitMenuOpen] = (0, import_react7.useState)(false);
11618
+ const [referencesMenuOpen, setReferencesMenuOpen] = (0, import_react7.useState)(false);
11619
+ const referencesMenuRef = (0, import_react7.useRef)(null);
11620
+ const referencesTriggerRef = (0, import_react7.useRef)(null);
11621
+ (0, import_react7.useEffect)(() => {
11622
+ if (!referencesMenuOpen) {
11623
+ return;
11624
+ }
11625
+ referencesMenuRef.current?.querySelector('[role="menuitem"]')?.focus();
11626
+ const closeOnOutsideClick = (event) => {
11627
+ if (!referencesMenuRef.current?.contains(event.target)) {
11628
+ setReferencesMenuOpen(false);
11629
+ }
11630
+ };
11631
+ const closeOnEscape = (event) => {
11632
+ if (event.key === "Escape") {
11633
+ setReferencesMenuOpen(false);
11634
+ referencesTriggerRef.current?.focus();
11635
+ }
11636
+ };
11637
+ document.addEventListener("mousedown", closeOnOutsideClick);
11638
+ document.addEventListener("keydown", closeOnEscape);
11639
+ return () => {
11640
+ document.removeEventListener("mousedown", closeOnOutsideClick);
11641
+ document.removeEventListener("keydown", closeOnEscape);
11642
+ };
11643
+ }, [referencesMenuOpen]);
11644
+ function runReferenceAction(action) {
11645
+ setReferencesMenuOpen(false);
11646
+ action();
11647
+ }
11648
+ function moveReferenceMenuFocus(event) {
11649
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
11650
+ return;
11651
+ }
11652
+ const items = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
11653
+ if (items.length === 0) {
11654
+ return;
11655
+ }
11656
+ event.preventDefault();
11657
+ const currentIndex = items.indexOf(document.activeElement);
11658
+ if (event.key === "Home") {
11659
+ items[0]?.focus();
11660
+ } else if (event.key === "End") {
11661
+ items[items.length - 1]?.focus();
11662
+ } else {
11663
+ const change = event.key === "ArrowDown" ? 1 : -1;
11664
+ const nextIndex = (currentIndex + change + items.length) % items.length;
11665
+ items[nextIndex]?.focus();
11666
+ }
11667
+ }
11441
11668
  function digitTitle(id) {
11442
11669
  if (id === "arabicIndic") {
11443
11670
  return messages.arabicIndicDigits;
@@ -11495,6 +11722,50 @@ function DocumentInsertToolbar({
11495
11722
  messages.selectedBlockCount
11496
11723
  ] }) : null
11497
11724
  ] }),
11725
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.textFormatting, children: [
11726
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11727
+ "button",
11728
+ {
11729
+ type: "button",
11730
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11731
+ title: messages.bold,
11732
+ "aria-label": messages.bold,
11733
+ "aria-pressed": activeTextStyles.bold === true,
11734
+ disabled: !canFormatText,
11735
+ onMouseDown: (event) => event.preventDefault(),
11736
+ onClick: () => onToggleTextStyle?.("bold"),
11737
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--bold", "aria-hidden": "true", children: "\u0646\u0635" })
11738
+ }
11739
+ ),
11740
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11741
+ "button",
11742
+ {
11743
+ type: "button",
11744
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11745
+ title: messages.italic,
11746
+ "aria-label": messages.italic,
11747
+ "aria-pressed": activeTextStyles.italic === true,
11748
+ disabled: !canFormatText,
11749
+ onMouseDown: (event) => event.preventDefault(),
11750
+ onClick: () => onToggleTextStyle?.("italic"),
11751
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--italic", "aria-hidden": "true", children: "\u0646\u0635" })
11752
+ }
11753
+ ),
11754
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11755
+ "button",
11756
+ {
11757
+ type: "button",
11758
+ className: "butex-document2-widget__icon-btn butex-document2-widget__format-btn",
11759
+ title: messages.underline,
11760
+ "aria-label": messages.underline,
11761
+ "aria-pressed": activeTextStyles.underline === true,
11762
+ disabled: !canFormatText,
11763
+ onMouseDown: (event) => event.preventDefault(),
11764
+ onClick: () => onToggleTextStyle?.("underline"),
11765
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__format-icon butex-document2-widget__format-icon--underline", "aria-hidden": "true", children: "\u0646\u0635" })
11766
+ }
11767
+ )
11768
+ ] }),
11498
11769
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.structure, children: [
11499
11770
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11500
11771
  "button",
@@ -11546,35 +11817,57 @@ function DocumentInsertToolbar({
11546
11817
  )) }) : null
11547
11818
  ] })
11548
11819
  ] }),
11549
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: [
11550
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertCitation, "aria-label": messages.insertCitation, onClick: onInsertCitation, children: "[]" }),
11551
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertInternalRef, "aria-label": messages.insertInternalRef, onClick: onInsertInternalRef, children: "\xA7" }),
11552
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.insertBibliography, "aria-label": messages.insertBibliography, onClick: onInsertBibliography, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: [
11553
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11554
- "path",
11555
- {
11556
- fill: "currentColor",
11557
- 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"
11558
- }
11559
- ),
11560
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11561
- "path",
11562
- {
11563
- fill: "currentColor",
11564
- d: "M3.4 5.2h5.2v1H3.4zm0 2.1h4.2v1H3.4zm0 2.1h3.4v1H3.4z"
11565
- }
11566
- ),
11567
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
11568
- "path",
11569
- {
11570
- fill: "currentColor",
11571
- 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"
11572
- }
11573
- )
11574
- ] }) }) }),
11575
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageReferences, "aria-label": messages.manageReferences, onClick: onManageReferences, children: "\u2630" }),
11576
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.manageLabels, "aria-label": messages.manageLabels, onClick: onManageLabels, children: "\u2317" })
11577
- ] }),
11820
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.citations, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__references-menu", ref: referencesMenuRef, children: [
11821
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
11822
+ "button",
11823
+ {
11824
+ ref: referencesTriggerRef,
11825
+ type: "button",
11826
+ className: "butex-document2-widget__icon-btn butex-document2-widget__references-trigger",
11827
+ title: messages.citations,
11828
+ "aria-label": messages.citations,
11829
+ "aria-haspopup": "menu",
11830
+ "aria-expanded": referencesMenuOpen,
11831
+ onClick: () => setReferencesMenuOpen((open) => !open),
11832
+ children: [
11833
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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" }) }) }),
11834
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.citations }),
11835
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__menu-chevron", "aria-hidden": "true", children: "\u2304" })
11836
+ ]
11837
+ }
11838
+ ),
11839
+ referencesMenuOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
11840
+ "div",
11841
+ {
11842
+ className: "butex-document2-widget__references-menu-options",
11843
+ role: "menu",
11844
+ "aria-label": messages.citations,
11845
+ onKeyDown: moveReferenceMenuFocus,
11846
+ children: [
11847
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.insertCitation, onClick: () => runReferenceAction(onInsertCitation), children: [
11848
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "[]" }),
11849
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.insertCitation })
11850
+ ] }),
11851
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.insertInternalRef, onClick: () => runReferenceAction(onInsertInternalRef), children: [
11852
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "\xA7" }),
11853
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.insertInternalRef })
11854
+ ] }),
11855
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.insertBibliography, onClick: () => runReferenceAction(onInsertBibliography), children: [
11856
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "butex-document2-widget__icon-bibliography", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { viewBox: "0 0 16 16", width: "14", height: "14", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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" }) }) }),
11857
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.insertBibliography })
11858
+ ] }),
11859
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.manageReferences, onClick: () => runReferenceAction(onManageReferences), children: [
11860
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "\u2630" }),
11861
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.manageReferences })
11862
+ ] }),
11863
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", role: "menuitem", title: messages.manageLabels, onClick: () => runReferenceAction(onManageLabels), children: [
11864
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", children: "\u2317" }),
11865
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: messages.manageLabels })
11866
+ ] })
11867
+ ]
11868
+ }
11869
+ ) : null
11870
+ ] }) }),
11578
11871
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "butex-document2-widget__toolbar-group", role: "group", "aria-label": messages.lists, children: [
11579
11872
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.bulletedList, "aria-label": messages.bulletedList, onClick: onAddList, children: "\u2022\u2261" }),
11580
11873
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "butex-document2-widget__icon-btn", title: messages.numberedList, "aria-label": messages.numberedList, onClick: onAddEnumerate, children: "1." })
@@ -11589,7 +11882,17 @@ var import_jsx_runtime11 = require("react/jsx-runtime");
11589
11882
  function PreviewInlines({ inlines, output }) {
11590
11883
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_jsx_runtime11.Fragment, { children: inlines.map((inline, index) => {
11591
11884
  if (inline.kind === "text") {
11592
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: inline.text }, index);
11885
+ let content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: inline.text });
11886
+ if (inline.style?.bold) {
11887
+ content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { children: content });
11888
+ }
11889
+ if (inline.style?.italic) {
11890
+ content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("em", { children: content });
11891
+ }
11892
+ if (inline.style?.underline) {
11893
+ content = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "butex-document2-widget__preview-underline", children: content });
11894
+ }
11895
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: content }, index);
11593
11896
  }
11594
11897
  if (inline.kind === "cite") {
11595
11898
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "butex-document2-widget__preview-cite", children: inline.label }, inline.id);
@@ -14785,7 +15088,7 @@ function RefPickerPopover({
14785
15088
 
14786
15089
  // src/react-document2/editorFocus.ts
14787
15090
  function createEmptyDocument2EditorFocus() {
14788
- return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0 };
15091
+ return { blockId: null, fieldId: null, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 };
14789
15092
  }
14790
15093
  function findBlockIdForField(blocks, fieldId) {
14791
15094
  for (const block of blocks) {
@@ -14993,6 +15296,89 @@ var DOCUMENT2_WIDGET_CSS = `
14993
15296
  display: block;
14994
15297
  }
14995
15298
 
15299
+ .butex-document2-widget__format-btn {
15300
+ font-family: "Amiri", "Noto Serif Arabic", Georgia, serif;
15301
+ min-width: 36px;
15302
+ }
15303
+
15304
+ .butex-document2-widget__format-icon {
15305
+ direction: rtl;
15306
+ display: inline-block;
15307
+ font-size: 0.84rem;
15308
+ font-weight: 500;
15309
+ }
15310
+
15311
+ .butex-document2-widget__format-icon--bold {
15312
+ font-weight: 900;
15313
+ }
15314
+
15315
+ .butex-document2-widget__format-icon--italic {
15316
+ font-style: italic;
15317
+ transform: skewX(-7deg);
15318
+ }
15319
+
15320
+ .butex-document2-widget__format-icon--underline {
15321
+ text-decoration: underline;
15322
+ text-underline-offset: 0.12em;
15323
+ }
15324
+
15325
+ .butex-document2-widget__format-underline,
15326
+ .butex-document2-widget__preview-underline {
15327
+ text-decoration: underline;
15328
+ text-underline-offset: 0.12em;
15329
+ }
15330
+
15331
+ .butex-document2-widget__references-menu {
15332
+ position: relative;
15333
+ }
15334
+
15335
+ .butex-document2-widget__references-trigger {
15336
+ gap: 5px;
15337
+ white-space: nowrap;
15338
+ }
15339
+
15340
+ .butex-document2-widget__menu-chevron {
15341
+ color: var(--butex-document2-muted);
15342
+ font-size: 0.8rem;
15343
+ }
15344
+
15345
+ .butex-document2-widget__references-menu-options {
15346
+ background: var(--butex-document2-panel);
15347
+ border: 1px solid var(--butex-document2-border);
15348
+ border-radius: 8px;
15349
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--butex-document2-fg) 16%, transparent);
15350
+ display: grid;
15351
+ gap: 2px;
15352
+ inset-inline-start: 0;
15353
+ margin-top: 4px;
15354
+ min-width: max-content;
15355
+ padding: 4px;
15356
+ position: absolute;
15357
+ top: 100%;
15358
+ z-index: 30;
15359
+ }
15360
+
15361
+ .butex-document2-widget__references-menu-options button {
15362
+ align-items: center;
15363
+ background: transparent;
15364
+ border: 0;
15365
+ border-radius: 6px;
15366
+ color: var(--butex-document2-fg);
15367
+ cursor: pointer;
15368
+ display: grid;
15369
+ font: inherit;
15370
+ gap: 8px;
15371
+ grid-template-columns: 18px 1fr;
15372
+ padding: 7px 9px;
15373
+ text-align: start;
15374
+ }
15375
+
15376
+ .butex-document2-widget__references-menu-options button:hover,
15377
+ .butex-document2-widget__references-menu-options button:focus-visible {
15378
+ background: var(--butex-document2-accent-bg);
15379
+ outline: none;
15380
+ }
15381
+
14996
15382
  .butex-document2-widget__float-meta {
14997
15383
  display: grid;
14998
15384
  gap: 8px;
@@ -15436,6 +15822,19 @@ var DOCUMENT2_WIDGET_CSS = `
15436
15822
  border-color: color-mix(in srgb, var(--butex-document2-text-token-border) 55%, var(--butex-document2-accent));
15437
15823
  }
15438
15824
 
15825
+ .butex-document2-widget__inline-text[data-bold="true"] {
15826
+ font-weight: 700;
15827
+ }
15828
+
15829
+ .butex-document2-widget__inline-text[data-italic="true"] {
15830
+ font-style: italic;
15831
+ }
15832
+
15833
+ .butex-document2-widget__inline-text[data-underline="true"] {
15834
+ text-decoration: underline;
15835
+ text-underline-offset: 0.12em;
15836
+ }
15837
+
15439
15838
  .butex-document2-widget__inline-field input {
15440
15839
  min-width: 12ch;
15441
15840
  }
@@ -16453,6 +16852,50 @@ function topLevelBlockIdForField(document2, fieldId) {
16453
16852
  function topLevelBlockIdForToken(document2, tokenId) {
16454
16853
  return document2.blocks.find((block) => blockHasToken(block, tokenId))?.id ?? null;
16455
16854
  }
16855
+ function findFormatTextToken(document2, focus) {
16856
+ if (!focus.fieldId || !focus.textTokenId || focus.selectionEnd <= focus.selectionStart) {
16857
+ return null;
16858
+ }
16859
+ function textTokenFromField(field) {
16860
+ if (field.id !== focus.fieldId) {
16861
+ return null;
16862
+ }
16863
+ const token = field.tokens.find((entry) => entry.id === focus.textTokenId && entry.kind === "text");
16864
+ return token ?? null;
16865
+ }
16866
+ function visit(blocks) {
16867
+ for (const block of blocks) {
16868
+ if (block.kind === "textBlock") {
16869
+ const token = block.command === "\\paragraph" ? textTokenFromField(block.field) : null;
16870
+ if (token) {
16871
+ return token;
16872
+ }
16873
+ } else if (block.kind === "list") {
16874
+ for (const item of block.items) {
16875
+ const itemToken = textTokenFromField(item.field);
16876
+ if (itemToken) {
16877
+ return itemToken;
16878
+ }
16879
+ const nestedToken = visit(item.blocks);
16880
+ if (nestedToken) {
16881
+ return nestedToken;
16882
+ }
16883
+ }
16884
+ } else if (block.kind === "table") {
16885
+ for (const row of block.rows) {
16886
+ for (const cell of row) {
16887
+ const token = textTokenFromField(cell);
16888
+ if (token) {
16889
+ return token;
16890
+ }
16891
+ }
16892
+ }
16893
+ }
16894
+ }
16895
+ return null;
16896
+ }
16897
+ return visit(document2.blocks);
16898
+ }
16456
16899
  function formatDocument2Diagnostic(d, messages) {
16457
16900
  if (d.message === "math_objects order mismatch") {
16458
16901
  return `${messages.importOrderMismatch} ${messages.path}: ${d.path}`;
@@ -16571,6 +17014,9 @@ function ButexDocumentEditor2({
16571
17014
  const selectionCount = blockSelection ? blockSelection.to - blockSelection.from + 1 : 0;
16572
17015
  const canMoveSelectionUp = Boolean(blockSelection && blockSelection.from > 0);
16573
17016
  const canMoveSelectionDown = Boolean(blockSelection && blockSelection.to < documentNode.blocks.length - 1);
17017
+ const formatTextToken = (0, import_react13.useMemo)(() => findFormatTextToken(documentNode, editorFocus), [documentNode, editorFocus]);
17018
+ const canFormatText = formatTextToken !== null;
17019
+ const activeTextStyles = formatTextToken?.style ?? {};
16574
17020
  function clearBlockSelection() {
16575
17021
  setBlockSelectionState(emptyBlockSelection());
16576
17022
  }
@@ -16771,15 +17217,15 @@ function ButexDocumentEditor2({
16771
17217
  root.addEventListener("keydown", onKeyDown);
16772
17218
  return () => root.removeEventListener("keydown", onKeyDown);
16773
17219
  }, [editorOpen]);
16774
- function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset) {
17220
+ function rememberFieldFocus(blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd) {
16775
17221
  openBlock(blockId);
16776
17222
  clearBlockSelection();
16777
- setEditorFocus({ blockId, fieldId, textTokenId, caretOffset });
17223
+ setEditorFocus({ blockId, fieldId, textTokenId, caretOffset, selectionStart, selectionEnd });
16778
17224
  }
16779
17225
  function rememberMathFocus(blockId, fieldId) {
16780
17226
  openBlock(blockId);
16781
17227
  clearBlockSelection();
16782
- setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0 });
17228
+ setEditorFocus({ blockId, fieldId, textTokenId: null, caretOffset: 0, selectionStart: 0, selectionEnd: 0 });
16783
17229
  }
16784
17230
  function rememberBlockFocus(blockId) {
16785
17231
  openBlock(blockId);
@@ -17042,6 +17488,22 @@ function ButexDocumentEditor2({
17042
17488
  function deleteRefToken(tokenId) {
17043
17489
  applyDocument(removeRefTokenById(documentRef.current, tokenId), "immediate");
17044
17490
  }
17491
+ function toggleInlineTextStyle(styleName) {
17492
+ if (!canFormatText || !editorFocus.fieldId || !editorFocus.textTokenId) {
17493
+ return;
17494
+ }
17495
+ applyDocument(
17496
+ toggleTextTokenStyle(
17497
+ documentRef.current,
17498
+ editorFocus.fieldId,
17499
+ editorFocus.textTokenId,
17500
+ editorFocus.selectionStart,
17501
+ editorFocus.selectionEnd,
17502
+ styleName
17503
+ ),
17504
+ "immediate"
17505
+ );
17506
+ }
17045
17507
  const showEditorPanel = !previewOnly && editorOpen;
17046
17508
  const showPreviewPanel = previewOnly || previewOpen;
17047
17509
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
@@ -17064,8 +17526,11 @@ function ButexDocumentEditor2({
17064
17526
  selectionCount,
17065
17527
  canMoveSelectionUp,
17066
17528
  canMoveSelectionDown,
17529
+ canFormatText,
17530
+ activeTextStyles,
17067
17531
  onUndo: undoDocument,
17068
17532
  onRedo: redoDocument,
17533
+ onToggleTextStyle: toggleInlineTextStyle,
17069
17534
  onMoveSelectionUp: () => moveSelectedBlocks(-1),
17070
17535
  onMoveSelectionDown: () => moveSelectedBlocks(1),
17071
17536
  onDeleteSelection: deleteSelectedBlocks,