Skip to content

Commit 805f10d

Browse files
committed
style: enhance dark mode support and improve text formatting in editors
- Updated class names in CollaborativeEditor and ReadonlyEditor to support dark mode text colors. - Added custom components for highlight, underline, and strikethrough in Markdown rendering. - Integrated new formatting styles in markdown.scss for better visual consistency in dark mode.
1 parent 23a9e88 commit 805f10d

File tree

6 files changed

+141
-4
lines changed

6 files changed

+141
-4
lines changed

packages/ai-workspace-common/src/components/document/collab-editor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,8 @@ export const CollaborativeEditor = memo(
533533
handleDrop: (view, event, _slice, moved) =>
534534
handleImageDrop(view, event, moved, uploadFn),
535535
attributes: {
536-
class: `prose prose-md prose-headings:font-title font-default focus:outline-none max-w-full prose-img:cursor-pointer ${isDarkMode ? '!text-white' : '!text-black'}`,
536+
class:
537+
'prose prose-md prose-headings:font-title font-default focus:outline-none max-w-full prose-img:cursor-pointer dark:text-white',
537538

538539
'data-doc-id': docId,
539540
},

packages/ai-workspace-common/src/components/document/readonly-editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const ReadonlyEditor = memo(
108108
},
109109
attributes: {
110110
class:
111-
'prose prose-md prose-headings:font-title font-default focus:outline-none max-w-full',
111+
'prose prose-md prose-headings:font-title font-default focus:outline-none max-w-full dark:text-white',
112112
'data-doc-id': docId,
113113
},
114114
}}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { visit } from 'unist-util-visit';
2+
import type { Node } from 'unist';
3+
4+
// Define minimal Plugin type inline to avoid dependency on 'unified'
5+
type Plugin = () => (tree: any) => void;
6+
7+
/**
8+
* Rehype plugin to transform text nodes with ==highlight== and __underline__ syntax
9+
* to proper HTML elements.
10+
*/
11+
const rehypeHighlightUnderline: Plugin = () => {
12+
return (tree: Node) => {
13+
// Process text nodes for special syntax
14+
visit(tree, 'text', (node: any, index: number | null, parent: any) => {
15+
if (!parent || index === null) return;
16+
17+
const { value } = node;
18+
if (typeof value !== 'string') return;
19+
20+
// Match patterns for highlight and underline
21+
const highlightRegex = /==(.*?)==/g;
22+
const underlineRegex = /__(.*?)__/g;
23+
24+
// Check if we have any matches
25+
if (!highlightRegex.test(value) && !underlineRegex.test(value)) return;
26+
27+
// Reset regex lastIndex
28+
highlightRegex.lastIndex = 0;
29+
underlineRegex.lastIndex = 0;
30+
31+
// Process the text to create HTML elements
32+
const parts = [];
33+
let lastIndex = 0;
34+
const text = value;
35+
36+
// First process highlights
37+
let match: any = highlightRegex.exec(text);
38+
while (match !== null) {
39+
// Add text before the match
40+
if (match.index > lastIndex) {
41+
parts.push({ type: 'text', value: text.slice(lastIndex, match.index) });
42+
}
43+
44+
// Add the highlight element
45+
parts.push({
46+
type: 'element',
47+
tagName: 'mark',
48+
properties: {},
49+
children: [{ type: 'text', value: match[1] }],
50+
});
51+
52+
lastIndex = match.index + match[0].length;
53+
match = highlightRegex.exec(text);
54+
}
55+
56+
// Add any remaining text
57+
if (lastIndex < text.length) {
58+
parts.push({ type: 'text', value: text.slice(lastIndex) });
59+
}
60+
61+
// If we found any highlights, replace the original node
62+
if (parts.length > 0) {
63+
parent.children.splice(index, 1, ...parts);
64+
return;
65+
}
66+
67+
// Otherwise, check for underlines
68+
lastIndex = 0;
69+
parts.length = 0;
70+
71+
match = underlineRegex.exec(text);
72+
while (match !== null) {
73+
// Add text before the match
74+
if (match.index > lastIndex) {
75+
parts.push({ type: 'text', value: text.slice(lastIndex, match.index) });
76+
}
77+
78+
// Add the underline element
79+
parts.push({
80+
type: 'element',
81+
tagName: 'u',
82+
properties: {},
83+
children: [{ type: 'text', value: match[1] }],
84+
});
85+
86+
lastIndex = match.index + match[0].length;
87+
match = underlineRegex.exec(text);
88+
}
89+
90+
// Add any remaining text
91+
if (lastIndex < text.length) {
92+
parts.push({ type: 'text', value: text.slice(lastIndex) });
93+
}
94+
95+
// Replace the original node with our processed parts
96+
if (parts.length > 0) {
97+
parent.children.splice(index, 1, ...parts);
98+
}
99+
});
100+
};
101+
};
102+
103+
export default rehypeHighlightUnderline;

packages/ai-workspace-common/src/components/markdown/index.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { memo, useEffect, useRef, useState, Suspense, useMemo } from 'react';
33
import ReactMarkdown from 'react-markdown';
44

55
import RemarkBreaks from 'remark-breaks';
6+
import RemarkGfm from 'remark-gfm';
67

78
import { cn, markdownCitationParse } from '@refly/utils';
89

910
// plugins
1011
import LinkElement from './plugins/link';
12+
import rehypeHighlightUnderline from './custom-plugins/rehype-highlight-underline';
1113

1214
// styles
1315
import './styles/markdown.scss';
@@ -48,6 +50,21 @@ const MarkdownImage = memo(({ src, alt, ...props }: React.ImgHTMLAttributes<HTML
4850
);
4951
});
5052

53+
// Custom Components for enhanced Markdown rendering
54+
const HighlightComponent = ({ children }: { children: React.ReactNode }) => (
55+
<mark className="bg-yellow-200 dark:bg-yellow-800 dark:text-gray-200 rounded-sm px-1 text-inherit">
56+
{children}
57+
</mark>
58+
);
59+
60+
const UnderlineComponent = ({ children }: { children: React.ReactNode }) => (
61+
<span className="underline">{children}</span>
62+
);
63+
64+
const StrikethroughComponent = ({ children }: { children: React.ReactNode }) => (
65+
<del>{children}</del>
66+
);
67+
5168
export const Markdown = memo(
5269
(
5370
props: {
@@ -127,9 +144,10 @@ export const Markdown = memo(
127144
plugins.RehypeKatex &&
128145
plugins.RehypeHighlight && (
129146
<ReactMarkdown
130-
remarkPlugins={[RemarkBreaks, plugins.RemarkMath]}
147+
remarkPlugins={[RemarkBreaks, plugins.RemarkMath, RemarkGfm]}
131148
rehypePlugins={[
132149
...rehypePlugins,
150+
rehypeHighlightUnderline,
133151
plugins.RehypeKatex,
134152
[
135153
plugins.RehypeHighlight,
@@ -143,6 +161,9 @@ export const Markdown = memo(
143161
...artifactComponents,
144162
a: (args) => LinkElement.Component(args, props?.sources || []),
145163
img: MarkdownImage,
164+
mark: HighlightComponent,
165+
del: StrikethroughComponent,
166+
u: UnderlineComponent,
146167
}}
147168
linkTarget={'_blank'}
148169
>

packages/ai-workspace-common/src/components/markdown/styles/markdown.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@
9898
background-color: var(--color-canvas-default);
9999
line-height: 1.5;
100100
word-wrap: break-word;
101+
102+
// Custom formatting styles
103+
del {
104+
text-decoration: line-through;
105+
color: var(--color-fg-subtle);
106+
}
107+
108+
u, .underline {
109+
text-decoration: underline;
110+
text-decoration-thickness: 0.1em;
111+
text-underline-offset: 0.1em;
112+
}
101113
}
102114

103115
.light {

packages/ai-workspace-common/src/modules/multilingual-search/components/action-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export const ActionMenu: React.FC<ActionMenuProps> = (props) => {
159159

160160
return (
161161
<Affix offsetBottom={0} target={props.getTarget}>
162-
<div className="intergation-footer bg-white dark:bg-[#1f1f1f] border-[#e5e5e5] dark:!border-[#2f2f2f]">
162+
<div className="intergation-footer bg-white dark:bg-[#1f1f1f] !border-[#e5e5e5] dark:!border-[#2f2f2f]">
163163
<div className="footer-location">
164164
<Checkbox
165165
checked={selectedItems.length && selectedItems.length === results.length}

0 commit comments

Comments
 (0)