@idealyst/markdown 1.2.38 → 1.2.40
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/package.json +74 -5
- package/src/Editor/EditorToolbar.web.tsx +470 -0
- package/src/Editor/MarkdownEditor.native.tsx +277 -0
- package/src/Editor/MarkdownEditor.styles.ts +337 -0
- package/src/Editor/MarkdownEditor.web.tsx +388 -0
- package/src/Editor/examples/MarkdownEditorExamples.tsx +235 -0
- package/src/Editor/examples/index.ts +1 -0
- package/src/Editor/index.native.ts +23 -0
- package/src/Editor/index.ts +43 -0
- package/src/Editor/types.ts +202 -0
- package/src/examples/index.ts +1 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import { View, StyleSheet, KeyboardAvoidingView, Platform } from 'react-native';
|
|
8
|
+
import {
|
|
9
|
+
RichText,
|
|
10
|
+
Toolbar,
|
|
11
|
+
useEditorBridge,
|
|
12
|
+
type EditorBridge,
|
|
13
|
+
} from '@10play/tentap-editor';
|
|
14
|
+
import Showdown from 'showdown';
|
|
15
|
+
import { editorStyles } from './MarkdownEditor.styles';
|
|
16
|
+
import type { MarkdownEditorProps, MarkdownEditorRef, ToolbarItem } from './types';
|
|
17
|
+
|
|
18
|
+
// Configure Showdown converter with GFM support
|
|
19
|
+
const showdownConverter = new Showdown.Converter({
|
|
20
|
+
tables: true,
|
|
21
|
+
strikethrough: true,
|
|
22
|
+
tasklists: true,
|
|
23
|
+
ghCodeBlocks: true,
|
|
24
|
+
smoothLivePreview: true,
|
|
25
|
+
simpleLineBreaks: false,
|
|
26
|
+
openLinksInNewWindow: false,
|
|
27
|
+
backslashEscapesHTMLTags: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Map our toolbar items to 10tap toolbar items
|
|
31
|
+
const TOOLBAR_ITEM_MAP: Record<ToolbarItem, string | string[] | null> = {
|
|
32
|
+
bold: 'bold',
|
|
33
|
+
italic: 'italic',
|
|
34
|
+
underline: 'underline',
|
|
35
|
+
strikethrough: 'strikethrough',
|
|
36
|
+
code: 'code',
|
|
37
|
+
// 'heading' expands to all heading levels since 10tap doesn't support dropdowns
|
|
38
|
+
heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
|
|
39
|
+
heading1: 'h1',
|
|
40
|
+
heading2: 'h2',
|
|
41
|
+
heading3: 'h3',
|
|
42
|
+
heading4: 'h4',
|
|
43
|
+
heading5: 'h5',
|
|
44
|
+
heading6: 'h6',
|
|
45
|
+
bulletList: 'bulletList',
|
|
46
|
+
orderedList: 'orderedList',
|
|
47
|
+
taskList: 'taskList',
|
|
48
|
+
blockquote: 'blockquote',
|
|
49
|
+
codeBlock: 'codeBlock',
|
|
50
|
+
horizontalRule: 'horizontalRule',
|
|
51
|
+
link: 'link',
|
|
52
|
+
image: 'image',
|
|
53
|
+
undo: 'undo',
|
|
54
|
+
redo: 'redo',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Convert markdown to HTML using Showdown.
|
|
59
|
+
*/
|
|
60
|
+
function markdownToHtml(markdown: string): string {
|
|
61
|
+
return showdownConverter.makeHtml(markdown);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert HTML to markdown using Showdown.
|
|
66
|
+
*/
|
|
67
|
+
function htmlToMarkdown(html: string): string {
|
|
68
|
+
return showdownConverter.makeMarkdown(html);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Markdown editor for React Native using 10tap-editor.
|
|
73
|
+
*
|
|
74
|
+
* Uses a WebView-based Tiptap editor under the hood for rich text editing
|
|
75
|
+
* with markdown input/output support.
|
|
76
|
+
*/
|
|
77
|
+
const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>(
|
|
78
|
+
(
|
|
79
|
+
{
|
|
80
|
+
initialValue = '',
|
|
81
|
+
value,
|
|
82
|
+
onChange,
|
|
83
|
+
onFocus,
|
|
84
|
+
onBlur,
|
|
85
|
+
editable = true,
|
|
86
|
+
autoFocus = false,
|
|
87
|
+
placeholder,
|
|
88
|
+
toolbar = {},
|
|
89
|
+
size = 'md',
|
|
90
|
+
linkIntent = 'primary',
|
|
91
|
+
minHeight = 200,
|
|
92
|
+
maxHeight,
|
|
93
|
+
style,
|
|
94
|
+
testID,
|
|
95
|
+
id,
|
|
96
|
+
accessibilityLabel,
|
|
97
|
+
avoidIosKeyboard = true,
|
|
98
|
+
},
|
|
99
|
+
ref
|
|
100
|
+
) => {
|
|
101
|
+
const isControlled = value !== undefined;
|
|
102
|
+
const lastValueRef = useRef<string>(value ?? initialValue);
|
|
103
|
+
const editorRef = useRef<EditorBridge | null>(null);
|
|
104
|
+
|
|
105
|
+
// Apply style variants
|
|
106
|
+
editorStyles.useVariants({
|
|
107
|
+
size,
|
|
108
|
+
linkIntent,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Initialize editor with HTML content
|
|
112
|
+
const initialHtml = markdownToHtml(value ?? initialValue);
|
|
113
|
+
|
|
114
|
+
const editor = useEditorBridge({
|
|
115
|
+
autofocus: autoFocus,
|
|
116
|
+
avoidIosKeyboard,
|
|
117
|
+
initialContent: initialHtml,
|
|
118
|
+
editable,
|
|
119
|
+
onChange: async () => {
|
|
120
|
+
if (editorRef.current) {
|
|
121
|
+
try {
|
|
122
|
+
const html = await editorRef.current.getHTML();
|
|
123
|
+
const markdown = htmlToMarkdown(html);
|
|
124
|
+
if (markdown !== lastValueRef.current) {
|
|
125
|
+
lastValueRef.current = markdown;
|
|
126
|
+
onChange?.(markdown);
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// Editor might not be ready
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Store editor ref
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
editorRef.current = editor;
|
|
138
|
+
}, [editor]);
|
|
139
|
+
|
|
140
|
+
// Handle controlled value changes
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (isControlled && value !== lastValueRef.current && editorRef.current) {
|
|
143
|
+
const html = markdownToHtml(value);
|
|
144
|
+
editorRef.current.setContent(html);
|
|
145
|
+
lastValueRef.current = value;
|
|
146
|
+
}
|
|
147
|
+
}, [value, isControlled]);
|
|
148
|
+
|
|
149
|
+
// Expose ref methods
|
|
150
|
+
useImperativeHandle(
|
|
151
|
+
ref,
|
|
152
|
+
() => ({
|
|
153
|
+
getMarkdown: async () => {
|
|
154
|
+
if (!editorRef.current) return '';
|
|
155
|
+
try {
|
|
156
|
+
const html = await editorRef.current.getHTML();
|
|
157
|
+
return htmlToMarkdown(html);
|
|
158
|
+
} catch {
|
|
159
|
+
return lastValueRef.current;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
setMarkdown: (markdown: string) => {
|
|
163
|
+
if (editorRef.current) {
|
|
164
|
+
const html = markdownToHtml(markdown);
|
|
165
|
+
editorRef.current.setContent(html);
|
|
166
|
+
lastValueRef.current = markdown;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
focus: () => editorRef.current?.focus(),
|
|
170
|
+
blur: () => editorRef.current?.blur(),
|
|
171
|
+
isEmpty: async () => {
|
|
172
|
+
if (!editorRef.current) return true;
|
|
173
|
+
try {
|
|
174
|
+
const text = await editorRef.current.getText();
|
|
175
|
+
return !text || text.trim().length === 0;
|
|
176
|
+
} catch {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
clear: () => {
|
|
181
|
+
editorRef.current?.setContent('');
|
|
182
|
+
lastValueRef.current = '';
|
|
183
|
+
},
|
|
184
|
+
undo: () => editorRef.current?.undo(),
|
|
185
|
+
redo: () => editorRef.current?.redo(),
|
|
186
|
+
}),
|
|
187
|
+
[]
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Default toolbar items - matches web
|
|
191
|
+
const defaultItems: ToolbarItem[] = [
|
|
192
|
+
'bold',
|
|
193
|
+
'italic',
|
|
194
|
+
'underline',
|
|
195
|
+
'strikethrough',
|
|
196
|
+
'code',
|
|
197
|
+
'heading',
|
|
198
|
+
'bulletList',
|
|
199
|
+
'orderedList',
|
|
200
|
+
'blockquote',
|
|
201
|
+
'codeBlock',
|
|
202
|
+
'link',
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
// Get disabled items set for filtering
|
|
206
|
+
const disabledItems = new Set(toolbar.disabledItems ?? []);
|
|
207
|
+
|
|
208
|
+
// Map toolbar items, expanding arrays (like 'heading' -> ['h1', 'h2', ...])
|
|
209
|
+
// and filtering out disabled items
|
|
210
|
+
const toolbarItems = (toolbar.items ?? defaultItems)
|
|
211
|
+
.filter((item) => !disabledItems.has(item))
|
|
212
|
+
.flatMap((item) => {
|
|
213
|
+
const mapped = TOOLBAR_ITEM_MAP[item];
|
|
214
|
+
if (mapped === null) return [];
|
|
215
|
+
if (Array.isArray(mapped)) return mapped;
|
|
216
|
+
return [mapped];
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const showToolbar = toolbar.visible !== false && editable;
|
|
220
|
+
const toolbarPosition = toolbar.position ?? 'top';
|
|
221
|
+
|
|
222
|
+
const containerStyle = [
|
|
223
|
+
(editorStyles.container as any)({ size, linkIntent }),
|
|
224
|
+
style,
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const editorContentStyle = [
|
|
228
|
+
(editorStyles.editorContent as any)({ size, linkIntent }),
|
|
229
|
+
{ minHeight },
|
|
230
|
+
maxHeight !== undefined && { maxHeight },
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const content = (
|
|
234
|
+
<View
|
|
235
|
+
style={containerStyle}
|
|
236
|
+
nativeID={id}
|
|
237
|
+
testID={testID}
|
|
238
|
+
accessibilityLabel={accessibilityLabel}
|
|
239
|
+
>
|
|
240
|
+
{showToolbar && toolbarPosition === 'top' && (
|
|
241
|
+
<Toolbar editor={editor} items={toolbarItems} />
|
|
242
|
+
)}
|
|
243
|
+
<View style={editorContentStyle}>
|
|
244
|
+
<RichText
|
|
245
|
+
editor={editor}
|
|
246
|
+
onFocus={onFocus}
|
|
247
|
+
onBlur={onBlur}
|
|
248
|
+
style={nativeStyles.richText}
|
|
249
|
+
/>
|
|
250
|
+
</View>
|
|
251
|
+
{showToolbar && toolbarPosition === 'bottom' && (
|
|
252
|
+
<Toolbar editor={editor} items={toolbarItems} />
|
|
253
|
+
)}
|
|
254
|
+
</View>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if (Platform.OS === 'ios' && avoidIosKeyboard) {
|
|
258
|
+
return (
|
|
259
|
+
<KeyboardAvoidingView behavior="padding" style={{ flex: 1 }}>
|
|
260
|
+
{content}
|
|
261
|
+
</KeyboardAvoidingView>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return content;
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const nativeStyles = StyleSheet.create({
|
|
270
|
+
richText: {
|
|
271
|
+
flex: 1,
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
MarkdownEditor.displayName = 'MarkdownEditor';
|
|
276
|
+
|
|
277
|
+
export default MarkdownEditor;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MarkdownEditor styles using defineStyle with theme integration.
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent styling for the markdown editor that
|
|
5
|
+
* integrates with the Idealyst theme system.
|
|
6
|
+
*/
|
|
7
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
8
|
+
import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
|
|
9
|
+
import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
|
|
10
|
+
|
|
11
|
+
// Required: Unistyles must see StyleSheet usage in original source to process this file
|
|
12
|
+
void StyleSheet;
|
|
13
|
+
|
|
14
|
+
// Wrap theme for $iterator support
|
|
15
|
+
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
16
|
+
|
|
17
|
+
export type EditorVariants = {
|
|
18
|
+
size: Size;
|
|
19
|
+
linkIntent: Intent;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Dynamic props passed to editor style functions.
|
|
24
|
+
*/
|
|
25
|
+
export type EditorDynamicProps = {
|
|
26
|
+
linkIntent?: Intent;
|
|
27
|
+
size?: Size;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Editor styles with theme integration.
|
|
32
|
+
*/
|
|
33
|
+
// @ts-expect-error - MarkdownEditor is not in the ComponentName union yet
|
|
34
|
+
export const editorStyles = defineStyle('MarkdownEditor', (theme: Theme) => ({
|
|
35
|
+
// Main container
|
|
36
|
+
container: (_props: EditorDynamicProps) => ({
|
|
37
|
+
borderWidth: 1,
|
|
38
|
+
borderColor: theme.colors.border.primary,
|
|
39
|
+
borderRadius: theme.radii.md,
|
|
40
|
+
backgroundColor: theme.colors.surface.primary,
|
|
41
|
+
overflow: 'hidden',
|
|
42
|
+
}),
|
|
43
|
+
|
|
44
|
+
// Editor content area
|
|
45
|
+
editorContent: (_props: EditorDynamicProps) => ({
|
|
46
|
+
padding: 16,
|
|
47
|
+
minHeight: 200,
|
|
48
|
+
fontSize: theme.sizes.typography.body1.fontSize,
|
|
49
|
+
lineHeight: theme.sizes.typography.body1.lineHeight,
|
|
50
|
+
color: theme.colors.text.primary,
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
// Toolbar container
|
|
54
|
+
toolbar: (_props: EditorDynamicProps) => ({
|
|
55
|
+
flexDirection: 'row' as const,
|
|
56
|
+
flexWrap: 'wrap' as const,
|
|
57
|
+
gap: 4,
|
|
58
|
+
padding: 8,
|
|
59
|
+
borderBottomWidth: 1,
|
|
60
|
+
borderBottomColor: theme.colors.border.primary,
|
|
61
|
+
backgroundColor: theme.colors.surface.secondary,
|
|
62
|
+
_web: {
|
|
63
|
+
display: 'flex',
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
// Toolbar button base
|
|
68
|
+
toolbarButton: (_props: EditorDynamicProps) => ({
|
|
69
|
+
paddingLeft: 10,
|
|
70
|
+
paddingRight: 10,
|
|
71
|
+
paddingTop: 6,
|
|
72
|
+
paddingBottom: 6,
|
|
73
|
+
borderRadius: theme.radii.sm,
|
|
74
|
+
backgroundColor: 'transparent',
|
|
75
|
+
borderWidth: 0,
|
|
76
|
+
borderColor: 'transparent',
|
|
77
|
+
color: theme.colors.text.primary,
|
|
78
|
+
fontSize: 14,
|
|
79
|
+
fontWeight: '500' as const,
|
|
80
|
+
minWidth: 32,
|
|
81
|
+
height: 32,
|
|
82
|
+
_web: {
|
|
83
|
+
display: 'flex',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
justifyContent: 'center',
|
|
86
|
+
cursor: 'pointer',
|
|
87
|
+
outline: 'none',
|
|
88
|
+
border: 'none',
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
|
|
92
|
+
// Toolbar button active state
|
|
93
|
+
toolbarButtonActive: (_props: EditorDynamicProps) => ({
|
|
94
|
+
backgroundColor: theme.intents?.primary?.primary ?? theme.colors.surface.tertiary,
|
|
95
|
+
color: theme.intents?.primary?.contrast ?? theme.colors.text.inverse,
|
|
96
|
+
}),
|
|
97
|
+
|
|
98
|
+
// Focus ring for accessibility
|
|
99
|
+
focusRing: (_props: EditorDynamicProps) => ({
|
|
100
|
+
}),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generate CSS for Tiptap editor based on theme colors.
|
|
105
|
+
* This is injected as a style tag since Tiptap uses its own DOM.
|
|
106
|
+
*/
|
|
107
|
+
export function generateTiptapCSS(theme: BaseTheme): string {
|
|
108
|
+
const primary = theme.intents?.primary?.primary ?? theme.colors.text.primary;
|
|
109
|
+
|
|
110
|
+
return `
|
|
111
|
+
.tiptap-editor-wrapper .tiptap {
|
|
112
|
+
outline: none;
|
|
113
|
+
min-height: 100%;
|
|
114
|
+
font-family: inherit;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.tiptap-editor-wrapper .tiptap:focus {
|
|
118
|
+
outline: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.tiptap-editor-wrapper .tiptap p.is-editor-empty:first-child::before {
|
|
122
|
+
content: attr(data-placeholder);
|
|
123
|
+
color: ${theme.colors.text.tertiary};
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
float: left;
|
|
126
|
+
height: 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Headings */
|
|
130
|
+
.tiptap-editor-wrapper .tiptap h1 {
|
|
131
|
+
font-size: ${theme.sizes.typography.h1.fontSize}px;
|
|
132
|
+
line-height: ${theme.sizes.typography.h1.lineHeight}px;
|
|
133
|
+
font-weight: 700;
|
|
134
|
+
margin-top: 1em;
|
|
135
|
+
margin-bottom: 0.5em;
|
|
136
|
+
color: ${theme.colors.text.primary};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.tiptap-editor-wrapper .tiptap h2 {
|
|
140
|
+
font-size: ${theme.sizes.typography.h2.fontSize}px;
|
|
141
|
+
line-height: ${theme.sizes.typography.h2.lineHeight}px;
|
|
142
|
+
font-weight: 600;
|
|
143
|
+
margin-top: 1em;
|
|
144
|
+
margin-bottom: 0.5em;
|
|
145
|
+
border-bottom: 1px solid ${theme.colors.border.primary};
|
|
146
|
+
padding-bottom: 0.25em;
|
|
147
|
+
color: ${theme.colors.text.primary};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.tiptap-editor-wrapper .tiptap h3 {
|
|
151
|
+
font-size: ${theme.sizes.typography.h3.fontSize}px;
|
|
152
|
+
line-height: ${theme.sizes.typography.h3.lineHeight}px;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
margin-top: 1em;
|
|
155
|
+
margin-bottom: 0.5em;
|
|
156
|
+
color: ${theme.colors.text.primary};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tiptap-editor-wrapper .tiptap h4,
|
|
160
|
+
.tiptap-editor-wrapper .tiptap h5,
|
|
161
|
+
.tiptap-editor-wrapper .tiptap h6 {
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
margin-top: 1em;
|
|
164
|
+
margin-bottom: 0.5em;
|
|
165
|
+
color: ${theme.colors.text.primary};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Remove top margin from first child */
|
|
169
|
+
.tiptap-editor-wrapper .tiptap > *:first-child {
|
|
170
|
+
margin-top: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Paragraphs */
|
|
174
|
+
.tiptap-editor-wrapper .tiptap p {
|
|
175
|
+
margin-top: 0;
|
|
176
|
+
margin-bottom: 0.5em;
|
|
177
|
+
color: ${theme.colors.text.primary};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Links */
|
|
181
|
+
.tiptap-editor-wrapper .tiptap a {
|
|
182
|
+
color: ${primary};
|
|
183
|
+
text-decoration: underline;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.tiptap-editor-wrapper .tiptap a:hover {
|
|
188
|
+
opacity: 0.8;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Inline Code */
|
|
192
|
+
.tiptap-editor-wrapper .tiptap code {
|
|
193
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
194
|
+
background-color: ${theme.colors.surface.secondary};
|
|
195
|
+
padding: 2px 6px;
|
|
196
|
+
border-radius: ${theme.radii.xs}px;
|
|
197
|
+
font-size: ${theme.sizes.typography.caption.fontSize}px;
|
|
198
|
+
color: ${theme.colors.text.primary};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Code Blocks */
|
|
202
|
+
.tiptap-editor-wrapper .tiptap pre {
|
|
203
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
204
|
+
background-color: ${theme.colors.surface.secondary};
|
|
205
|
+
padding: 16px;
|
|
206
|
+
border-radius: ${theme.radii.md}px;
|
|
207
|
+
margin-top: 12px;
|
|
208
|
+
margin-bottom: 12px;
|
|
209
|
+
overflow-x: auto;
|
|
210
|
+
color: ${theme.colors.text.primary};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.tiptap-editor-wrapper .tiptap pre code {
|
|
214
|
+
background-color: transparent;
|
|
215
|
+
padding: 0;
|
|
216
|
+
font-size: inherit;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Blockquote */
|
|
220
|
+
.tiptap-editor-wrapper .tiptap blockquote {
|
|
221
|
+
border-left: 4px solid ${primary};
|
|
222
|
+
padding-left: 16px;
|
|
223
|
+
padding-top: 8px;
|
|
224
|
+
padding-bottom: 8px;
|
|
225
|
+
margin-top: 12px;
|
|
226
|
+
margin-bottom: 12px;
|
|
227
|
+
margin-left: 0;
|
|
228
|
+
margin-right: 0;
|
|
229
|
+
background-color: ${theme.colors.surface.secondary};
|
|
230
|
+
border-radius: ${theme.radii.sm}px;
|
|
231
|
+
font-style: italic;
|
|
232
|
+
color: ${theme.colors.text.secondary};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Lists */
|
|
236
|
+
.tiptap-editor-wrapper .tiptap ul,
|
|
237
|
+
.tiptap-editor-wrapper .tiptap ol {
|
|
238
|
+
margin-top: 8px;
|
|
239
|
+
margin-bottom: 8px;
|
|
240
|
+
padding-left: 24px;
|
|
241
|
+
color: ${theme.colors.text.primary};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.tiptap-editor-wrapper .tiptap li {
|
|
245
|
+
margin-top: 4px;
|
|
246
|
+
margin-bottom: 4px;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.tiptap-editor-wrapper .tiptap li p {
|
|
250
|
+
margin: 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Task list */
|
|
254
|
+
.tiptap-editor-wrapper .tiptap ul[data-type="taskList"] {
|
|
255
|
+
list-style: none;
|
|
256
|
+
padding: 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.tiptap-editor-wrapper .tiptap ul[data-type="taskList"] li {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: flex-start;
|
|
262
|
+
gap: 8px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.tiptap-editor-wrapper .tiptap ul[data-type="taskList"] li > label {
|
|
266
|
+
flex-shrink: 0;
|
|
267
|
+
margin-top: 4px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.tiptap-editor-wrapper .tiptap ul[data-type="taskList"] li > div {
|
|
271
|
+
flex: 1;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.tiptap-editor-wrapper .tiptap ul[data-type="taskList"] input[type="checkbox"] {
|
|
275
|
+
width: 16px;
|
|
276
|
+
height: 16px;
|
|
277
|
+
accent-color: ${primary};
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Horizontal rule */
|
|
282
|
+
.tiptap-editor-wrapper .tiptap hr {
|
|
283
|
+
height: 1px;
|
|
284
|
+
background-color: ${theme.colors.border.secondary};
|
|
285
|
+
margin-top: 24px;
|
|
286
|
+
margin-bottom: 24px;
|
|
287
|
+
border: none;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* Table */
|
|
291
|
+
.tiptap-editor-wrapper .tiptap table {
|
|
292
|
+
border-collapse: collapse;
|
|
293
|
+
width: 100%;
|
|
294
|
+
margin-top: 12px;
|
|
295
|
+
margin-bottom: 12px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.tiptap-editor-wrapper .tiptap th,
|
|
299
|
+
.tiptap-editor-wrapper .tiptap td {
|
|
300
|
+
border: 1px solid ${theme.colors.border.primary};
|
|
301
|
+
padding: 12px;
|
|
302
|
+
text-align: left;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.tiptap-editor-wrapper .tiptap th {
|
|
306
|
+
background-color: ${theme.colors.surface.secondary};
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* Strong/Bold */
|
|
311
|
+
.tiptap-editor-wrapper .tiptap strong {
|
|
312
|
+
font-weight: 700;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Italic/Emphasis */
|
|
316
|
+
.tiptap-editor-wrapper .tiptap em {
|
|
317
|
+
font-style: italic;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Strikethrough */
|
|
321
|
+
.tiptap-editor-wrapper .tiptap s {
|
|
322
|
+
text-decoration: line-through;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Underline */
|
|
326
|
+
.tiptap-editor-wrapper .tiptap u {
|
|
327
|
+
text-decoration: underline;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* Images */
|
|
331
|
+
.tiptap-editor-wrapper .tiptap img {
|
|
332
|
+
max-width: 100%;
|
|
333
|
+
height: auto;
|
|
334
|
+
border-radius: ${theme.radii.sm}px;
|
|
335
|
+
}
|
|
336
|
+
`;
|
|
337
|
+
}
|