Skip to content

Commit 19779ca

Browse files
committed
chore: imageテストを現行実装に追従
1 parent 2cbf3b1 commit 19779ca

File tree

6 files changed

+163
-304
lines changed

6 files changed

+163
-304
lines changed

packages/parser/src/parser/rules/block/paragraph.ts

Lines changed: 4 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,16 @@ function processCloseSpanMarkers(elements: Element[]): Element[] {
2121
if (!elem) continue;
2222

2323
// Check for closeSpan marker
24-
// _closeSpan is on data directly, not data.attributes
2524
if (
2625
elem.element === "container" &&
2726
elem.data &&
2827
typeof elem.data === "object" &&
2928
"type" in elem.data &&
3029
elem.data.type === "span" &&
31-
"_closeSpan" in elem.data &&
32-
(elem.data as any)._closeSpan === true
30+
"attributes" in elem.data &&
31+
typeof elem.data.attributes === "object" &&
32+
elem.data.attributes &&
33+
"_closeSpan" in elem.data.attributes
3334
) {
3435
// Wrap all preceding content in a span
3536
if (result.length > 0) {
@@ -79,50 +80,6 @@ export const paragraphRule: BlockRule = {
7980
// Process closeSpan markers (for split spans)
8081
let elements = processCloseSpanMarkers(result.elements);
8182

82-
// Split paragraph at aligned images (they become block-level elements)
83-
// This also removes float center images (invalid in Wikidot)
84-
const splitResult = splitAtAlignedImages(elements);
85-
const hasAlignedImages = splitResult.some((part) => part.type === "image");
86-
87-
if (splitResult.length > 1 || hasAlignedImages) {
88-
// Return multiple elements: paragraphs and standalone images
89-
const outputElements: Element[] = [];
90-
for (const part of splitResult) {
91-
if (part.type === "image") {
92-
outputElements.push(part.element);
93-
} else if (part.elements.length > 0) {
94-
// Clean up paragraph elements
95-
const cleaned = cleanParagraphElements(part.elements);
96-
if (cleaned.length > 0) {
97-
outputElements.push({
98-
element: "container",
99-
data: {
100-
type: "paragraph",
101-
attributes: {},
102-
elements: cleaned,
103-
},
104-
});
105-
}
106-
}
107-
}
108-
if (outputElements.length === 0) {
109-
return { success: false };
110-
}
111-
return {
112-
success: true,
113-
elements: outputElements,
114-
consumed: result.consumed,
115-
};
116-
}
117-
118-
// Rebuild elements from splitResult (may have float center removed)
119-
elements = [];
120-
for (const part of splitResult) {
121-
if (part.type === "text") {
122-
elements.push(...part.elements);
123-
}
124-
}
125-
12683
// Remove trailing line-breaks (they shouldn't appear at end of paragraph)
12784
// Exception: line-breaks flagged by preserveTrailingLineBreak context are kept
12885
while (elements.length > 0 && elements[elements.length - 1]?.element === "line-break") {
@@ -198,95 +155,3 @@ function parseInlineContent(ctx: ParseContext): {
198155
// The parser will stop at double NEWLINE (paragraph break)
199156
return parseInlineUntil(ctx, "PARAGRAPH_BREAK" as any);
200157
}
201-
202-
type SplitPart = { type: "text"; elements: Element[] } | { type: "image"; element: Element };
203-
204-
/**
205-
* Split elements at aligned images
206-
* Aligned images become block-level elements, splitting the paragraph
207-
* Float center images are removed entirely (invalid in Wikidot)
208-
*/
209-
function splitAtAlignedImages(elements: Element[]): SplitPart[] {
210-
const parts: SplitPart[] = [];
211-
let currentText: Element[] = [];
212-
213-
for (let i = 0; i < elements.length; i++) {
214-
const elem = elements[i];
215-
if (!elem) continue;
216-
217-
if (isAlignedImage(elem)) {
218-
const imageData = (elem as any).data;
219-
// Float center is invalid - skip the image AND preceding text on same line
220-
if (imageData?.alignment?.float && imageData.alignment.align === "center") {
221-
// Remove text preceding the image on the same line (back to last line-break)
222-
while (currentText.length > 0) {
223-
const last = currentText[currentText.length - 1];
224-
if (last?.element === "line-break") {
225-
break;
226-
}
227-
currentText.pop();
228-
}
229-
// Also skip line-break after the image if present
230-
if (elements[i + 1]?.element === "line-break") {
231-
i++;
232-
}
233-
continue;
234-
}
235-
236-
// Save current text as a part
237-
if (currentText.length > 0) {
238-
parts.push({ type: "text", elements: [...currentText] });
239-
currentText = [];
240-
}
241-
// Add image as standalone element
242-
parts.push({ type: "image", element: elem });
243-
} else {
244-
currentText.push(elem);
245-
}
246-
}
247-
248-
// Add remaining text
249-
if (currentText.length > 0) {
250-
parts.push({ type: "text", elements: currentText });
251-
}
252-
253-
return parts;
254-
}
255-
256-
/**
257-
* Check if element is an aligned image (has alignment property)
258-
*/
259-
function isAlignedImage(elem: Element): boolean {
260-
if (elem.element !== "image") return false;
261-
const data = (elem as any).data;
262-
return data?.alignment != null;
263-
}
264-
265-
/**
266-
* Clean up paragraph elements (remove trailing/leading line-breaks)
267-
*/
268-
function cleanParagraphElements(elements: Element[]): Element[] {
269-
let result = [...elements];
270-
271-
// Remove trailing line-breaks
272-
while (result.length > 0 && result[result.length - 1]?.element === "line-break") {
273-
result.pop();
274-
}
275-
276-
// Remove trailing whitespace
277-
while (result.length > 0) {
278-
const last = result[result.length - 1];
279-
if (last?.element === "text" && typeof last.data === "string" && last.data.trim() === "") {
280-
result.pop();
281-
} else {
282-
break;
283-
}
284-
}
285-
286-
// Remove leading line-breaks
287-
while (result.length > 0 && result[0]?.element === "line-break") {
288-
result.shift();
289-
}
290-
291-
return result;
292-
}

packages/render/src/elements/container.ts

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
1-
import type { ContainerData, Element } from "@wdprlib/ast";
1+
import type { ContainerData } from "@wdprlib/ast";
22
import { isStringContainerType, isHeaderType, isAlignType } from "@wdprlib/ast";
33
import type { RenderContext } from "../context";
44
import { escapeAttr, sanitizeAttributes } from "../escape";
55
import { renderElements } from "../render";
66

7-
/**
8-
* Check if elements contain an inline image (image without alignment)
9-
* Wikidot skips <p> tags for paragraphs containing inline images
10-
*/
11-
function hasInlineImage(elements: Element[]): boolean {
12-
for (const elem of elements) {
13-
if (elem.element === "image") {
14-
const data = (elem as any).data;
15-
// Inline image = no alignment (alignment is null or undefined)
16-
if (data?.alignment == null) {
17-
return true;
18-
}
19-
}
20-
}
21-
return false;
22-
}
23-
247
/** Render a container element */
258
export function renderContainer(ctx: RenderContext, data: ContainerData): void {
269
const { type, attributes, elements } = data;
@@ -70,14 +53,9 @@ function renderStringContainer(
7053
): void {
7154
switch (type) {
7255
case "paragraph":
73-
// Wikidot: paragraphs containing inline images (no alignment) skip <p> tags
74-
if (hasInlineImage(elements)) {
75-
renderElements(ctx, elements);
76-
} else {
77-
ctx.push(`<p${renderAttrs(attributes)}>`);
78-
renderElements(ctx, elements);
79-
ctx.push("</p>");
80-
}
56+
ctx.push(`<p${renderAttrs(attributes)}>`);
57+
renderElements(ctx, elements);
58+
ctx.push("</p>");
8159
break;
8260
case "bold":
8361
ctx.push(`<strong${renderAttrs(attributes)}>`);

0 commit comments

Comments
 (0)