@sofereditor/react 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1899,8 +1899,15 @@ function ImageEmbedViewImpl({
1899
1899
  }
1900
1900
  );
1901
1901
  }
1902
+ function decorateBlanks(text) {
1903
+ const segs = (0, import_core3.splitUnderscoreRuns)(text);
1904
+ if (segs.length === 1 && !segs[0].blank) return text;
1905
+ return segs.map(
1906
+ (s, i) => s.blank ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-blank": "true", style: import_core3.BLANK_STYLE, children: s.text }, i) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react8.Fragment, { children: s.text }, i)
1907
+ );
1908
+ }
1902
1909
  function wrap(text, attrs) {
1903
- let node = text;
1910
+ let node = decorateBlanks(text);
1904
1911
  if (!attrs) return node;
1905
1912
  if (attrs.bold) node = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: node });
1906
1913
  if (attrs.italic) node = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("em", { children: node });
@@ -1908,6 +1915,7 @@ function wrap(text, attrs) {
1908
1915
  if (attrs.strike) node = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("s", { children: node });
1909
1916
  const styleParts = {};
1910
1917
  if (attrs.color) styleParts.color = attrs.color;
1918
+ if (attrs.highlight) styleParts.backgroundColor = attrs.highlight;
1911
1919
  if (attrs.fontFamily) styleParts.fontFamily = attrs.fontFamily;
1912
1920
  if (attrs.fontSize) styleParts.fontSize = attrs.fontSize;
1913
1921
  if (Object.keys(styleParts).length > 0) {
@@ -2154,6 +2162,7 @@ function NodeView({ block, index, children, fragment, tableFragment }) {
2154
2162
  function commonBlockProps(attrs, index, type, fragment) {
2155
2163
  const style = {};
2156
2164
  if (attrs.align) style.textAlign = attrs.align;
2165
+ Object.assign(style, (0, import_core4.answerLineStyle)(attrs) ?? {});
2157
2166
  const base = {
2158
2167
  "data-block-index": index,
2159
2168
  "data-block-type": type,
@@ -2173,6 +2182,12 @@ function fragmentClass(f) {
2173
2182
  function alignClass(a) {
2174
2183
  return a ? ` ed-align-${a}` : "";
2175
2184
  }
2185
+ function cellStyle(attrs, border) {
2186
+ const style = { ...border };
2187
+ if (attrs?.align) style.textAlign = attrs.align;
2188
+ if (attrs?.bgColor) style.backgroundColor = attrs.bgColor;
2189
+ return Object.keys(style).length > 0 ? style : void 0;
2190
+ }
2176
2191
  function clampLevel(l) {
2177
2192
  if (l === 2 || l === 3 || l === 4 || l === 5 || l === 6) return l;
2178
2193
  return 1;
@@ -2226,7 +2241,16 @@ function TableView({ block, index, tableFragment }) {
2226
2241
  "data-cell-colspan": colspan,
2227
2242
  rowSpan: rowspan > 1 ? rowspan : void 0,
2228
2243
  colSpan: colspan > 1 ? colspan : void 0,
2229
- style: cell?.attrs.align ? { textAlign: cell.attrs.align } : void 0,
2244
+ style: cellStyle(
2245
+ cell?.attrs,
2246
+ // rowStart/rowEnd são os limites do FRAGMENTO: numa tabela
2247
+ // quebrada entre páginas, cada página fecha a própria caixa.
2248
+ (0, import_core4.cellBorderStyle)(
2249
+ block.attrs.borderPreset,
2250
+ { row: r, col: c, rowspan, colspan, cols, rowStart, rowEnd },
2251
+ "screen"
2252
+ )
2253
+ ),
2230
2254
  className: inRect ? "ed-cell ed-cell--selected" : "ed-cell",
2231
2255
  children: renderInline(delta, `t${index}-c${flat}`)
2232
2256
  },
@@ -3001,6 +3025,24 @@ function useEditor(opts = {}) {
3001
3025
  }
3002
3026
  return getBlockAttr("align");
3003
3027
  }, [doc, getBlockAttr]);
3028
+ const setCellBackground = (0, import_react13.useCallback)((color) => {
3029
+ (0, import_core5.setCellAttr)(ctxRef.current, "bgColor", color);
3030
+ }, []);
3031
+ const getCellBackground = (0, import_react13.useCallback)(() => {
3032
+ const sel = selectionRef.current;
3033
+ if (sel.focus.cellIndex == null || !doc.isTable(sel.focus.blockIndex)) return void 0;
3034
+ return doc.getCellAttrs(sel.focus.blockIndex, sel.focus.cellIndex).bgColor;
3035
+ }, [doc]);
3036
+ const setTableBorderPreset = (0, import_react13.useCallback)((preset) => {
3037
+ const sel = selectionRef.current;
3038
+ if (!doc.isTable(sel.focus.blockIndex)) return;
3039
+ (0, import_core5.setBlockAttrAtIndex)(ctxRef.current, sel.focus.blockIndex, "borderPreset", preset);
3040
+ }, [doc]);
3041
+ const getTableBorderPreset = (0, import_react13.useCallback)(() => {
3042
+ const sel = selectionRef.current;
3043
+ if (!doc.isTable(sel.focus.blockIndex)) return "all";
3044
+ return doc.getBlockAttrs(sel.focus.blockIndex).borderPreset ?? "all";
3045
+ }, [doc]);
3004
3046
  const isListActive = (0, import_react13.useCallback)(
3005
3047
  (kind) => {
3006
3048
  const sel = selectionRef.current;
@@ -3034,6 +3076,9 @@ function useEditor(opts = {}) {
3034
3076
  const insertTable = (0, import_react13.useCallback)((rows, cols) => {
3035
3077
  (0, import_core5.insertTable)(ctxRef.current, rows, cols);
3036
3078
  }, []);
3079
+ const insertAnswerLines = (0, import_react13.useCallback)((count, spacing) => {
3080
+ (0, import_core5.insertAnswerLines)(ctxRef.current, count, spacing);
3081
+ }, []);
3037
3082
  const deleteTable = (0, import_react13.useCallback)((blockIndex) => {
3038
3083
  const idx = blockIndex ?? selectionRef.current.focus.blockIndex;
3039
3084
  (0, import_core5.deleteTable)(ctxRef.current, idx);
@@ -3240,6 +3285,10 @@ function useEditor(opts = {}) {
3240
3285
  setBlockAttr,
3241
3286
  setAlign,
3242
3287
  getAlign,
3288
+ setCellBackground,
3289
+ getCellBackground,
3290
+ setTableBorderPreset,
3291
+ getTableBorderPreset,
3243
3292
  isListActive,
3244
3293
  toggleList,
3245
3294
  indentList,
@@ -3250,6 +3299,7 @@ function useEditor(opts = {}) {
3250
3299
  resolveLinkRequest,
3251
3300
  isInTable,
3252
3301
  getTableLocation,
3302
+ insertAnswerLines,
3253
3303
  insertTable,
3254
3304
  deleteTable,
3255
3305
  insertRowAbove,
@@ -4204,6 +4254,7 @@ function caretRangeFromPoint(x, y) {
4204
4254
 
4205
4255
  // src/Toolbar.tsx
4206
4256
  var import_react15 = require("react");
4257
+ var import_core7 = require("@sofereditor/core");
4207
4258
  var import_jsx_runtime13 = require("react/jsx-runtime");
4208
4259
  var FONT_FAMILIES = ["Arial"];
4209
4260
  var FONT_SIZES = ["8pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", "24pt", "32pt", "48pt"];
@@ -4270,6 +4321,9 @@ function Toolbar({ className }) {
4270
4321
  const onColorChange = (e) => {
4271
4322
  setMark("color", e.target.value);
4272
4323
  };
4324
+ const onHighlightChange = (e) => {
4325
+ setMark("highlight", e.target.value);
4326
+ };
4273
4327
  const onFontFamilyChange = (e) => {
4274
4328
  const v = e.target.value;
4275
4329
  if (v === "") removeMark("fontFamily");
@@ -4334,6 +4388,7 @@ function Toolbar({ className }) {
4334
4388
  [active.link, editor, removeMark, setMark]
4335
4389
  );
4336
4390
  const colorValue = typeof active.color === "string" ? active.color : "#000000";
4391
+ const highlightValue = typeof active.highlight === "string" ? active.highlight : "#fff176";
4337
4392
  const fontFamilyValue = typeof active.fontFamily === "string" ? active.fontFamily : "";
4338
4393
  const fontSizeValue = typeof active.fontSize === "string" ? active.fontSize : "";
4339
4394
  const blockValue = blockSelectValue(blockType, blockLevel);
@@ -4397,6 +4452,40 @@ function Toolbar({ className }) {
4397
4452
  },
4398
4453
  children: "\u232B"
4399
4454
  }
4455
+ ),
4456
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", title: "Marca-texto", children: [
4457
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4458
+ "span",
4459
+ {
4460
+ className: "ed-toolbar-swatch ed-toolbar-swatch--highlight",
4461
+ "aria-hidden": true,
4462
+ style: { background: highlightValue }
4463
+ }
4464
+ ),
4465
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4466
+ "input",
4467
+ {
4468
+ type: "color",
4469
+ value: highlightValue,
4470
+ onChange: onHighlightChange,
4471
+ onMouseDown: stop,
4472
+ "aria-label": "Marca-texto"
4473
+ }
4474
+ )
4475
+ ] }),
4476
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4477
+ "button",
4478
+ {
4479
+ type: "button",
4480
+ className: "ed-toolbar-btn",
4481
+ title: "Remover marca-texto",
4482
+ onMouseDown: stop,
4483
+ onClick: (e) => {
4484
+ e.preventDefault();
4485
+ removeMark("highlight");
4486
+ },
4487
+ children: "\u232B"
4488
+ }
4400
4489
  )
4401
4490
  ] }),
4402
4491
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
@@ -4526,7 +4615,10 @@ function Toolbar({ className }) {
4526
4615
  children: "\u{1F517}"
4527
4616
  }
4528
4617
  ) }),
4529
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Group, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TableMenu, {}) }),
4618
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
4619
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TableMenu, {}),
4620
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AnswerLinesMenu, {})
4621
+ ] }),
4530
4622
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Group, { children: [
4531
4623
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4532
4624
  "button",
@@ -4612,6 +4704,102 @@ function Toolbar({ className }) {
4612
4704
  }
4613
4705
  );
4614
4706
  }
4707
+ function AnswerLinesMenu() {
4708
+ const editor = useEditorContext();
4709
+ const [open, setOpen] = (0, import_react15.useState)(false);
4710
+ const [count, setCount] = (0, import_react15.useState)(5);
4711
+ const [spacing, setSpacing] = (0, import_react15.useState)(1.5);
4712
+ const rootRef = (0, import_react15.useRef)(null);
4713
+ (0, import_react15.useEffect)(() => {
4714
+ if (!open) return;
4715
+ const onDown = (e) => {
4716
+ if (!rootRef.current) return;
4717
+ if (rootRef.current.contains(e.target)) return;
4718
+ setOpen(false);
4719
+ };
4720
+ document.addEventListener("mousedown", onDown);
4721
+ return () => document.removeEventListener("mousedown", onDown);
4722
+ }, [open]);
4723
+ const clamped = Math.max(1, Math.min(import_core7.ANSWER_LINE_MAX, Math.trunc(count) || 1));
4724
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { ref: rootRef, className: "ed-toolbar-tablemenu", children: [
4725
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4726
+ "button",
4727
+ {
4728
+ type: "button",
4729
+ className: "ed-toolbar-btn",
4730
+ title: "Linhas de resposta",
4731
+ "aria-pressed": open,
4732
+ onMouseDown: (e) => e.preventDefault(),
4733
+ onClick: (e) => {
4734
+ e.preventDefault();
4735
+ setOpen((v) => !v);
4736
+ },
4737
+ children: "\u2630"
4738
+ }
4739
+ ),
4740
+ open && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4741
+ "div",
4742
+ {
4743
+ className: "ed-table-popover ed-answerlines-popover",
4744
+ role: "menu",
4745
+ onMouseDown: (e) => {
4746
+ const t = e.target;
4747
+ if (!t || t.tagName !== "INPUT" && t.tagName !== "BUTTON" && t.tagName !== "SELECT") {
4748
+ e.preventDefault();
4749
+ }
4750
+ },
4751
+ children: [
4752
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4753
+ "Quantidade",
4754
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4755
+ "input",
4756
+ {
4757
+ type: "number",
4758
+ min: 1,
4759
+ max: import_core7.ANSWER_LINE_MAX,
4760
+ value: count,
4761
+ onChange: (e) => setCount(Number(e.target.value)),
4762
+ "aria-label": "Quantidade de linhas"
4763
+ }
4764
+ )
4765
+ ] }),
4766
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4767
+ "Entrelinha",
4768
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4769
+ "select",
4770
+ {
4771
+ value: String(spacing),
4772
+ onChange: (e) => setSpacing(Number(e.target.value)),
4773
+ "aria-label": "Entrelinha",
4774
+ children: [
4775
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "1", children: "Simples" }),
4776
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "1.5", children: "1,5" }),
4777
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "2", children: "Duplo" })
4778
+ ]
4779
+ }
4780
+ )
4781
+ ] }),
4782
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4783
+ "button",
4784
+ {
4785
+ type: "button",
4786
+ onClick: () => {
4787
+ setOpen(false);
4788
+ editor.insertAnswerLines(clamped, spacing);
4789
+ },
4790
+ children: [
4791
+ "Inserir ",
4792
+ clamped,
4793
+ " linha",
4794
+ clamped > 1 ? "s" : ""
4795
+ ]
4796
+ }
4797
+ )
4798
+ ]
4799
+ }
4800
+ )
4801
+ ] });
4802
+ }
4615
4803
  function TableMenu() {
4616
4804
  const editor = useEditorContext();
4617
4805
  const inTable = editor.isInTable();
@@ -4714,6 +4902,51 @@ function TableMenu() {
4714
4902
  setOpen(false);
4715
4903
  editor.splitCell();
4716
4904
  }, children: "Dividir c\xE9lulas mescladas" }),
4905
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4906
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label ed-table-bgcolor", children: [
4907
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4908
+ "span",
4909
+ {
4910
+ className: "ed-toolbar-swatch",
4911
+ "aria-hidden": true,
4912
+ style: { background: editor.getCellBackground() ?? "#ffffff" }
4913
+ }
4914
+ ),
4915
+ "Cor de fundo da c\xE9lula",
4916
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4917
+ "input",
4918
+ {
4919
+ type: "color",
4920
+ value: editor.getCellBackground() ?? "#ffffff",
4921
+ onChange: (e) => editor.setCellBackground(e.target.value),
4922
+ "aria-label": "Cor de fundo da c\xE9lula"
4923
+ }
4924
+ )
4925
+ ] }),
4926
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", onClick: () => {
4927
+ setOpen(false);
4928
+ editor.setCellBackground(null);
4929
+ }, children: "Remover cor de fundo" }),
4930
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4931
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "ed-toolbar-label", children: [
4932
+ "Bordas",
4933
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4934
+ "select",
4935
+ {
4936
+ value: editor.getTableBorderPreset(),
4937
+ onChange: (e) => editor.setTableBorderPreset(e.target.value),
4938
+ "aria-label": "Bordas da tabela",
4939
+ children: [
4940
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "all", children: "Todas" }),
4941
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "outer", children: "S\xF3 externas" }),
4942
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "horizontal", children: "S\xF3 horizontais" }),
4943
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "vertical", children: "S\xF3 verticais" }),
4944
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "none", children: "Nenhuma" })
4945
+ ]
4946
+ }
4947
+ )
4948
+ ] }),
4949
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("hr", {}),
4717
4950
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4718
4951
  "button",
4719
4952
  {