Skip to content

Commit 45eb0cd

Browse files
committed
Refactor comment content rendering and add Content.vue
1 parent 989bcb8 commit 45eb0cd

File tree

8 files changed

+116
-59
lines changed

8 files changed

+116
-59
lines changed

packages/comment-widget/src/base-comment-item.ts

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import './user-avatar';
22
import { msg } from '@lit/localize';
3-
import { css, html, LitElement, unsafeCSS } from 'lit';
3+
import { css, html, LitElement } from 'lit';
44
import { property, state } from 'lit/decorators.js';
5-
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
65
import baseStyles from './styles/base';
7-
import contentStyles from './styles/content.css?inline';
86
import { formatDate, timeAgo } from './utils/date';
97
import './commenter-ua-bar';
108
import { consume } from '@lit/context';
11-
import sanitizeHtml from 'sanitize-html';
129
import { configMapDataContext } from './context';
1310
import type { ConfigMapData } from './types';
11+
import './comment-content';
1412

1513
export class BaseCommentItem extends LitElement {
1614
@property({ type: String })
@@ -41,49 +39,6 @@ export class BaseCommentItem extends LitElement {
4139
@state()
4240
configMapData: ConfigMapData | undefined;
4341

44-
protected override firstUpdated() {
45-
const codeElements = this.shadowRoot?.querySelectorAll('pre>code');
46-
if (!codeElements?.length) return;
47-
48-
Promise.all(
49-
Array.from(codeElements).map(async (codeblock) => {
50-
const lang =
51-
this.extractLanguageFromCodeElement(codeblock) || 'plaintext';
52-
const content = codeblock.textContent || '';
53-
54-
try {
55-
const { codeToHtml } = await import('shiki/bundle/full');
56-
57-
const html = await codeToHtml(content, {
58-
lang,
59-
theme: 'github-dark',
60-
});
61-
62-
if (codeblock.parentElement) {
63-
codeblock.parentElement.outerHTML = html;
64-
}
65-
} catch (error) {
66-
console.error('Failed to highlight code:', error);
67-
}
68-
})
69-
);
70-
}
71-
72-
private extractLanguageFromCodeElement(codeElement: Element): string | null {
73-
const supportedPrefixes = ['language-', 'lang-'];
74-
75-
const langClass = Array.from(codeElement.classList).find((className) =>
76-
supportedPrefixes.some((prefix) => className.startsWith(prefix))
77-
);
78-
79-
if (langClass) {
80-
const prefix = supportedPrefixes.find((p) => langClass.startsWith(p));
81-
return prefix ? langClass.substring(prefix.length) : null;
82-
}
83-
84-
return null;
85-
}
86-
8742
override render() {
8843
return html`<div class="item flex gap-3 py-4 ${this.breath ? 'animate-breath' : ''}">
8944
<div class="item-avatar flex-none">
@@ -121,14 +76,7 @@ export class BaseCommentItem extends LitElement {
12176
}
12277
</div>
12378
124-
<div class="item-content mt-2.5 space-y-2.5 content"><slot name="pre-content"></slot>${unsafeHTML(
125-
sanitizeHtml(this.content, {
126-
allowedAttributes: {
127-
...sanitizeHtml.defaults.allowedAttributes,
128-
code: ['class'],
129-
},
130-
})
131-
)}</div>
79+
<div class="item-content mt-2.5 space-y-2.5"><slot name="pre-content"></slot><comment-content .content=${this.content}></comment-content></div>
13280
13381
<div class="item-actions mt-2 flex items-center gap-3">
13482
<slot name="action"></slot>
@@ -141,7 +89,6 @@ export class BaseCommentItem extends LitElement {
14189

14290
static override styles = [
14391
...baseStyles,
144-
unsafeCSS(contentStyles),
14592
css`
14693
.animate-breath {
14794
animation: breath 1s ease-in-out infinite;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { css, html, LitElement, type PropertyValues, unsafeCSS } from 'lit';
2+
import { property } from 'lit/decorators.js';
3+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
4+
import sanitizeHtml from 'sanitize-html';
5+
import baseStyles from './styles/base';
6+
import contentStyles from './styles/content.css?inline';
7+
8+
export class CommentContent extends LitElement {
9+
@property({ type: String })
10+
content: string = '';
11+
12+
protected override firstUpdated(_changedProperties: PropertyValues) {
13+
super.firstUpdated(_changedProperties);
14+
const codeElements = this.shadowRoot?.querySelectorAll('pre>code');
15+
if (!codeElements?.length) return;
16+
17+
Promise.all(
18+
Array.from(codeElements).map(async (codeblock) => {
19+
const lang =
20+
this.extractLanguageFromCodeElement(codeblock) || 'plaintext';
21+
const content = codeblock.textContent || '';
22+
23+
try {
24+
const { codeToHtml } = await import('shiki/bundle/full');
25+
26+
const html = await codeToHtml(content, {
27+
lang,
28+
theme: 'github-dark',
29+
});
30+
31+
if (codeblock.parentElement) {
32+
codeblock.parentElement.outerHTML = html;
33+
}
34+
} catch (error) {
35+
console.error('Failed to highlight code:', error);
36+
}
37+
})
38+
);
39+
}
40+
41+
private extractLanguageFromCodeElement(codeElement: Element): string | null {
42+
const supportedPrefixes = ['language-', 'lang-'];
43+
44+
const langClass = Array.from(codeElement.classList).find((className) =>
45+
supportedPrefixes.some((prefix) => className.startsWith(prefix))
46+
);
47+
48+
if (langClass) {
49+
const prefix = supportedPrefixes.find((p) => langClass.startsWith(p));
50+
return prefix ? langClass.substring(prefix.length) : null;
51+
}
52+
53+
return null;
54+
}
55+
56+
protected override render() {
57+
return html`
58+
<div class="content">${unsafeHTML(
59+
sanitizeHtml(this.content, {
60+
allowedAttributes: {
61+
...sanitizeHtml.defaults.allowedAttributes,
62+
code: ['class'],
63+
},
64+
})
65+
)}</div>
66+
`;
67+
}
68+
69+
static override styles = [
70+
...baseStyles,
71+
unsafeCSS(contentStyles),
72+
css`
73+
:host {
74+
display: block;
75+
width: 100%;
76+
}
77+
78+
@unocss-placeholder;
79+
`,
80+
];
81+
}
82+
83+
customElements.get('comment-content') ||
84+
customElements.define('comment-content', CommentContent);
85+
86+
declare global {
87+
interface HTMLElementTagNameMap {
88+
'comment-content': CommentContent;
89+
}
90+
}

packages/comment-widget/src/comment-editor-skeleton.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { css, html, LitElement } from 'lit';
22
import { repeat } from 'lit/directives/repeat.js';
3-
import './emoji-button';
43

54
export class CommentEditorSkeleton extends LitElement {
65
protected override render() {

packages/comment-widget/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BaseCommentItem } from './base-comment-item';
22
import { BaseForm } from './base-form';
3+
import { CommentContent } from './comment-content';
34
import { CommentEditor } from './comment-editor';
45
import { CommentItem } from './comment-item';
56
import { CommentList } from './comment-list';
@@ -27,4 +28,5 @@ export {
2728
CommentList,
2829
LitToast,
2930
CommentEditor,
31+
CommentContent,
3032
};

packages/ui/rsbuild.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default rsbuildConfig({
2121
pluginVue({
2222
vueLoaderOptions: {
2323
compilerOptions: {
24-
isCustomElement: (tag) => tag === 'comment-editor',
24+
isCustomElement: (tag) => tag.startsWith('comment-'),
2525
},
2626
},
2727
}),
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script lang="ts" setup>
2+
import '@halo-dev/comment-widget';
3+
4+
defineProps<{
5+
content: string;
6+
}>();
7+
</script>
8+
<template>
9+
<comment-content :content="content"></comment-content>
10+
</template>

packages/ui/src/components/Editor.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ onMounted(() => {
2929
});
3030
</script>
3131
<template>
32-
<comment-editor ref="editorRef"></comment-editor>
32+
<comment-editor ref="editorRef" keep-alive></comment-editor>
3333
</template>

packages/ui/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,14 @@ export default definePlugin({
1616
),
1717
};
1818
},
19+
'comment:list-item:content:replace': () => {
20+
return {
21+
component: markRaw(
22+
defineAsyncComponent({
23+
loader: () => import('./components/Content.vue'),
24+
})
25+
),
26+
};
27+
},
1928
},
2029
});

0 commit comments

Comments
 (0)