@meistrari/minuta-editor 1.0.1 → 1.0.2
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/chunks/docxExport.mjs +110 -2
- package/package.json +1 -1
|
@@ -70,6 +70,8 @@ function parseStyle(style) {
|
|
|
70
70
|
if (bg && bg.toLowerCase() !== "ffffff")
|
|
71
71
|
info.highlight = bg;
|
|
72
72
|
info.size = cssSizeToHalfPoints(getProp(style, "font-size"));
|
|
73
|
+
if (getProp(style, "text-transform") === "uppercase")
|
|
74
|
+
info.caps = true;
|
|
73
75
|
const family = getProp(style, "font-family");
|
|
74
76
|
if (family)
|
|
75
77
|
info.font = family.split(",")[0].trim().replace(/["']/g, "");
|
|
@@ -123,6 +125,7 @@ function measureEl(el) {
|
|
|
123
125
|
color: cssColorToHex(cs.color),
|
|
124
126
|
bold: (Number.parseInt(cs.fontWeight) || 400) >= 600,
|
|
125
127
|
italics: cs.fontStyle.includes("italic"),
|
|
128
|
+
caps: cs.textTransform === "uppercase",
|
|
126
129
|
align: cssAlignToDocx(cs.textAlign)
|
|
127
130
|
};
|
|
128
131
|
}
|
|
@@ -145,6 +148,7 @@ function runOptions(info) {
|
|
|
145
148
|
italics: info.italics,
|
|
146
149
|
underline: info.underline ? {} : void 0,
|
|
147
150
|
strike: info.strike,
|
|
151
|
+
allCaps: info.caps || void 0,
|
|
148
152
|
color: info.color,
|
|
149
153
|
size: info.size,
|
|
150
154
|
font: info.font,
|
|
@@ -165,6 +169,22 @@ function primaryFont(cs) {
|
|
|
165
169
|
const family = cs.fontFamily?.split(",")[0]?.trim().replace(/["']/g, "");
|
|
166
170
|
return family || void 0;
|
|
167
171
|
}
|
|
172
|
+
function withMarks(base, marks) {
|
|
173
|
+
const info = { ...base };
|
|
174
|
+
for (const mark of marks) {
|
|
175
|
+
if (mark.type.name === "bold")
|
|
176
|
+
info.bold = true;
|
|
177
|
+
if (mark.type.name === "italic")
|
|
178
|
+
info.italics = true;
|
|
179
|
+
if (mark.type.name === "underline")
|
|
180
|
+
info.underline = true;
|
|
181
|
+
if (mark.type.name === "strike")
|
|
182
|
+
info.strike = true;
|
|
183
|
+
if (mark.type.name === "inlineStyle")
|
|
184
|
+
Object.assign(info, cleanUndefined(parseStyle(mark.attrs.style)));
|
|
185
|
+
}
|
|
186
|
+
return info;
|
|
187
|
+
}
|
|
168
188
|
function inlineToRuns(node, base, ctx) {
|
|
169
189
|
const runs = [];
|
|
170
190
|
node.forEach((child) => {
|
|
@@ -186,7 +206,7 @@ function inlineToRuns(node, base, ctx) {
|
|
|
186
206
|
}
|
|
187
207
|
if (child.type.name === "variableChip") {
|
|
188
208
|
if (child.attrs.text)
|
|
189
|
-
runs.push(new TextRun({ text: child.attrs.text, ...runOptions(base) }));
|
|
209
|
+
runs.push(new TextRun({ text: child.attrs.text, ...runOptions(withMarks(base, child.marks)) }));
|
|
190
210
|
return;
|
|
191
211
|
}
|
|
192
212
|
if (child.type.name === "hardBreak") {
|
|
@@ -245,6 +265,7 @@ function domInlineToRuns(root, fallback = {}) {
|
|
|
245
265
|
italics: cs.fontStyle.includes("italic") || void 0,
|
|
246
266
|
underline: cs.textDecorationLine?.includes("underline") ? {} : void 0,
|
|
247
267
|
strike: cs.textDecorationLine?.includes("line-through") || void 0,
|
|
268
|
+
allCaps: cs.textTransform === "uppercase" || void 0,
|
|
248
269
|
characterSpacing: TRACKING_COMPENSATION_TW
|
|
249
270
|
}));
|
|
250
271
|
}
|
|
@@ -296,7 +317,7 @@ function spacingFrom(m, style) {
|
|
|
296
317
|
function textBlockToParagraph(node, pos, ctx, opts = {}) {
|
|
297
318
|
const m = measureAt(ctx, pos);
|
|
298
319
|
const style = node.attrs.style;
|
|
299
|
-
const base = m ? { bold: m.bold, italics: m.italics, color: m.color, size: m.sizeHalf, font: primaryFont(m.cs) } : parseStyle(style);
|
|
320
|
+
const base = m ? { bold: m.bold, italics: m.italics, caps: m.caps, color: m.color, size: m.sizeHalf, font: primaryFont(m.cs) } : parseStyle(style);
|
|
300
321
|
if (ctx.extraTrackingTw)
|
|
301
322
|
base.trackingTw = ctx.extraTrackingTw;
|
|
302
323
|
const ownIndent = m ? m.marginLeftTw + Math.round((Number.parseFloat(m.cs.paddingLeft) || 0) * PX_TO_TWIP) : cssLengthToTwips(getProp(style, "margin-left")) ?? 0;
|
|
@@ -652,6 +673,66 @@ function isFlexRow(node) {
|
|
|
652
673
|
return false;
|
|
653
674
|
return node.childCount > 1;
|
|
654
675
|
}
|
|
676
|
+
function islandImage(node) {
|
|
677
|
+
if (node.type.name !== "htmlIsland")
|
|
678
|
+
return null;
|
|
679
|
+
const tpl = document.createElement("template");
|
|
680
|
+
tpl.innerHTML = node.attrs.html || "";
|
|
681
|
+
const el = tpl.content.firstElementChild;
|
|
682
|
+
if (!el)
|
|
683
|
+
return null;
|
|
684
|
+
const img = el.tagName === "IMG" ? el : el.querySelector("img");
|
|
685
|
+
return img instanceof HTMLImageElement ? img : null;
|
|
686
|
+
}
|
|
687
|
+
function collectImageIslands(node, pos) {
|
|
688
|
+
if (islandImage(node))
|
|
689
|
+
return [{ node, pos }];
|
|
690
|
+
if (node.type.name !== "styledDiv")
|
|
691
|
+
return null;
|
|
692
|
+
const out = [];
|
|
693
|
+
let childPos = pos + 1;
|
|
694
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
695
|
+
const child = node.child(i);
|
|
696
|
+
if (!(child.isTextblock && child.content.size === 0)) {
|
|
697
|
+
const inner = collectImageIslands(child, childPos);
|
|
698
|
+
if (!inner)
|
|
699
|
+
return null;
|
|
700
|
+
out.push(...inner);
|
|
701
|
+
}
|
|
702
|
+
childPos += child.nodeSize;
|
|
703
|
+
}
|
|
704
|
+
return out.length ? out : null;
|
|
705
|
+
}
|
|
706
|
+
async function inlineImagesParagraph(islands, innerPx, ctx) {
|
|
707
|
+
const GAP_PX = 8;
|
|
708
|
+
const items = [];
|
|
709
|
+
for (const { node, pos } of islands) {
|
|
710
|
+
const img = islandImage(node);
|
|
711
|
+
const src = await srcToDataUrl(img.getAttribute("src") || "");
|
|
712
|
+
if (!src)
|
|
713
|
+
continue;
|
|
714
|
+
const live = domAt(ctx, pos)?.querySelector("img");
|
|
715
|
+
const styleW = Number.parseFloat(getProp(img.getAttribute("style"), "width") ?? "");
|
|
716
|
+
const widthPx = live?.offsetWidth || 0 || (Number.isFinite(styleW) && styleW > 0 ? styleW : 0) || Math.min(innerPx, 200);
|
|
717
|
+
items.push({ src, widthPx });
|
|
718
|
+
}
|
|
719
|
+
if (!items.length)
|
|
720
|
+
return null;
|
|
721
|
+
const total = items.reduce((a, b) => a + b.widthPx, 0) + GAP_PX * (items.length - 1);
|
|
722
|
+
const scale = innerPx > 0 && total > innerPx ? (innerPx - GAP_PX * (items.length - 1)) / (total - GAP_PX * (items.length - 1)) : 1;
|
|
723
|
+
const runs = [];
|
|
724
|
+
for (let i = 0; i < items.length; i++) {
|
|
725
|
+
const run = await dataUrlToImageRun(items[i].src, Math.max(1, Math.round(items[i].widthPx * scale)));
|
|
726
|
+
if (!run)
|
|
727
|
+
continue;
|
|
728
|
+
if (runs.length)
|
|
729
|
+
runs.push(new TextRun({ text: " " }));
|
|
730
|
+
runs.push(run);
|
|
731
|
+
}
|
|
732
|
+
if (!runs.length)
|
|
733
|
+
return null;
|
|
734
|
+
return new Paragraph({ children: runs, alignment: AlignmentType.CENTER, spacing: { after: 0, before: 0 } });
|
|
735
|
+
}
|
|
655
736
|
async function tableToDocx(node, pos, contentWidthPx, ctx) {
|
|
656
737
|
const tableDomRaw = domAt(ctx, pos);
|
|
657
738
|
const tableEl = tableDomRaw?.tagName === "TABLE" ? tableDomRaw : tableDomRaw?.querySelector("table") ?? null;
|
|
@@ -681,9 +762,36 @@ async function tableToDocx(node, pos, contentWidthPx, ctx) {
|
|
|
681
762
|
}
|
|
682
763
|
}
|
|
683
764
|
const cellCtx = { ...ctx, insideCell: true, indentLeft: 0, lastAfterTw: 0, pending: { breakBefore: false }, extraTrackingTw: extraTrackingTw || void 0 };
|
|
765
|
+
const cellInnerPx = cellM?.el ? cellM.el.clientWidth - (Number.parseFloat(cellM.cs.paddingLeft) || 0) - (Number.parseFloat(cellM.cs.paddingRight) || 0) : contentWidthPx / 2;
|
|
766
|
+
const isEmptyBlock = (n) => n.isTextblock && n.content.size === 0;
|
|
767
|
+
const besideImage = (k) => k > 0 && !!collectImageIslands(cellNode.child(k - 1), 0) || k + 1 < cellNode.childCount && !!collectImageIslands(cellNode.child(k + 1), 0);
|
|
684
768
|
let childPos = cellPos + 1;
|
|
685
769
|
for (let k = 0; k < cellNode.childCount; k++) {
|
|
686
770
|
const child = cellNode.child(k);
|
|
771
|
+
if (isEmptyBlock(child) && besideImage(k)) {
|
|
772
|
+
childPos += child.nodeSize;
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
const islands = collectImageIslands(child, childPos);
|
|
776
|
+
if (islands?.length) {
|
|
777
|
+
let end = k + 1;
|
|
778
|
+
let endPos = childPos + child.nodeSize;
|
|
779
|
+
while (end < cellNode.childCount) {
|
|
780
|
+
const next = cellNode.child(end);
|
|
781
|
+
const more = collectImageIslands(next, endPos);
|
|
782
|
+
if (!more?.length)
|
|
783
|
+
break;
|
|
784
|
+
islands.push(...more);
|
|
785
|
+
endPos += next.nodeSize;
|
|
786
|
+
end++;
|
|
787
|
+
}
|
|
788
|
+
const para = await inlineImagesParagraph(islands, cellInnerPx, cellCtx);
|
|
789
|
+
if (para)
|
|
790
|
+
cellContent.push(para);
|
|
791
|
+
childPos = endPos;
|
|
792
|
+
k = end - 1;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
687
795
|
if (child.isTextblock)
|
|
688
796
|
cellContent.push(textBlockToParagraph(child, childPos, cellCtx));
|
|
689
797
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meistrari/minuta-editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Editor de minuta do defesa.ai sem framework: folha A4 paginada, edição rica, placeholders de variável e export DOCX fiel ao preview — monta em qualquer app (React, Vue, vanilla)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|