@x33025/sveltely 0.1.10 → 0.1.12
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/dist/components/Library/ArticleEditor/ArticleBlockCode.svelte +21 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockCode.svelte.d.ts +8 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockDragControl.svelte +144 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockDragControl.svelte.d.ts +14 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockFAQ.svelte +47 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockFAQ.svelte.d.ts +8 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockFallback.svelte +79 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockFallback.svelte.d.ts +15 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockHeading.svelte +73 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockHeading.svelte.d.ts +14 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockImage.svelte +48 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockImage.svelte.d.ts +9 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockInsertControl.svelte +120 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockInsertControl.svelte.d.ts +9 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockList.svelte +114 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockList.svelte.d.ts +15 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockParagraph.svelte +79 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockParagraph.svelte.d.ts +15 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockShell.svelte +127 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockShell.svelte.d.ts +22 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockTable.svelte +274 -0
- package/dist/components/Library/ArticleEditor/ArticleBlockTable.svelte.d.ts +13 -0
- package/dist/components/Library/ArticleEditor/ArticleEditor.svelte +192 -0
- package/dist/components/Library/ArticleEditor/ArticleEditor.svelte.d.ts +39 -0
- package/dist/components/Library/ArticleEditor/ArticleEditorBody.svelte +328 -0
- package/dist/components/Library/ArticleEditor/ArticleEditorBody.svelte.d.ts +31 -0
- package/dist/components/Library/ArticleEditor/ArticleEditorHeader.svelte +57 -0
- package/dist/components/Library/ArticleEditor/ArticleEditorHeader.svelte.d.ts +11 -0
- package/dist/components/Library/ArticleEditor/ArticleImagePreview.svelte +71 -0
- package/dist/components/Library/ArticleEditor/ArticleImagePreview.svelte.d.ts +8 -0
- package/dist/components/Library/ArticleEditor/articleEditor.svelte.js +532 -0
- package/dist/components/Library/ArticleEditor/index.d.ts +18 -0
- package/dist/components/Library/ArticleEditor/index.js +16 -0
- package/dist/components/Library/ArticleEditor/types.d.ts +37 -0
- package/dist/components/Library/ArticleEditor/types.js +1 -0
- package/dist/components/Library/Floating/Floating.svelte +2 -1
- package/dist/components/Library/Grid/index.d.ts +1 -0
- package/dist/components/Library/Grid/index.js +1 -0
- package/dist/components/Library/HStack/HStack.svelte +12 -7
- package/dist/components/Library/Sheet/Sheet.svelte +1 -0
- package/dist/components/Library/TextEditor/TextEditor.svelte +94 -0
- package/dist/components/Library/TextEditor/TextEditor.svelte.d.ts +16 -0
- package/dist/components/Library/TextEditor/index.d.ts +1 -0
- package/dist/components/Library/TextEditor/index.js +1 -0
- package/dist/components/Library/TokenSearchField/TokenSearchField.svelte +2 -1
- package/dist/components/Library/VStack/VStack.svelte +12 -7
- package/dist/components/Local/ComponentGrid.svelte +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/style/index.css +1 -0
- package/dist/style.css +233 -0
- package/package.json +1 -1
- package/dist/components/Library/GridItem/index.d.ts +0 -1
- package/dist/components/Library/GridItem/index.js +0 -1
- /package/dist/components/Library/{GridItem → Grid}/GridItem.svelte +0 -0
- /package/dist/components/Library/{GridItem → Grid}/GridItem.svelte.d.ts +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TextEditor from '../TextEditor';
|
|
3
|
+
import type { BlockDraft } from './types.js';
|
|
4
|
+
|
|
5
|
+
let { block, onUpdate } = $props<{
|
|
6
|
+
block: BlockDraft;
|
|
7
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
8
|
+
}>();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<TextEditor
|
|
12
|
+
autosize
|
|
13
|
+
rows={6}
|
|
14
|
+
value={block.code ?? ''}
|
|
15
|
+
onInput={(event) =>
|
|
16
|
+
onUpdate(block.id, {
|
|
17
|
+
code: (event.currentTarget as HTMLTextAreaElement).value
|
|
18
|
+
})}
|
|
19
|
+
className="bg-zinc-950 px-4 py-4 font-mono text-sm leading-6 text-zinc-100"
|
|
20
|
+
placeholder="Code block"
|
|
21
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BlockDraft } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
block: BlockDraft;
|
|
4
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
5
|
+
};
|
|
6
|
+
declare const ArticleBlockCode: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ArticleBlockCode = ReturnType<typeof ArticleBlockCode>;
|
|
8
|
+
export default ArticleBlockCode;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { GripVertical, Heading2, Heading3, List, ListOrdered, Type } from '@lucide/svelte';
|
|
3
|
+
import Dropdown from '../Dropdown';
|
|
4
|
+
import { articleBlockLabel, type BlockTextFormat } from './articleEditor.svelte.js';
|
|
5
|
+
import type { BlockDraft } from './types.js';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
block,
|
|
9
|
+
textFormat,
|
|
10
|
+
canChangeTextFormat,
|
|
11
|
+
onSelect,
|
|
12
|
+
onChangeTextFormat,
|
|
13
|
+
onDragStart,
|
|
14
|
+
onDragEnd
|
|
15
|
+
} = $props<{
|
|
16
|
+
block: BlockDraft;
|
|
17
|
+
textFormat: BlockTextFormat | null;
|
|
18
|
+
canChangeTextFormat: boolean;
|
|
19
|
+
onSelect: (id: string) => void;
|
|
20
|
+
onChangeTextFormat: (id: string, format: BlockTextFormat) => void;
|
|
21
|
+
onDragStart: (event: DragEvent, id: string) => void;
|
|
22
|
+
onDragEnd: () => void;
|
|
23
|
+
}>();
|
|
24
|
+
|
|
25
|
+
const blockTextFormatLabels: Record<BlockTextFormat, string> = {
|
|
26
|
+
paragraph: 'Text',
|
|
27
|
+
'heading-2': 'Heading 2',
|
|
28
|
+
'heading-3': 'Heading 3',
|
|
29
|
+
bullet_list: 'Bulleted list',
|
|
30
|
+
numbered_list: 'Numbered list'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const selectedLabel = () =>
|
|
34
|
+
textFormat ? blockTextFormatLabels[textFormat as BlockTextFormat] : articleBlockLabel(block);
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<Dropdown
|
|
38
|
+
value={textFormat}
|
|
39
|
+
selectedLabel={selectedLabel()}
|
|
40
|
+
placement="bottom"
|
|
41
|
+
showCheck={true}
|
|
42
|
+
onValueChange={(value: BlockTextFormat | null) => {
|
|
43
|
+
if (typeof value !== 'string') return;
|
|
44
|
+
onChangeTextFormat(block.id, value);
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{#snippet trigger(triggerState: any)}
|
|
48
|
+
<button
|
|
49
|
+
use:triggerState.useTrigger
|
|
50
|
+
type="button"
|
|
51
|
+
draggable="true"
|
|
52
|
+
aria-label="Block options and drag to reorder"
|
|
53
|
+
aria-expanded={triggerState.open}
|
|
54
|
+
aria-haspopup="dialog"
|
|
55
|
+
class="article-block-control-button"
|
|
56
|
+
onclick={(event: MouseEvent) => {
|
|
57
|
+
event.stopPropagation();
|
|
58
|
+
onSelect(block.id);
|
|
59
|
+
triggerState.toggle();
|
|
60
|
+
}}
|
|
61
|
+
onkeydown={(event: KeyboardEvent) => {
|
|
62
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
event.stopPropagation();
|
|
65
|
+
onSelect(block.id);
|
|
66
|
+
triggerState.toggle();
|
|
67
|
+
}
|
|
68
|
+
}}
|
|
69
|
+
ondragstart={(event: DragEvent) => onDragStart(event, block.id)}
|
|
70
|
+
ondragend={onDragEnd}
|
|
71
|
+
>
|
|
72
|
+
<GripVertical size={15} />
|
|
73
|
+
</button>
|
|
74
|
+
{/snippet}
|
|
75
|
+
|
|
76
|
+
<Dropdown.Section label="Text style">
|
|
77
|
+
<Dropdown.Item value={'paragraph' as BlockTextFormat} disabled={!canChangeTextFormat}>
|
|
78
|
+
<span class="article-block-control-menu-item">
|
|
79
|
+
<Type size={14} />
|
|
80
|
+
<span>Text</span>
|
|
81
|
+
</span>
|
|
82
|
+
</Dropdown.Item>
|
|
83
|
+
<Dropdown.Item value={'heading-2' as BlockTextFormat} disabled={!canChangeTextFormat}>
|
|
84
|
+
<span class="article-block-control-menu-item">
|
|
85
|
+
<Heading2 size={14} />
|
|
86
|
+
<span>Heading 2</span>
|
|
87
|
+
</span>
|
|
88
|
+
</Dropdown.Item>
|
|
89
|
+
<Dropdown.Item value={'heading-3' as BlockTextFormat} disabled={!canChangeTextFormat}>
|
|
90
|
+
<span class="article-block-control-menu-item">
|
|
91
|
+
<Heading3 size={14} />
|
|
92
|
+
<span>Heading 3</span>
|
|
93
|
+
</span>
|
|
94
|
+
</Dropdown.Item>
|
|
95
|
+
<Dropdown.Item value={'bullet_list' as BlockTextFormat} disabled={!canChangeTextFormat}>
|
|
96
|
+
<span class="article-block-control-menu-item">
|
|
97
|
+
<List size={14} />
|
|
98
|
+
<span>Bulleted list</span>
|
|
99
|
+
</span>
|
|
100
|
+
</Dropdown.Item>
|
|
101
|
+
<Dropdown.Item value={'numbered_list' as BlockTextFormat} disabled={!canChangeTextFormat}>
|
|
102
|
+
<span class="article-block-control-menu-item">
|
|
103
|
+
<ListOrdered size={14} />
|
|
104
|
+
<span>Numbered list</span>
|
|
105
|
+
</span>
|
|
106
|
+
</Dropdown.Item>
|
|
107
|
+
</Dropdown.Section>
|
|
108
|
+
</Dropdown>
|
|
109
|
+
|
|
110
|
+
<style>
|
|
111
|
+
.article-block-control-button {
|
|
112
|
+
display: flex;
|
|
113
|
+
width: 1.75rem;
|
|
114
|
+
height: 1.75rem;
|
|
115
|
+
cursor: grab;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
border: 0;
|
|
119
|
+
background: transparent;
|
|
120
|
+
color: #a1a1aa;
|
|
121
|
+
transition:
|
|
122
|
+
background-color 150ms,
|
|
123
|
+
color 150ms;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.article-block-control-button:hover {
|
|
127
|
+
background: #f5f5f4;
|
|
128
|
+
color: #3f3f46;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.article-block-control-button:active {
|
|
132
|
+
cursor: grabbing;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.article-block-control-menu-item {
|
|
136
|
+
display: inline-flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: 0.5rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.article-block-control-menu-item :global(svg) {
|
|
142
|
+
flex: 0 0 auto;
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type BlockTextFormat } from './articleEditor.svelte.js';
|
|
2
|
+
import type { BlockDraft } from './types.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
block: BlockDraft;
|
|
5
|
+
textFormat: BlockTextFormat | null;
|
|
6
|
+
canChangeTextFormat: boolean;
|
|
7
|
+
onSelect: (id: string) => void;
|
|
8
|
+
onChangeTextFormat: (id: string, format: BlockTextFormat) => void;
|
|
9
|
+
onDragStart: (event: DragEvent, id: string) => void;
|
|
10
|
+
onDragEnd: () => void;
|
|
11
|
+
};
|
|
12
|
+
declare const ArticleBlockDragControl: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type ArticleBlockDragControl = ReturnType<typeof ArticleBlockDragControl>;
|
|
14
|
+
export default ArticleBlockDragControl;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TextEditor from '../TextEditor';
|
|
3
|
+
import type { BlockDraft } from './types.js';
|
|
4
|
+
|
|
5
|
+
let { block, onUpdateItem } = $props<{
|
|
6
|
+
block: BlockDraft;
|
|
7
|
+
onUpdateItem: (
|
|
8
|
+
blockID: string,
|
|
9
|
+
index: number,
|
|
10
|
+
field: 'question' | 'answer',
|
|
11
|
+
value: string
|
|
12
|
+
) => void;
|
|
13
|
+
}>();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="space-y-4">
|
|
17
|
+
{#each block.faqItems ?? [] as item, index}
|
|
18
|
+
<div class="border-b border-zinc-200/70 pb-4 last:border-b-0 last:pb-0">
|
|
19
|
+
<input
|
|
20
|
+
class="mb-2 w-full border-0 bg-transparent px-0 text-[17px] leading-7 font-semibold text-zinc-950 outline-none placeholder:text-zinc-300"
|
|
21
|
+
value={item.question}
|
|
22
|
+
oninput={(event) =>
|
|
23
|
+
onUpdateItem(
|
|
24
|
+
block.id,
|
|
25
|
+
index,
|
|
26
|
+
'question',
|
|
27
|
+
(event.currentTarget as HTMLInputElement).value
|
|
28
|
+
)}
|
|
29
|
+
placeholder="Question"
|
|
30
|
+
/>
|
|
31
|
+
<TextEditor
|
|
32
|
+
autosize
|
|
33
|
+
rows={1}
|
|
34
|
+
className="text-[15px] leading-7 text-zinc-700 placeholder:text-zinc-300"
|
|
35
|
+
value={item.answer}
|
|
36
|
+
onInput={(event) =>
|
|
37
|
+
onUpdateItem(
|
|
38
|
+
block.id,
|
|
39
|
+
index,
|
|
40
|
+
'answer',
|
|
41
|
+
(event.currentTarget as HTMLTextAreaElement).value
|
|
42
|
+
)}
|
|
43
|
+
placeholder="Answer"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
{/each}
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BlockDraft } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
block: BlockDraft;
|
|
4
|
+
onUpdateItem: (blockID: string, index: number, field: 'question' | 'answer', value: string) => void;
|
|
5
|
+
};
|
|
6
|
+
declare const ArticleBlockFAQ: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type ArticleBlockFAQ = ReturnType<typeof ArticleBlockFAQ>;
|
|
8
|
+
export default ArticleBlockFAQ;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TextEditor from '../TextEditor';
|
|
3
|
+
import type { BlockDraft } from './types.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
block,
|
|
7
|
+
onUpdate,
|
|
8
|
+
onCreateParagraphAfter,
|
|
9
|
+
onMergeBlockWithPrevious,
|
|
10
|
+
onConvertToList,
|
|
11
|
+
onRemoveBlock,
|
|
12
|
+
shouldFocus,
|
|
13
|
+
focusPosition,
|
|
14
|
+
onFocused
|
|
15
|
+
} = $props<{
|
|
16
|
+
block: BlockDraft;
|
|
17
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
18
|
+
onCreateParagraphAfter?: (id: string, text?: string, currentText?: string | null) => void;
|
|
19
|
+
onMergeBlockWithPrevious?: (id: string) => void;
|
|
20
|
+
onConvertToList?: (id: string, kind: 'bullet_list' | 'numbered_list') => void;
|
|
21
|
+
onRemoveBlock?: (id: string) => void;
|
|
22
|
+
shouldFocus?: boolean;
|
|
23
|
+
focusPosition?: number | null;
|
|
24
|
+
onFocused?: () => void;
|
|
25
|
+
}>();
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<TextEditor
|
|
29
|
+
autosize
|
|
30
|
+
rows={1}
|
|
31
|
+
value={block.text ?? ''}
|
|
32
|
+
onInput={(event) =>
|
|
33
|
+
onUpdate(block.id, {
|
|
34
|
+
text: (event.currentTarget as HTMLTextAreaElement).value
|
|
35
|
+
})}
|
|
36
|
+
onKeyDown={(event) => {
|
|
37
|
+
const value = (event.currentTarget as HTMLTextAreaElement).value.trim();
|
|
38
|
+
if (event.key === ' ' && value === '-') {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
onConvertToList?.(block.id, 'bullet_list');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (event.key === ' ' && /^\d+\.$/.test(value)) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
onConvertToList?.(block.id, 'numbered_list');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
const textarea = event.currentTarget as HTMLTextAreaElement;
|
|
51
|
+
const text = textarea.value;
|
|
52
|
+
const before = text.slice(0, textarea.selectionStart);
|
|
53
|
+
const after = text.slice(textarea.selectionEnd);
|
|
54
|
+
if (!text.trim()) {
|
|
55
|
+
onRemoveBlock?.(block.id);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
onCreateParagraphAfter?.(block.id, after, before);
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
event.key === 'Backspace' &&
|
|
62
|
+
!event.shiftKey &&
|
|
63
|
+
!event.metaKey &&
|
|
64
|
+
!event.ctrlKey &&
|
|
65
|
+
!event.altKey
|
|
66
|
+
) {
|
|
67
|
+
const textarea = event.currentTarget as HTMLTextAreaElement;
|
|
68
|
+
if (textarea.selectionStart === 0 && textarea.selectionEnd === 0) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
onMergeBlockWithPrevious?.(block.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
{shouldFocus}
|
|
75
|
+
{focusPosition}
|
|
76
|
+
{onFocused}
|
|
77
|
+
className="text-[16px] leading-7 text-zinc-700 placeholder:text-zinc-300"
|
|
78
|
+
placeholder="Block content"
|
|
79
|
+
/>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BlockDraft } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
block: BlockDraft;
|
|
4
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
5
|
+
onCreateParagraphAfter?: (id: string, text?: string, currentText?: string | null) => void;
|
|
6
|
+
onMergeBlockWithPrevious?: (id: string) => void;
|
|
7
|
+
onConvertToList?: (id: string, kind: 'bullet_list' | 'numbered_list') => void;
|
|
8
|
+
onRemoveBlock?: (id: string) => void;
|
|
9
|
+
shouldFocus?: boolean;
|
|
10
|
+
focusPosition?: number | null;
|
|
11
|
+
onFocused?: () => void;
|
|
12
|
+
};
|
|
13
|
+
declare const ArticleBlockFallback: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type ArticleBlockFallback = ReturnType<typeof ArticleBlockFallback>;
|
|
15
|
+
export default ArticleBlockFallback;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TextEditor from '../TextEditor';
|
|
3
|
+
import type { BlockDraft } from './types.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
block,
|
|
7
|
+
onUpdate,
|
|
8
|
+
onCreateParagraphAfter,
|
|
9
|
+
onMergeBlockWithPrevious,
|
|
10
|
+
onRemoveBlock,
|
|
11
|
+
shouldFocus,
|
|
12
|
+
focusPosition,
|
|
13
|
+
onFocused
|
|
14
|
+
} = $props<{
|
|
15
|
+
block: BlockDraft;
|
|
16
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
17
|
+
onCreateParagraphAfter?: (id: string, text?: string, currentText?: string | null) => void;
|
|
18
|
+
onMergeBlockWithPrevious?: (id: string) => void;
|
|
19
|
+
onRemoveBlock?: (id: string) => void;
|
|
20
|
+
shouldFocus?: boolean;
|
|
21
|
+
focusPosition?: number | null;
|
|
22
|
+
onFocused?: () => void;
|
|
23
|
+
}>();
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<TextEditor
|
|
27
|
+
autosize
|
|
28
|
+
rows={1}
|
|
29
|
+
value={block.text ?? ''}
|
|
30
|
+
onInput={(event) =>
|
|
31
|
+
onUpdate(block.id, {
|
|
32
|
+
text: (event.currentTarget as HTMLTextAreaElement).value
|
|
33
|
+
})}
|
|
34
|
+
onKeyDown={(event) => {
|
|
35
|
+
const value = (event.currentTarget as HTMLTextAreaElement).value.trim();
|
|
36
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
const textarea = event.currentTarget as HTMLTextAreaElement;
|
|
39
|
+
const text = textarea.value;
|
|
40
|
+
const before = text.slice(0, textarea.selectionStart);
|
|
41
|
+
const after = text.slice(textarea.selectionEnd);
|
|
42
|
+
if (!text.trim()) {
|
|
43
|
+
onRemoveBlock?.(block.id);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
onCreateParagraphAfter?.(block.id, after, before);
|
|
47
|
+
}
|
|
48
|
+
if (
|
|
49
|
+
event.key === 'Backspace' &&
|
|
50
|
+
!event.shiftKey &&
|
|
51
|
+
!event.metaKey &&
|
|
52
|
+
!event.ctrlKey &&
|
|
53
|
+
!event.altKey
|
|
54
|
+
) {
|
|
55
|
+
const textarea = event.currentTarget as HTMLTextAreaElement;
|
|
56
|
+
if (textarea.selectionStart === 0 && textarea.selectionEnd === 0) {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
onMergeBlockWithPrevious?.(block.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}}
|
|
62
|
+
{shouldFocus}
|
|
63
|
+
{focusPosition}
|
|
64
|
+
{onFocused}
|
|
65
|
+
className={`placeholder:text-zinc-300 ${
|
|
66
|
+
Number(block.level ?? 2) <= 1
|
|
67
|
+
? 'text-3xl font-semibold tracking-[-0.03em]'
|
|
68
|
+
: Number(block.level ?? 2) === 2
|
|
69
|
+
? 'text-2xl font-semibold tracking-[-0.025em]'
|
|
70
|
+
: 'text-xl font-semibold'
|
|
71
|
+
}`}
|
|
72
|
+
placeholder="Heading"
|
|
73
|
+
/>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BlockDraft } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
block: BlockDraft;
|
|
4
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
5
|
+
onCreateParagraphAfter?: (id: string, text?: string, currentText?: string | null) => void;
|
|
6
|
+
onMergeBlockWithPrevious?: (id: string) => void;
|
|
7
|
+
onRemoveBlock?: (id: string) => void;
|
|
8
|
+
shouldFocus?: boolean;
|
|
9
|
+
focusPosition?: number | null;
|
|
10
|
+
onFocused?: () => void;
|
|
11
|
+
};
|
|
12
|
+
declare const ArticleBlockHeading: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type ArticleBlockHeading = ReturnType<typeof ArticleBlockHeading>;
|
|
14
|
+
export default ArticleBlockHeading;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ArticleImagePreview from './ArticleImagePreview.svelte';
|
|
3
|
+
import TextEditor from '../TextEditor';
|
|
4
|
+
import type { BlockDraft } from './types.js';
|
|
5
|
+
|
|
6
|
+
let { block, titleFallback, onUpdate } = $props<{
|
|
7
|
+
block: BlockDraft;
|
|
8
|
+
titleFallback: string;
|
|
9
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
10
|
+
}>();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="article-block-image">
|
|
14
|
+
<ArticleImagePreview src={block.imageURL} alt={block.imageAlt ?? titleFallback} />
|
|
15
|
+
<div>
|
|
16
|
+
<TextEditor
|
|
17
|
+
autosize
|
|
18
|
+
rows={1}
|
|
19
|
+
className="article-block-image-description"
|
|
20
|
+
value={block.imageAlt ?? ''}
|
|
21
|
+
onInput={(event) =>
|
|
22
|
+
onUpdate(block.id, {
|
|
23
|
+
imageAlt: (event.currentTarget as HTMLTextAreaElement).value
|
|
24
|
+
})}
|
|
25
|
+
placeholder="Alt text"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<style>
|
|
31
|
+
.article-block-image {
|
|
32
|
+
display: grid;
|
|
33
|
+
gap: 1rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:global(.article-block-image-description) {
|
|
37
|
+
border: 0;
|
|
38
|
+
background: transparent;
|
|
39
|
+
padding: 0;
|
|
40
|
+
color: #3f3f46;
|
|
41
|
+
font-size: 0.875rem;
|
|
42
|
+
line-height: 1.5rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:global(.article-block-image-description::placeholder) {
|
|
46
|
+
color: #d4d4d8;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlockDraft } from './types.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
block: BlockDraft;
|
|
4
|
+
titleFallback: string;
|
|
5
|
+
onUpdate: (id: string, patch: Partial<BlockDraft>) => void;
|
|
6
|
+
};
|
|
7
|
+
declare const ArticleBlockImage: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type ArticleBlockImage = ReturnType<typeof ArticleBlockImage>;
|
|
9
|
+
export default ArticleBlockImage;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ImageIcon, List, ListOrdered, Plus, Table2, Type } from '@lucide/svelte';
|
|
3
|
+
import Dropdown from '../Dropdown';
|
|
4
|
+
import type { BlockInsertKind } from './articleEditor.svelte.js';
|
|
5
|
+
|
|
6
|
+
let { blockID, onSelect, onInsertBlockAfter } = $props<{
|
|
7
|
+
blockID: string;
|
|
8
|
+
onSelect: (id: string) => void;
|
|
9
|
+
onInsertBlockAfter: (id: string, kind: BlockInsertKind) => void;
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const insertBlockLabels: Record<BlockInsertKind, string> = {
|
|
13
|
+
paragraph: 'Paragraph',
|
|
14
|
+
image: 'Image',
|
|
15
|
+
table: 'Table',
|
|
16
|
+
bullet_list: 'Bulleted list',
|
|
17
|
+
numbered_list: 'Numbered list'
|
|
18
|
+
};
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<Dropdown
|
|
22
|
+
value={null}
|
|
23
|
+
selectedLabel="Add block"
|
|
24
|
+
placement="bottom"
|
|
25
|
+
showCheck={false}
|
|
26
|
+
onValueChange={(value: BlockInsertKind | null) => {
|
|
27
|
+
if (typeof value !== 'string') return;
|
|
28
|
+
onInsertBlockAfter(blockID, value);
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
{#snippet trigger(triggerState: any)}
|
|
32
|
+
<button
|
|
33
|
+
use:triggerState.useTrigger
|
|
34
|
+
type="button"
|
|
35
|
+
aria-label="Add block after"
|
|
36
|
+
aria-expanded={triggerState.open}
|
|
37
|
+
aria-haspopup="dialog"
|
|
38
|
+
class="article-block-control-button"
|
|
39
|
+
onclick={(event: MouseEvent) => {
|
|
40
|
+
event.stopPropagation();
|
|
41
|
+
onSelect(blockID);
|
|
42
|
+
triggerState.toggle();
|
|
43
|
+
}}
|
|
44
|
+
onkeydown={(event: KeyboardEvent) => {
|
|
45
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.stopPropagation();
|
|
48
|
+
onSelect(blockID);
|
|
49
|
+
triggerState.toggle();
|
|
50
|
+
}
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<Plus size={15} />
|
|
54
|
+
</button>
|
|
55
|
+
{/snippet}
|
|
56
|
+
|
|
57
|
+
<Dropdown.Section label="Insert block">
|
|
58
|
+
<Dropdown.Item value={'paragraph' as BlockInsertKind}>
|
|
59
|
+
<span class="article-block-control-menu-item">
|
|
60
|
+
<Type size={14} />
|
|
61
|
+
<span>{insertBlockLabels.paragraph}</span>
|
|
62
|
+
</span>
|
|
63
|
+
</Dropdown.Item>
|
|
64
|
+
<Dropdown.Item value={'image' as BlockInsertKind}>
|
|
65
|
+
<span class="article-block-control-menu-item">
|
|
66
|
+
<ImageIcon size={14} />
|
|
67
|
+
<span>{insertBlockLabels.image}</span>
|
|
68
|
+
</span>
|
|
69
|
+
</Dropdown.Item>
|
|
70
|
+
<Dropdown.Item value={'table' as BlockInsertKind}>
|
|
71
|
+
<span class="article-block-control-menu-item">
|
|
72
|
+
<Table2 size={14} />
|
|
73
|
+
<span>{insertBlockLabels.table}</span>
|
|
74
|
+
</span>
|
|
75
|
+
</Dropdown.Item>
|
|
76
|
+
<Dropdown.Item value={'bullet_list' as BlockInsertKind}>
|
|
77
|
+
<span class="article-block-control-menu-item">
|
|
78
|
+
<List size={14} />
|
|
79
|
+
<span>{insertBlockLabels.bullet_list}</span>
|
|
80
|
+
</span>
|
|
81
|
+
</Dropdown.Item>
|
|
82
|
+
<Dropdown.Item value={'numbered_list' as BlockInsertKind}>
|
|
83
|
+
<span class="article-block-control-menu-item">
|
|
84
|
+
<ListOrdered size={14} />
|
|
85
|
+
<span>{insertBlockLabels.numbered_list}</span>
|
|
86
|
+
</span>
|
|
87
|
+
</Dropdown.Item>
|
|
88
|
+
</Dropdown.Section>
|
|
89
|
+
</Dropdown>
|
|
90
|
+
|
|
91
|
+
<style>
|
|
92
|
+
.article-block-control-button {
|
|
93
|
+
display: flex;
|
|
94
|
+
width: 1.75rem;
|
|
95
|
+
height: 1.75rem;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
border: 0;
|
|
99
|
+
background: transparent;
|
|
100
|
+
color: #a1a1aa;
|
|
101
|
+
transition:
|
|
102
|
+
background-color 150ms,
|
|
103
|
+
color 150ms;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.article-block-control-button:hover {
|
|
107
|
+
background: #f5f5f4;
|
|
108
|
+
color: #3f3f46;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.article-block-control-menu-item {
|
|
112
|
+
display: inline-flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
gap: 0.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.article-block-control-menu-item :global(svg) {
|
|
118
|
+
flex: 0 0 auto;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlockInsertKind } from './articleEditor.svelte.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
blockID: string;
|
|
4
|
+
onSelect: (id: string) => void;
|
|
5
|
+
onInsertBlockAfter: (id: string, kind: BlockInsertKind) => void;
|
|
6
|
+
};
|
|
7
|
+
declare const ArticleBlockInsertControl: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type ArticleBlockInsertControl = ReturnType<typeof ArticleBlockInsertControl>;
|
|
9
|
+
export default ArticleBlockInsertControl;
|