Skip to content

Commit d692a5d

Browse files
committed
Show attribution only in copied HTML
1 parent 398e768 commit d692a5d

4 files changed

Lines changed: 72 additions & 27 deletions

File tree

src/lib/dc/export-document.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export type DcExportOptions = {
3030
showLineNumbers: boolean;
3131
documentTheme?: DcDocumentTheme;
3232
structure?: DcExportStructure;
33+
includeAttribution?: boolean;
3334
};
3435

3536
export type DcExportStructure = "dcTable";
@@ -386,6 +387,18 @@ function textOf(node: JSONContent): string {
386387
return childrenOf(node).map(textOf).join("");
387388
}
388389

390+
function hasRenderableDocumentContent(node: JSONContent): boolean {
391+
if (typeof node.text === "string" && node.text.trim()) {
392+
return true;
393+
}
394+
395+
if (node.type === "horizontalRule") {
396+
return true;
397+
}
398+
399+
return childrenOf(node).some(hasRenderableDocumentContent);
400+
}
401+
389402
function safeSize(value: string, fallback: string): string {
390403
const normalized = value.trim();
391404
return /^\d{1,2}px$/.test(normalized) ? normalized : fallback;
@@ -2619,9 +2632,15 @@ export async function exportDocumentToDcHtml(
26192632
document: JSONContent,
26202633
options: DcExportOptions,
26212634
): Promise<string> {
2635+
if (!hasRenderableDocumentContent(document)) {
2636+
return "";
2637+
}
2638+
26222639
const palette = documentPalette(options);
26232640
const body = await renderBlockAsync(document, options, { proseTableSafe: true });
2624-
const bodyWithAttribution = `${body}${renderAttributionFooter(options)}`;
2641+
const bodyWithAttribution = options.includeAttribution
2642+
? `${body}${renderAttributionFooter(options)}`
2643+
: body;
26252644
const wrapperStyle = joinStyle({
26262645
display: "block",
26272646
"background-color": palette.articleBackground,

src/routes/+page.svelte

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@
267267
let renderTurn = 0;
268268
269269
const htmlSize = $derived(
270-
`${Math.max(1, Math.ceil(html.length / 1024))}KB`,
270+
html.length === 0 ? "0KB" : `${Math.ceil(html.length / 1024)}KB`,
271271
);
272272
const copyLabel = $derived(copyState === "copied" ? "복사됨" : "디씨 복사");
273273
const sourceCopyLabel = $derived(
@@ -2246,7 +2246,12 @@
22462246
copyState = "idle";
22472247
22482248
try {
2249-
await copyDcHtml(html, editor?.getText() ?? "");
2249+
const copyHtml = await exportDocumentToDcHtml(documentJson, {
2250+
...exportOptions(),
2251+
includeAttribution: true,
2252+
});
2253+
2254+
await copyDcHtml(copyHtml, editor?.getText() ?? "");
22502255
copyState = "copied";
22512256
window.setTimeout(() => {
22522257
copyState = "idle";
@@ -3492,8 +3497,6 @@
34923497
>
34933498
{#if html}
34943499
{@html html}
3495-
{:else}
3496-
<div class="empty">...</div>
34973500
{/if}
34983501
</div>
34993502
{:else}
@@ -3711,7 +3714,7 @@
37113714
}
37123715
37133716
.toolbar .copy-button:disabled {
3714-
cursor: wait;
3717+
cursor: not-allowed;
37153718
opacity: 0.72;
37163719
}
37173720
@@ -4252,7 +4255,7 @@
42524255
}
42534256
42544257
.source-copy-button:disabled {
4255-
cursor: wait;
4258+
cursor: not-allowed;
42564259
opacity: 0.66;
42574260
}
42584261
@@ -5152,25 +5155,6 @@
51525155
max-width: 100%;
51535156
}
51545157
5155-
.empty {
5156-
color: oklch(51.52% 0.02 87.11);
5157-
font-family:
5158-
Cascadia Mono,
5159-
D2Coding,
5160-
나눔고딕코딩,
5161-
Noto Sans Mono CJK,
5162-
JetBrains Mono,
5163-
Fira Code,
5164-
Hack,
5165-
Source Code Pro,
5166-
IBM Plex Mono,
5167-
Roboto Mono,
5168-
Consolas,
5169-
Menlo,
5170-
Monaco,
5171-
monospace;
5172-
}
5173-
51745158
.copy-error {
51755159
margin: 14px 0 0;
51765160
color: var(--danger);

tests/e2e/paste-preview.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ test("renders the paste tool", async ({ page }) => {
148148
await expect(page.locator(".editor-surface")).not.toContainText(
149149
"Go 반복문 정복: for 하나로 모든 루프를 제어한다",
150150
);
151+
await expect(page.getByText("HTML 0KB")).toBeVisible();
152+
await expect(page.locator(".preview-surface")).not.toContainText("...");
153+
await expect(page.getByRole("button", { name: / / })).toHaveCSS("cursor", "not-allowed");
151154
page.once("dialog", async (dialog) => {
152155
expect(dialog.message()).toContain("예시 템플릿");
153156
await dialog.accept();
@@ -275,6 +278,7 @@ test("renders the paste tool", async ({ page }) => {
275278
await expect(htmlSource).toHaveValue(/while_style\.go/);
276279
await expect(htmlSource).toHaveValue(/infinite_loop\.go/);
277280
await expect(htmlSource).toHaveValue(/range_with_index\.go/);
281+
await expect(htmlSource).not.toHaveValue(/Created with dc-code-paste/);
278282
await expect(htmlSource).toHaveValue(/line-height:18px;vertical-align:middle/);
279283
await expect(htmlSource).not.toHaveValue(/<pre/);
280284
await expect(htmlSource).toHaveValue(/&nbsp;&nbsp;&nbsp;&nbsp;/);

tests/unit/export-document.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,22 @@ function textOfTestDocument(node: JSONContent): string {
2727
}
2828

2929
describe("exportDocumentToDcHtml", () => {
30-
it("appends a subtle linked attribution footer to copied DC HTML", async () => {
30+
it("returns empty HTML for an empty editor document", async () => {
31+
const document: JSONContent = {
32+
type: "doc",
33+
content: [{ type: "paragraph" }],
34+
};
35+
36+
await expect(exportDocumentToDcHtml(document, exportOptions)).resolves.toBe("");
37+
await expect(
38+
exportDocumentToDcHtml(document, {
39+
...exportOptions,
40+
includeAttribution: true,
41+
}),
42+
).resolves.toBe("");
43+
});
44+
45+
it("omits the attribution footer by default for preview HTML", async () => {
3146
const document: JSONContent = {
3247
type: "doc",
3348
content: [
@@ -39,6 +54,27 @@ describe("exportDocumentToDcHtml", () => {
3954
};
4055

4156
const html = await exportDocumentToDcHtml(document, exportOptions);
57+
58+
expect(html).toContain("본문 내용");
59+
expect(html).not.toContain("Created with dc-code-paste");
60+
expect(html).not.toContain('href="https://0disoft.github.io/dc-code-paste/"');
61+
});
62+
63+
it("appends a subtle linked attribution footer to copied DC HTML", async () => {
64+
const document: JSONContent = {
65+
type: "doc",
66+
content: [
67+
{
68+
type: "paragraph",
69+
content: [{ type: "text", text: "본문 내용" }],
70+
},
71+
],
72+
};
73+
74+
const html = await exportDocumentToDcHtml(document, {
75+
...exportOptions,
76+
includeAttribution: true,
77+
});
4278
const footerStart = html.indexOf("Created with dc-code-paste");
4379

4480
expect(footerStart).toBeGreaterThan(html.indexOf("본문 내용"));
@@ -66,6 +102,7 @@ describe("exportDocumentToDcHtml", () => {
66102
const html = await exportDocumentToDcHtml(document, {
67103
...exportOptions,
68104
documentTheme: "darkEditorial",
105+
includeAttribution: true,
69106
});
70107

71108
expect(html).toContain("Created with dc-code-paste");
@@ -637,6 +674,7 @@ describe("exportDocumentToDcHtml", () => {
637674
bodyFontSize: "17px",
638675
codeFontSize: "15px",
639676
theme: "catppuccin-mocha",
677+
includeAttribution: true,
640678
});
641679

642680
expect(markdown.length).toBeGreaterThan(3_000);

0 commit comments

Comments
 (0)