svelte-rtf-editor 0.1.0
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/README.md +224 -0
- package/dist/RtfViewer.svelte +95 -0
- package/dist/RtfViewer.svelte.d.ts +6 -0
- package/dist/components/ColorPicker.svelte +72 -0
- package/dist/components/ColorPicker.svelte.d.ts +8 -0
- package/dist/components/InkEditor.svelte +703 -0
- package/dist/components/InkEditor.svelte.d.ts +36 -0
- package/dist/components/Modal.svelte +104 -0
- package/dist/components/Modal.svelte.d.ts +11 -0
- package/dist/components/Toolbar.svelte +182 -0
- package/dist/components/Toolbar.svelte.d.ts +11 -0
- package/dist/components/ToolbarButton.svelte +71 -0
- package/dist/components/ToolbarButton.svelte.d.ts +10 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +7 -0
- package/dist/rtf-parser.d.ts +10 -0
- package/dist/rtf-parser.js +553 -0
- package/dist/rtf-writer.d.ts +9 -0
- package/dist/rtf-writer.js +281 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +96 -0
- package/package.json +60 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface ChangePayload {
|
|
2
|
+
html: string;
|
|
3
|
+
text: string;
|
|
4
|
+
wordCount: number;
|
|
5
|
+
charCount: number;
|
|
6
|
+
}
|
|
7
|
+
interface Props {
|
|
8
|
+
content?: string;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
autosave?: boolean;
|
|
11
|
+
storageKey?: string;
|
|
12
|
+
showToolbar?: boolean;
|
|
13
|
+
showStatusBar?: boolean;
|
|
14
|
+
minHeight?: string;
|
|
15
|
+
readonly?: boolean;
|
|
16
|
+
onchange?: (payload: ChangePayload) => void;
|
|
17
|
+
onsave?: (payload: {
|
|
18
|
+
html: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
onimport?: (payload: {
|
|
21
|
+
html: string;
|
|
22
|
+
}) => void;
|
|
23
|
+
}
|
|
24
|
+
declare const InkEditor: import("svelte").Component<Props, {
|
|
25
|
+
getHTML: () => string;
|
|
26
|
+
getText: () => string;
|
|
27
|
+
setHTML: (html: string) => void;
|
|
28
|
+
getMarkdown: () => string;
|
|
29
|
+
getRTF: () => string;
|
|
30
|
+
clear: () => void;
|
|
31
|
+
focus: () => void;
|
|
32
|
+
exportFile: (format?: string) => void;
|
|
33
|
+
importRtf: () => void;
|
|
34
|
+
}, "">;
|
|
35
|
+
type InkEditor = ReturnType<typeof InkEditor>;
|
|
36
|
+
export default InkEditor;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
open?: boolean;
|
|
6
|
+
title?: string;
|
|
7
|
+
onclose?: () => void;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
actions?: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { open = false, title = '', onclose, children, actions }: Props = $props();
|
|
13
|
+
|
|
14
|
+
function handleOverlayClick(e: MouseEvent) {
|
|
15
|
+
if (e.target === e.currentTarget) onclose?.();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
19
|
+
if (e.key === 'Escape') onclose?.();
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<svelte:window onkeydown={open ? handleKeydown : undefined} />
|
|
24
|
+
|
|
25
|
+
{#if open}
|
|
26
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
27
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
28
|
+
<div class="modal-overlay" onclick={handleOverlayClick}>
|
|
29
|
+
<div class="modal">
|
|
30
|
+
<h3>{title}</h3>
|
|
31
|
+
{@render children()}
|
|
32
|
+
{#if actions}
|
|
33
|
+
<div class="modal-actions">
|
|
34
|
+
{@render actions()}
|
|
35
|
+
</div>
|
|
36
|
+
{/if}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
{/if}
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
.modal-overlay {
|
|
43
|
+
position: fixed;
|
|
44
|
+
inset: 0;
|
|
45
|
+
background: rgba(44, 37, 32, 0.3);
|
|
46
|
+
backdrop-filter: blur(4px);
|
|
47
|
+
z-index: 200;
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
animation: fadeOverlay 0.15s ease-out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@keyframes fadeOverlay {
|
|
55
|
+
from { opacity: 0; }
|
|
56
|
+
to { opacity: 1; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.modal {
|
|
60
|
+
background: white;
|
|
61
|
+
border-radius: var(--radius);
|
|
62
|
+
padding: 28px;
|
|
63
|
+
width: 420px;
|
|
64
|
+
max-width: 90vw;
|
|
65
|
+
box-shadow: var(--shadow-lg);
|
|
66
|
+
animation: slideUp 0.2s ease-out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@keyframes slideUp {
|
|
70
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
71
|
+
to { opacity: 1; transform: translateY(0); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.modal h3 {
|
|
75
|
+
font-family: 'Instrument Serif', serif;
|
|
76
|
+
font-size: 22px;
|
|
77
|
+
margin-bottom: 16px;
|
|
78
|
+
color: var(--ink);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.modal :global(input[type='text']),
|
|
82
|
+
.modal :global(input[type='url']) {
|
|
83
|
+
width: 100%;
|
|
84
|
+
padding: 10px 14px;
|
|
85
|
+
border: 1px solid var(--border);
|
|
86
|
+
border-radius: var(--radius-sm);
|
|
87
|
+
font-family: 'DM Sans', sans-serif;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
margin-bottom: 12px;
|
|
90
|
+
outline: none;
|
|
91
|
+
transition: border-color var(--transition);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.modal :global(input:focus) {
|
|
95
|
+
border-color: var(--accent);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.modal-actions {
|
|
99
|
+
display: flex;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
justify-content: flex-end;
|
|
102
|
+
margin-top: 8px;
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
open?: boolean;
|
|
4
|
+
title?: string;
|
|
5
|
+
onclose?: () => void;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
actions?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
declare const Modal: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type Modal = ReturnType<typeof Modal>;
|
|
11
|
+
export default Modal;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ToolbarButton from './ToolbarButton.svelte';
|
|
3
|
+
import ColorPicker from './ColorPicker.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
formatState?: Record<string, boolean>;
|
|
7
|
+
blockType?: string;
|
|
8
|
+
onexec?: (cmd: string, value?: string | null) => void;
|
|
9
|
+
onblock?: (tag: string) => void;
|
|
10
|
+
oninsertlink?: () => void;
|
|
11
|
+
oninsertimage?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
formatState = {},
|
|
16
|
+
blockType = 'p',
|
|
17
|
+
onexec,
|
|
18
|
+
onblock,
|
|
19
|
+
oninsertlink,
|
|
20
|
+
oninsertimage
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
|
|
23
|
+
function exec(cmd: string, value: string | null = null) {
|
|
24
|
+
onexec?.(cmd, value);
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div class="toolbar">
|
|
29
|
+
<!-- Block type -->
|
|
30
|
+
<div class="toolbar-group">
|
|
31
|
+
<select
|
|
32
|
+
class="tb-select"
|
|
33
|
+
value={blockType}
|
|
34
|
+
onchange={(e) => onblock?.((e.target as HTMLSelectElement).value)}
|
|
35
|
+
>
|
|
36
|
+
<option value="p">Paragraph</option>
|
|
37
|
+
<option value="h1">Heading 1</option>
|
|
38
|
+
<option value="h2">Heading 2</option>
|
|
39
|
+
<option value="h3">Heading 3</option>
|
|
40
|
+
<option value="blockquote">Quote</option>
|
|
41
|
+
<option value="pre">Code Block</option>
|
|
42
|
+
</select>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="toolbar-divider"></div>
|
|
46
|
+
|
|
47
|
+
<!-- Inline formatting -->
|
|
48
|
+
<div class="toolbar-group">
|
|
49
|
+
<ToolbarButton active={formatState.bold} title="Bold (Ctrl+B)" onclick={() => exec('bold')}>
|
|
50
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg>
|
|
51
|
+
</ToolbarButton>
|
|
52
|
+
<ToolbarButton active={formatState.italic} title="Italic (Ctrl+I)" onclick={() => exec('italic')}>
|
|
53
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg>
|
|
54
|
+
</ToolbarButton>
|
|
55
|
+
<ToolbarButton active={formatState.underline} title="Underline (Ctrl+U)" onclick={() => exec('underline')}>
|
|
56
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/></svg>
|
|
57
|
+
</ToolbarButton>
|
|
58
|
+
<ToolbarButton active={formatState.strikeThrough} title="Strikethrough" onclick={() => exec('strikeThrough')}>
|
|
59
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M16 4c-.5-1.5-2.5-3-5-3-3 0-5 2-5 4.5 0 2 1 3.5 5 5"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M8 20c.5 1.5 2.5 3 5 3 3 0 5-2 5-4.5 0-1.5-.5-2.5-2-3.5"/></svg>
|
|
60
|
+
</ToolbarButton>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="toolbar-divider"></div>
|
|
64
|
+
|
|
65
|
+
<!-- Colors -->
|
|
66
|
+
<div class="toolbar-group">
|
|
67
|
+
<ColorPicker value="#2C2520" title="Text color" onchange={(c) => exec('foreColor', c)} />
|
|
68
|
+
<ColorPicker value="#FFFBCC" title="Highlight" onchange={(c) => exec('hiliteColor', c)} />
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="toolbar-divider"></div>
|
|
72
|
+
|
|
73
|
+
<!-- Lists -->
|
|
74
|
+
<div class="toolbar-group">
|
|
75
|
+
<ToolbarButton title="Bullet list" onclick={() => exec('insertUnorderedList')}>
|
|
76
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="5" cy="6" r="1.5" fill="currentColor"/><circle cx="5" cy="12" r="1.5" fill="currentColor"/><circle cx="5" cy="18" r="1.5" fill="currentColor"/></svg>
|
|
77
|
+
</ToolbarButton>
|
|
78
|
+
<ToolbarButton title="Numbered list" onclick={() => exec('insertOrderedList')}>
|
|
79
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="10" y1="6" x2="20" y2="6"/><line x1="10" y1="12" x2="20" y2="12"/><line x1="10" y1="18" x2="20" y2="18"/><text x="3" y="8" font-size="8" fill="currentColor" stroke="none" font-family="sans-serif">1</text><text x="3" y="14" font-size="8" fill="currentColor" stroke="none" font-family="sans-serif">2</text><text x="3" y="20" font-size="8" fill="currentColor" stroke="none" font-family="sans-serif">3</text></svg>
|
|
80
|
+
</ToolbarButton>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="toolbar-divider"></div>
|
|
84
|
+
|
|
85
|
+
<!-- Alignment -->
|
|
86
|
+
<div class="toolbar-group">
|
|
87
|
+
<ToolbarButton title="Align left" onclick={() => exec('justifyLeft')}>
|
|
88
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/></svg>
|
|
89
|
+
</ToolbarButton>
|
|
90
|
+
<ToolbarButton title="Align center" onclick={() => exec('justifyCenter')}>
|
|
91
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
|
|
92
|
+
</ToolbarButton>
|
|
93
|
+
<ToolbarButton title="Align right" onclick={() => exec('justifyRight')}>
|
|
94
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/></svg>
|
|
95
|
+
</ToolbarButton>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="toolbar-divider"></div>
|
|
99
|
+
|
|
100
|
+
<!-- Insert -->
|
|
101
|
+
<div class="toolbar-group">
|
|
102
|
+
<ToolbarButton title="Insert link (Ctrl+K)" onclick={oninsertlink}>
|
|
103
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
|
104
|
+
</ToolbarButton>
|
|
105
|
+
<ToolbarButton title="Insert image" onclick={oninsertimage}>
|
|
106
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
|
|
107
|
+
</ToolbarButton>
|
|
108
|
+
<ToolbarButton title="Horizontal rule" onclick={() => exec('insertHorizontalRule')}>
|
|
109
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="12" x2="21" y2="12"/></svg>
|
|
110
|
+
</ToolbarButton>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="toolbar-divider"></div>
|
|
114
|
+
|
|
115
|
+
<!-- History -->
|
|
116
|
+
<div class="toolbar-group">
|
|
117
|
+
<ToolbarButton title="Undo (Ctrl+Z)" onclick={() => exec('undo')}>
|
|
118
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 7v6h6"/><path d="M3 13c0 0 2.03-7 9-7a9 9 0 0 1 9 9 9 9 0 0 1-9 9c-3.5 0-6-1.5-7.5-4"/></svg>
|
|
119
|
+
</ToolbarButton>
|
|
120
|
+
<ToolbarButton title="Redo" onclick={() => exec('redo')}>
|
|
121
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 7v6h-6"/><path d="M21 13c0 0-2.03-7-9-7a9 9 0 0 0-9 9 9 9 0 0 0 9 9c3.5 0 6-1.5 7.5-4"/></svg>
|
|
122
|
+
</ToolbarButton>
|
|
123
|
+
<ToolbarButton title="Clear formatting" onclick={() => exec('removeFormat')}>
|
|
124
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/><line x1="18" y1="4" x2="4" y2="20" stroke="var(--accent)" stroke-width="2"/></svg>
|
|
125
|
+
</ToolbarButton>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<style>
|
|
130
|
+
.toolbar {
|
|
131
|
+
position: static;
|
|
132
|
+
background: var(--bg-toolbar);
|
|
133
|
+
border: 1px solid var(--border);
|
|
134
|
+
border-radius: var(--radius);
|
|
135
|
+
padding: 6px 8px;
|
|
136
|
+
margin: 24px 0 16px;
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
gap: 2px;
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
box-shadow: var(--shadow-sm);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.toolbar-group {
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
gap: 2px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.toolbar-divider {
|
|
151
|
+
width: 1px;
|
|
152
|
+
height: 24px;
|
|
153
|
+
background: var(--border);
|
|
154
|
+
margin: 0 6px;
|
|
155
|
+
flex-shrink: 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.tb-select {
|
|
159
|
+
height: 34px;
|
|
160
|
+
padding: 0 10px;
|
|
161
|
+
border: none;
|
|
162
|
+
background: transparent;
|
|
163
|
+
border-radius: var(--radius-sm);
|
|
164
|
+
font-family: 'DM Sans', sans-serif;
|
|
165
|
+
font-size: 13px;
|
|
166
|
+
font-weight: 500;
|
|
167
|
+
color: var(--text-muted);
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
transition: all var(--transition);
|
|
170
|
+
appearance: none;
|
|
171
|
+
-webkit-appearance: none;
|
|
172
|
+
padding-right: 22px;
|
|
173
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238A7E72' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
174
|
+
background-repeat: no-repeat;
|
|
175
|
+
background-position: right 6px center;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.tb-select:hover {
|
|
179
|
+
background: var(--surface);
|
|
180
|
+
color: var(--text);
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
formatState?: Record<string, boolean>;
|
|
3
|
+
blockType?: string;
|
|
4
|
+
onexec?: (cmd: string, value?: string | null) => void;
|
|
5
|
+
onblock?: (tag: string) => void;
|
|
6
|
+
oninsertlink?: () => void;
|
|
7
|
+
oninsertimage?: () => void;
|
|
8
|
+
}
|
|
9
|
+
declare const Toolbar: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type Toolbar = ReturnType<typeof Toolbar>;
|
|
11
|
+
export default Toolbar;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
active?: boolean;
|
|
6
|
+
title?: string;
|
|
7
|
+
onclick?: () => void;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { active = false, title = '', onclick, children }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<button
|
|
15
|
+
class="tb"
|
|
16
|
+
class:active
|
|
17
|
+
{title}
|
|
18
|
+
onclick={onclick}
|
|
19
|
+
onmousedown={(e) => e.preventDefault()}
|
|
20
|
+
type="button"
|
|
21
|
+
>
|
|
22
|
+
{@render children()}
|
|
23
|
+
</button>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
.tb {
|
|
27
|
+
width: 34px;
|
|
28
|
+
height: 34px;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
border: none;
|
|
33
|
+
background: transparent;
|
|
34
|
+
border-radius: var(--radius-sm);
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
color: var(--text-muted);
|
|
37
|
+
transition: all var(--transition);
|
|
38
|
+
position: relative;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.tb:hover {
|
|
42
|
+
background: var(--surface);
|
|
43
|
+
color: var(--text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.tb.active {
|
|
47
|
+
background: var(--accent-soft);
|
|
48
|
+
color: var(--accent);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.tb :global(svg) {
|
|
52
|
+
width: 18px;
|
|
53
|
+
height: 18px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tb:hover::after {
|
|
57
|
+
content: attr(title);
|
|
58
|
+
position: absolute;
|
|
59
|
+
bottom: -30px;
|
|
60
|
+
left: 50%;
|
|
61
|
+
transform: translateX(-50%);
|
|
62
|
+
background: var(--ink);
|
|
63
|
+
color: #e8e2da;
|
|
64
|
+
font-size: 11px;
|
|
65
|
+
padding: 4px 8px;
|
|
66
|
+
border-radius: 4px;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
z-index: 10;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
active?: boolean;
|
|
4
|
+
title?: string;
|
|
5
|
+
onclick?: () => void;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
declare const ToolbarButton: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type ToolbarButton = ReturnType<typeof ToolbarButton>;
|
|
10
|
+
export default ToolbarButton;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as RtfViewer } from './RtfViewer.svelte';
|
|
2
|
+
export { default as InkEditor } from './components/InkEditor.svelte';
|
|
3
|
+
export { rtfToHtml, readRtfFile } from './rtf-parser.js';
|
|
4
|
+
export { htmlToRtf } from './rtf-writer.js';
|
|
5
|
+
export { htmlToMarkdown, downloadFile } from './utils.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Svelte components
|
|
2
|
+
export { default as RtfViewer } from './RtfViewer.svelte';
|
|
3
|
+
export { default as InkEditor } from './components/InkEditor.svelte';
|
|
4
|
+
// RTF utilities
|
|
5
|
+
export { rtfToHtml, readRtfFile } from './rtf-parser.js';
|
|
6
|
+
export { htmlToRtf } from './rtf-writer.js';
|
|
7
|
+
export { htmlToMarkdown, downloadFile } from './utils.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RTF to HTML converter
|
|
3
|
+
*
|
|
4
|
+
* Parses RTF control words and groups into an HTML string.
|
|
5
|
+
* Supports: bold, italic, underline, strikethrough, super/subscript,
|
|
6
|
+
* font size, foreground color, color table, font table, paragraphs,
|
|
7
|
+
* bullet/numbered lists, Unicode characters, hex escapes, and nested groups.
|
|
8
|
+
*/
|
|
9
|
+
export declare function rtfToHtml(rtfString: string): string;
|
|
10
|
+
export declare function readRtfFile(file: File): Promise<string>;
|