@shelamkoff/rector 1.0.0 → 1.0.2
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 +25 -23
- package/dist/core/clipboard/Clipboard.d.ts +2 -0
- package/dist/core/clipboard/Clipboard.js +52 -24
- package/dist/core/index.js +195 -194
- package/dist/core/locale/en.d.ts +1 -0
- package/dist/core/locale/en.js +1 -0
- package/dist/core/locale/ru.d.ts +1 -0
- package/dist/core/locale/ru.js +1 -0
- package/dist/core/themes/light.css +1 -1
- package/dist/core/themes/variables.css +72 -10
- package/dist/inline-plugins/mention/index.js +1 -2
- package/dist/locale/en.d.ts +41 -0
- package/dist/locale/ru.d.ts +41 -0
- package/dist/plugins/attaches/attaches.css +16 -14
- package/dist/plugins/attaches/index.js +997 -921
- package/dist/plugins/attaches/locale/en.d.ts +9 -0
- package/dist/plugins/attaches/locale/en.js +11 -2
- package/dist/plugins/attaches/locale/ru.d.ts +9 -0
- package/dist/plugins/attaches/locale/ru.js +11 -2
- package/dist/plugins/carousel/carousel.css +213 -49
- package/dist/plugins/carousel/index.js +918 -859
- package/dist/plugins/carousel/locale/en.d.ts +15 -0
- package/dist/plugins/carousel/locale/en.js +18 -3
- package/dist/plugins/carousel/locale/ru.d.ts +15 -0
- package/dist/plugins/carousel/locale/ru.js +18 -3
- package/dist/plugins/checklist/index.js +1 -2
- package/dist/plugins/code/index.js +1 -2
- package/dist/plugins/columns/index.js +1 -2
- package/dist/plugins/delimiter/index.js +1 -2
- package/dist/plugins/embed/index.js +1 -2
- package/dist/plugins/gallery/gallery.css +6 -1
- package/dist/plugins/gallery/index.js +375 -355
- package/dist/plugins/gallery/locale/en.d.ts +8 -0
- package/dist/plugins/gallery/locale/en.js +11 -3
- package/dist/plugins/gallery/locale/ru.d.ts +8 -0
- package/dist/plugins/gallery/locale/ru.js +11 -3
- package/dist/plugins/gallery/view-empty.d.ts +2 -0
- package/dist/plugins/gallery/view-empty.js +21 -15
- package/dist/plugins/gallery/view-filled.d.ts +2 -2
- package/dist/plugins/gallery/view-filled.js +61 -56
- package/dist/plugins/heading/index.js +1 -2
- package/dist/plugins/image/image.css +7 -2
- package/dist/plugins/image/index.js +365 -346
- package/dist/plugins/image/locale/en.d.ts +8 -0
- package/dist/plugins/image/locale/en.js +11 -3
- package/dist/plugins/image/locale/ru.d.ts +8 -0
- package/dist/plugins/image/locale/ru.js +11 -3
- package/dist/plugins/image/view-empty.d.ts +2 -0
- package/dist/plugins/image/view-empty.js +21 -15
- package/dist/plugins/image/view-filled.d.ts +2 -2
- package/dist/plugins/image/view-filled.js +77 -72
- package/dist/plugins/link-preview/index.js +1 -2
- package/dist/plugins/list/index.js +1 -2
- package/dist/plugins/paragraph/index.js +1 -2
- package/dist/plugins/person/index.js +1 -2
- package/dist/plugins/poll/index.js +1 -2
- package/dist/plugins/quote/index.js +1 -2
- package/dist/plugins/raw/index.js +1 -2
- package/dist/plugins/shared/dropzone.d.ts +6 -0
- package/dist/plugins/shared/dropzone.js +47 -36
- package/dist/plugins/shared/layer.d.ts +13 -0
- package/dist/plugins/shared/layer.js +45 -0
- package/dist/plugins/shared/sourceEditor.css +165 -0
- package/dist/plugins/shared/sourceEditor.d.ts +97 -0
- package/dist/plugins/shared/sourceEditor.js +260 -0
- package/dist/plugins/spoiler/index.js +1 -2
- package/dist/plugins/table/index.js +1 -2
- package/dist/plugins/toggle/index.js +1 -2
- package/dist/plugins/warning/index.js +1 -2
- package/dist/renderer/index.js +1 -2
- package/dist/renderer/renderers/attaches/index.js +1 -2
- package/dist/renderer/renderers/carousel/index.js +24 -24
- package/dist/renderer/renderers/carousel/styles.css +8 -2
- package/dist/renderer/renderers/checklist/index.js +1 -2
- package/dist/renderer/renderers/code/index.js +1 -2
- package/dist/renderer/renderers/columns/index.js +1 -2
- package/dist/renderer/renderers/delimiter/index.js +1 -2
- package/dist/renderer/renderers/embed/index.js +1 -2
- package/dist/renderer/renderers/gallery/index.js +1 -2
- package/dist/renderer/renderers/heading/index.js +1 -2
- package/dist/renderer/renderers/image/index.js +1 -2
- package/dist/renderer/renderers/link-preview/index.js +1 -2
- package/dist/renderer/renderers/list/index.js +1 -2
- package/dist/renderer/renderers/paragraph/index.js +1 -2
- package/dist/renderer/renderers/person/index.js +1 -2
- package/dist/renderer/renderers/poll/index.js +1 -2
- package/dist/renderer/renderers/quote/index.js +1 -2
- package/dist/renderer/renderers/spoiler/index.js +1 -2
- package/dist/renderer/renderers/table/index.js +1 -2
- package/dist/renderer/renderers/toggle/index.js +1 -2
- package/dist/renderer/renderers/warning/index.js +1 -2
- package/package.json +27 -29
- package/dist/shared/resolvePath.d.ts +0 -8
- package/dist/shared/resolvePath.js +0 -10
|
@@ -6,6 +6,14 @@ declare const _default: {
|
|
|
6
6
|
'plugin.image.dropzone': string;
|
|
7
7
|
'plugin.image.dropzoneUpload': string;
|
|
8
8
|
'plugin.image.dropzoneText': string;
|
|
9
|
+
'plugin.image.dropzoneUrlPrefix': string;
|
|
10
|
+
'plugin.image.dropzoneUrl': string;
|
|
11
|
+
'plugin.image.urlEditorTitle': string;
|
|
12
|
+
'plugin.image.urlEditorLabel': string;
|
|
13
|
+
'plugin.image.urlEditorPlaceholder': string;
|
|
14
|
+
'plugin.image.sourceSubmit': string;
|
|
15
|
+
'plugin.image.sourceCancel': string;
|
|
16
|
+
'plugin.image.invalidUrl': string;
|
|
9
17
|
'plugin.image.replace': string;
|
|
10
18
|
'plugin.image.delete': string;
|
|
11
19
|
'plugin.image.caption': string;
|
|
@@ -6,6 +6,14 @@ export default {
|
|
|
6
6
|
'plugin.image.dropzone': 'or drag and drop a file here',
|
|
7
7
|
'plugin.image.dropzoneUpload': 'Upload',
|
|
8
8
|
'plugin.image.dropzoneText': 'an image from your device or drag and drop it here',
|
|
9
|
+
'plugin.image.dropzoneUrlPrefix': 'or',
|
|
10
|
+
'plugin.image.dropzoneUrl': 'insert by URL',
|
|
11
|
+
'plugin.image.urlEditorTitle': 'Insert image by URL',
|
|
12
|
+
'plugin.image.urlEditorLabel': 'Image URL',
|
|
13
|
+
'plugin.image.urlEditorPlaceholder': 'https://example.com/image.jpg',
|
|
14
|
+
'plugin.image.sourceSubmit': 'Insert',
|
|
15
|
+
'plugin.image.sourceCancel': 'Cancel',
|
|
16
|
+
'plugin.image.invalidUrl': 'Enter a valid image URL.',
|
|
9
17
|
'plugin.image.replace': 'Replace',
|
|
10
18
|
'plugin.image.delete': 'Delete',
|
|
11
19
|
'plugin.image.caption': 'Caption',
|
|
@@ -33,6 +41,6 @@ export default {
|
|
|
33
41
|
'plugin.image.value.fill': 'Fill',
|
|
34
42
|
'plugin.image.value.scale-down': 'Scale down',
|
|
35
43
|
'plugin.image.value.solid': 'Solid',
|
|
36
|
-
'plugin.image.value.dashed': 'Dashed',
|
|
37
|
-
'plugin.image.emptyReadonly': 'No image',
|
|
38
|
-
}
|
|
44
|
+
'plugin.image.value.dashed': 'Dashed',
|
|
45
|
+
'plugin.image.emptyReadonly': 'No image',
|
|
46
|
+
}
|
|
@@ -6,6 +6,14 @@ declare const _default: {
|
|
|
6
6
|
'plugin.image.dropzone': string;
|
|
7
7
|
'plugin.image.dropzoneUpload': string;
|
|
8
8
|
'plugin.image.dropzoneText': string;
|
|
9
|
+
'plugin.image.dropzoneUrlPrefix': string;
|
|
10
|
+
'plugin.image.dropzoneUrl': string;
|
|
11
|
+
'plugin.image.urlEditorTitle': string;
|
|
12
|
+
'plugin.image.urlEditorLabel': string;
|
|
13
|
+
'plugin.image.urlEditorPlaceholder': string;
|
|
14
|
+
'plugin.image.sourceSubmit': string;
|
|
15
|
+
'plugin.image.sourceCancel': string;
|
|
16
|
+
'plugin.image.invalidUrl': string;
|
|
9
17
|
'plugin.image.replace': string;
|
|
10
18
|
'plugin.image.delete': string;
|
|
11
19
|
'plugin.image.caption': string;
|
|
@@ -6,6 +6,14 @@ export default {
|
|
|
6
6
|
'plugin.image.dropzone': 'или перетащите файл сюда',
|
|
7
7
|
'plugin.image.dropzoneUpload': 'Загрузите',
|
|
8
8
|
'plugin.image.dropzoneText': 'изображение с устройства или перетащите его сюда',
|
|
9
|
+
'plugin.image.dropzoneUrlPrefix': 'или',
|
|
10
|
+
'plugin.image.dropzoneUrl': 'вставьте по URL',
|
|
11
|
+
'plugin.image.urlEditorTitle': 'Вставка изображения по URL',
|
|
12
|
+
'plugin.image.urlEditorLabel': 'URL изображения',
|
|
13
|
+
'plugin.image.urlEditorPlaceholder': 'https://example.com/image.jpg',
|
|
14
|
+
'plugin.image.sourceSubmit': 'Вставить',
|
|
15
|
+
'plugin.image.sourceCancel': 'Отмена',
|
|
16
|
+
'plugin.image.invalidUrl': 'Введите корректный URL изображения.',
|
|
9
17
|
'plugin.image.replace': 'Заменить',
|
|
10
18
|
'plugin.image.delete': 'Удалить',
|
|
11
19
|
'plugin.image.caption': 'Подпись',
|
|
@@ -33,6 +41,6 @@ export default {
|
|
|
33
41
|
'plugin.image.value.fill': 'Растянуть',
|
|
34
42
|
'plugin.image.value.scale-down': 'Только уменьшать',
|
|
35
43
|
'plugin.image.value.solid': 'Сплошная',
|
|
36
|
-
'plugin.image.value.dashed': 'Штриховая',
|
|
37
|
-
'plugin.image.emptyReadonly': 'Нет изображения',
|
|
38
|
-
}
|
|
44
|
+
'plugin.image.value.dashed': 'Штриховая',
|
|
45
|
+
'plugin.image.emptyReadonly': 'Нет изображения',
|
|
46
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @property {boolean} readOnly
|
|
5
5
|
* @property {(file: File) => void} onFileDropped
|
|
6
6
|
* @property {() => void} onUploadClick
|
|
7
|
+
* @property {() => void} onOpenUrlEditor
|
|
7
8
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<{url: string, alt?: string} | null> }>} customActions
|
|
8
9
|
* @property {(handler: (context: { signal: AbortSignal }) => Promise<{url: string, alt?: string} | null>) => Promise<void>} runCustomAction
|
|
9
10
|
*/
|
|
@@ -25,6 +26,7 @@ export type EmptyViewDeps = {
|
|
|
25
26
|
readOnly: boolean;
|
|
26
27
|
onFileDropped: (file: File) => void;
|
|
27
28
|
onUploadClick: () => void;
|
|
29
|
+
onOpenUrlEditor: () => void;
|
|
28
30
|
customActions: Array<{
|
|
29
31
|
icon?: string;
|
|
30
32
|
label: string;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { renderDropzone } from '../shared/dropzone.js'
|
|
2
|
-
import { isSupportedImageFile } from '../shared/fileInput.js'
|
|
1
|
+
import { renderDropzone } from '../shared/dropzone.js'
|
|
2
|
+
import { isSupportedImageFile } from '../shared/fileInput.js'
|
|
3
3
|
import { CSS } from './css.js'
|
|
4
4
|
import { ICON_SELECT } from './icons.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @typedef {Object} EmptyViewDeps
|
|
8
|
-
* @property {(key: string, fallback: string) => string} t
|
|
9
|
-
* @property {boolean} readOnly
|
|
7
|
+
* @typedef {Object} EmptyViewDeps
|
|
8
|
+
* @property {(key: string, fallback: string) => string} t
|
|
9
|
+
* @property {boolean} readOnly
|
|
10
10
|
* @property {(file: File) => void} onFileDropped
|
|
11
11
|
* @property {() => void} onUploadClick
|
|
12
|
+
* @property {() => void} onOpenUrlEditor
|
|
12
13
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<{url: string, alt?: string} | null> }>} customActions
|
|
13
14
|
* @property {(handler: (context: { signal: AbortSignal }) => Promise<{url: string, alt?: string} | null>) => Promise<void>} runCustomAction
|
|
14
15
|
*/
|
|
@@ -20,11 +21,11 @@ import { ICON_SELECT } from './icons.js'
|
|
|
20
21
|
* Listeners are attached with the state's AbortSignal so they're
|
|
21
22
|
* automatically removed on the next render or on disposal.
|
|
22
23
|
*
|
|
23
|
-
* @param {HTMLElement} wrapper
|
|
24
|
-
* @param {import('./state.js').ImageState} state
|
|
25
|
-
* @param {EmptyViewDeps} deps
|
|
26
|
-
* @returns {void}
|
|
27
|
-
*/
|
|
24
|
+
* @param {HTMLElement} wrapper
|
|
25
|
+
* @param {import('./state.js').ImageState} state
|
|
26
|
+
* @param {EmptyViewDeps} deps
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*/
|
|
28
29
|
export function renderEmptyView(wrapper, state, deps) {
|
|
29
30
|
state.resetTransient()
|
|
30
31
|
|
|
@@ -41,10 +42,15 @@ export function renderEmptyView(wrapper, state, deps) {
|
|
|
41
42
|
filled: CSS.filled,
|
|
42
43
|
}, {
|
|
43
44
|
iconHtml: ICON_SELECT,
|
|
44
|
-
uploadText: deps.t('dropzoneUpload', 'Upload'),
|
|
45
|
-
afterText: deps.t('dropzoneText', 'an image from your device or drag and drop it here'),
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
uploadText: deps.t('dropzoneUpload', 'Upload'),
|
|
46
|
+
afterText: deps.t('dropzoneText', 'an image from your device or drag and drop it here'),
|
|
47
|
+
inlineActions: [{
|
|
48
|
+
prefix: deps.t('dropzoneUrlPrefix', 'or'),
|
|
49
|
+
label: deps.t('dropzoneUrl', 'insert by URL'),
|
|
50
|
+
onSelect: deps.onOpenUrlEditor,
|
|
51
|
+
}],
|
|
52
|
+
readOnly: deps.readOnly,
|
|
53
|
+
emptyText: deps.t('emptyReadonly', 'No image'),
|
|
48
54
|
onUploadClick: deps.onUploadClick,
|
|
49
55
|
actions: deps.customActions.map(action => ({
|
|
50
56
|
icon: action.icon,
|
|
@@ -53,7 +59,7 @@ export function renderEmptyView(wrapper, state, deps) {
|
|
|
53
59
|
})),
|
|
54
60
|
onDrop: (dt) => {
|
|
55
61
|
const file = dt.files[0]
|
|
56
|
-
if (file && isSupportedImageFile(file)) {
|
|
62
|
+
if (file && isSupportedImageFile(file)) {
|
|
57
63
|
deps.onFileDropped(file)
|
|
58
64
|
}
|
|
59
65
|
},
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @property {(key: string, fallback: string) => string} t
|
|
4
4
|
* @property {boolean} readOnly
|
|
5
5
|
* @property {() => void} onTriggerFileInput
|
|
6
|
-
* @property {() => void}
|
|
6
|
+
* @property {() => void} onOpenUrlEditor
|
|
7
7
|
* @property {() => void} onDelete
|
|
8
8
|
* @property {(operation: () => void) => void} mutate
|
|
9
9
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<{url: string, alt?: string} | null> }>} customActions
|
|
@@ -27,7 +27,7 @@ export type FilledViewDeps = {
|
|
|
27
27
|
t: (key: string, fallback: string) => string;
|
|
28
28
|
readOnly: boolean;
|
|
29
29
|
onTriggerFileInput: () => void;
|
|
30
|
-
|
|
30
|
+
onOpenUrlEditor: () => void;
|
|
31
31
|
onDelete: () => void;
|
|
32
32
|
mutate: (operation: () => void) => void;
|
|
33
33
|
customActions: Array<{
|
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
} from './icons.js'
|
|
9
9
|
import { applyInlineStyles } from './styles.js'
|
|
10
10
|
import { buildSettingsPanel } from './settings.js'
|
|
11
|
+
import { createPluginLayer } from '../shared/layer.js'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* @typedef {Object} FilledViewDeps
|
|
14
|
-
* @property {(key: string, fallback: string) => string} t
|
|
15
|
-
* @property {boolean} readOnly
|
|
14
|
+
* @typedef {Object} FilledViewDeps
|
|
15
|
+
* @property {(key: string, fallback: string) => string} t
|
|
16
|
+
* @property {boolean} readOnly
|
|
16
17
|
* @property {() => void} onTriggerFileInput
|
|
17
|
-
* @property {() => void}
|
|
18
|
+
* @property {() => void} onOpenUrlEditor
|
|
18
19
|
* @property {() => void} onDelete
|
|
19
20
|
* @property {(operation: () => void) => void} mutate
|
|
20
21
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<{url: string, alt?: string} | null> }>} customActions
|
|
@@ -29,11 +30,11 @@ import { buildSettingsPanel } from './settings.js'
|
|
|
29
30
|
* Listeners use the state's AbortSignal — automatically removed on the next
|
|
30
31
|
* render or on disposal.
|
|
31
32
|
*
|
|
32
|
-
* @param {HTMLElement} wrapper
|
|
33
|
-
* @param {import('./state.js').ImageState} state
|
|
34
|
-
* @param {FilledViewDeps} deps
|
|
35
|
-
* @returns {void}
|
|
36
|
-
*/
|
|
33
|
+
* @param {HTMLElement} wrapper
|
|
34
|
+
* @param {import('./state.js').ImageState} state
|
|
35
|
+
* @param {FilledViewDeps} deps
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
37
38
|
export function renderFilledView(wrapper, state, deps) {
|
|
38
39
|
state.resetTransient()
|
|
39
40
|
wrapper.innerHTML = ''
|
|
@@ -45,24 +46,24 @@ export function renderFilledView(wrapper, state, deps) {
|
|
|
45
46
|
container.className = CSS.imageContainer
|
|
46
47
|
|
|
47
48
|
const img = document.createElement('img')
|
|
48
|
-
img.className = CSS.image
|
|
49
|
-
setSafeUrlAttribute(img, 'src', state.data.file.url, 'media')
|
|
50
|
-
applyInlineStyles(state, img, container)
|
|
49
|
+
img.className = CSS.image
|
|
50
|
+
setSafeUrlAttribute(img, 'src', state.data.file.url, 'media')
|
|
51
|
+
applyInlineStyles(state, img, container)
|
|
51
52
|
container.appendChild(img)
|
|
52
53
|
|
|
53
|
-
// Caption
|
|
54
|
-
const caption = renderCaption(state, signal, deps)
|
|
55
|
-
img.alt = caption.textContent?.trim() || ''
|
|
56
|
-
if (!deps.readOnly) {
|
|
57
|
-
caption.addEventListener('input', () => {
|
|
58
|
-
img.alt = caption.textContent?.trim() || ''
|
|
59
|
-
}, { signal })
|
|
60
|
-
}
|
|
54
|
+
// Caption
|
|
55
|
+
const caption = renderCaption(state, signal, deps)
|
|
56
|
+
img.alt = caption.textContent?.trim() || ''
|
|
57
|
+
if (!deps.readOnly) {
|
|
58
|
+
caption.addEventListener('input', () => {
|
|
59
|
+
img.alt = caption.textContent?.trim() || ''
|
|
60
|
+
}, { signal })
|
|
61
|
+
}
|
|
61
62
|
container.appendChild(caption)
|
|
62
63
|
wrapper.appendChild(container)
|
|
63
64
|
|
|
64
65
|
// Action bar
|
|
65
|
-
if (!deps.readOnly) wrapper.appendChild(renderActions(wrapper, state, deps, signal))
|
|
66
|
+
if (!deps.readOnly) wrapper.appendChild(renderActions(wrapper, state, deps, signal))
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
@@ -74,7 +75,7 @@ export function renderFilledView(wrapper, state, deps) {
|
|
|
74
75
|
function renderCaption(state, signal, deps) {
|
|
75
76
|
const caption = document.createElement('div')
|
|
76
77
|
caption.className = CSS.caption
|
|
77
|
-
caption.contentEditable = deps.readOnly ? 'false' : 'true'
|
|
78
|
+
caption.contentEditable = deps.readOnly ? 'false' : 'true'
|
|
78
79
|
caption.dataset.placeholder = deps.t('caption', 'Caption')
|
|
79
80
|
|
|
80
81
|
if (state.data.caption) {
|
|
@@ -96,23 +97,23 @@ function renderCaption(state, signal, deps) {
|
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
if (!deps.readOnly) {
|
|
100
|
-
caption.addEventListener('input', syncEmpty, { signal })
|
|
101
|
-
caption.addEventListener('focus', () => caption.removeAttribute('data-empty'), { signal })
|
|
102
|
-
caption.addEventListener('blur', () => {
|
|
103
|
-
if (!caption.textContent?.trim()) {
|
|
104
|
-
caption.innerHTML = ''
|
|
105
|
-
caption.setAttribute('data-empty', 'true')
|
|
106
|
-
state.data.caption = ''
|
|
107
|
-
}
|
|
108
|
-
}, { signal })
|
|
109
|
-
caption.addEventListener('keydown', (e) => {
|
|
110
|
-
if (e.key === 'Backspace' && !caption.textContent?.trim()) {
|
|
111
|
-
e.preventDefault()
|
|
112
|
-
e.stopPropagation()
|
|
113
|
-
}
|
|
114
|
-
}, { signal })
|
|
115
|
-
}
|
|
100
|
+
if (!deps.readOnly) {
|
|
101
|
+
caption.addEventListener('input', syncEmpty, { signal })
|
|
102
|
+
caption.addEventListener('focus', () => caption.removeAttribute('data-empty'), { signal })
|
|
103
|
+
caption.addEventListener('blur', () => {
|
|
104
|
+
if (!caption.textContent?.trim()) {
|
|
105
|
+
caption.innerHTML = ''
|
|
106
|
+
caption.setAttribute('data-empty', 'true')
|
|
107
|
+
state.data.caption = ''
|
|
108
|
+
}
|
|
109
|
+
}, { signal })
|
|
110
|
+
caption.addEventListener('keydown', (e) => {
|
|
111
|
+
if (e.key === 'Backspace' && !caption.textContent?.trim()) {
|
|
112
|
+
e.preventDefault()
|
|
113
|
+
e.stopPropagation()
|
|
114
|
+
}
|
|
115
|
+
}, { signal })
|
|
116
|
+
}
|
|
116
117
|
|
|
117
118
|
return caption
|
|
118
119
|
}
|
|
@@ -135,27 +136,33 @@ function renderActions(wrapper, state, deps, signal) {
|
|
|
135
136
|
dropdown.className = CSS.dropdown
|
|
136
137
|
|
|
137
138
|
const settingsBtn = document.createElement('button')
|
|
138
|
-
settingsBtn.type = 'button'
|
|
139
|
-
settingsBtn.className = CSS.actionBtn
|
|
140
|
-
settingsBtn.innerHTML = `${ICON_SETTINGS} ${deps.t('settings', 'Settings')}`
|
|
141
|
-
settingsBtn.setAttribute('aria-haspopup', 'true')
|
|
142
|
-
settingsBtn.setAttribute('aria-expanded', 'false')
|
|
143
|
-
|
|
144
|
-
const panel = buildSettingsPanel(wrapper, state, settingsDeps)
|
|
145
|
-
panel.setAttribute('role', 'group')
|
|
146
|
-
panel.setAttribute('aria-label', deps.t('settings', 'Settings'))
|
|
139
|
+
settingsBtn.type = 'button'
|
|
140
|
+
settingsBtn.className = CSS.actionBtn
|
|
141
|
+
settingsBtn.innerHTML = `${ICON_SETTINGS} ${deps.t('settings', 'Settings')}`
|
|
142
|
+
settingsBtn.setAttribute('aria-haspopup', 'true')
|
|
143
|
+
settingsBtn.setAttribute('aria-expanded', 'false')
|
|
144
|
+
|
|
145
|
+
const panel = buildSettingsPanel(wrapper, state, settingsDeps)
|
|
146
|
+
panel.setAttribute('role', 'group')
|
|
147
|
+
panel.setAttribute('aria-label', deps.t('settings', 'Settings'))
|
|
148
|
+
const settingsLayer = createPluginLayer(wrapper, signal)
|
|
149
|
+
const closeSettings = () => {
|
|
150
|
+
dropdown.classList.remove(CSS.dropdownOpen)
|
|
151
|
+
settingsBtn.setAttribute('aria-expanded', 'false')
|
|
152
|
+
settingsLayer.close()
|
|
153
|
+
}
|
|
147
154
|
|
|
148
155
|
settingsBtn.addEventListener('click', (e) => {
|
|
149
156
|
e.stopPropagation()
|
|
150
157
|
const isOpen = dropdown.classList.contains(CSS.dropdownOpen)
|
|
151
|
-
if (isOpen) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
158
|
+
if (isOpen) {
|
|
159
|
+
closeSettings()
|
|
160
|
+
} else {
|
|
161
|
+
settingsLayer.open()
|
|
155
162
|
panel.style.top = ''
|
|
156
163
|
panel.style.bottom = ''
|
|
157
|
-
dropdown.classList.add(CSS.dropdownOpen)
|
|
158
|
-
settingsBtn.setAttribute('aria-expanded', 'true')
|
|
164
|
+
dropdown.classList.add(CSS.dropdownOpen)
|
|
165
|
+
settingsBtn.setAttribute('aria-expanded', 'true')
|
|
159
166
|
|
|
160
167
|
const btnRect = settingsBtn.getBoundingClientRect()
|
|
161
168
|
const panelRect = panel.getBoundingClientRect()
|
|
@@ -173,19 +180,17 @@ function renderActions(wrapper, state, deps, signal) {
|
|
|
173
180
|
dropdown.append(settingsBtn, panel)
|
|
174
181
|
|
|
175
182
|
document.addEventListener('click', (e) => {
|
|
176
|
-
if (!dropdown.contains(/** @type {Node} */ (e.target))) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
settingsBtn.focus()
|
|
188
|
-
}, { signal })
|
|
183
|
+
if (!dropdown.contains(/** @type {Node} */ (e.target))) {
|
|
184
|
+
closeSettings()
|
|
185
|
+
}
|
|
186
|
+
}, { signal })
|
|
187
|
+
|
|
188
|
+
dropdown.addEventListener('keydown', (event) => {
|
|
189
|
+
if (event.key !== 'Escape' || !dropdown.classList.contains(CSS.dropdownOpen)) return
|
|
190
|
+
event.preventDefault()
|
|
191
|
+
closeSettings()
|
|
192
|
+
settingsBtn.focus()
|
|
193
|
+
}, { signal })
|
|
189
194
|
|
|
190
195
|
const sep1 = makeSep()
|
|
191
196
|
|
|
@@ -209,10 +214,10 @@ function renderActions(wrapper, state, deps, signal) {
|
|
|
209
214
|
|
|
210
215
|
// Delete (icon only)
|
|
211
216
|
const deleteBtn = document.createElement('button')
|
|
212
|
-
deleteBtn.type = 'button'
|
|
213
|
-
deleteBtn.className = `${CSS.actionBtn} ${CSS.actionBtnDanger}`
|
|
214
|
-
deleteBtn.innerHTML = ICON_TRASH
|
|
215
|
-
deleteBtn.setAttribute('aria-label', deps.t('delete', 'Delete'))
|
|
217
|
+
deleteBtn.type = 'button'
|
|
218
|
+
deleteBtn.className = `${CSS.actionBtn} ${CSS.actionBtnDanger}`
|
|
219
|
+
deleteBtn.innerHTML = ICON_TRASH
|
|
220
|
+
deleteBtn.setAttribute('aria-label', deps.t('delete', 'Delete'))
|
|
216
221
|
deleteBtn.addEventListener('mousedown', (e) => e.preventDefault(), { signal })
|
|
217
222
|
deleteBtn.addEventListener('click', (e) => {
|
|
218
223
|
e.stopPropagation()
|
|
@@ -264,7 +269,7 @@ function showReplaceView(actions, mainView, deps, signal) {
|
|
|
264
269
|
|
|
265
270
|
view.appendChild(makeActionBtn(
|
|
266
271
|
`${ICON_URL} URL`,
|
|
267
|
-
() => { deps.
|
|
272
|
+
() => { deps.onOpenUrlEditor(); restore() },
|
|
268
273
|
signal,
|
|
269
274
|
))
|
|
270
275
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
2
1
|
import { sanitizeUrl, setSafeUrlAttribute } from '../../shared/sanitize/sanitizeUrl.js'
|
|
3
2
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
4
3
|
import { validateLinkPreviewData } from '../../shared/blockDataValidators.js'
|
|
5
4
|
import { normalizeTextValue } from '../../shared/textFormat.js'
|
|
6
5
|
|
|
7
|
-
const editorStyles =
|
|
6
|
+
const editorStyles = new URL('./link-preview.css', import.meta.url).href
|
|
8
7
|
|
|
9
8
|
// Tabler: link (toolbox)
|
|
10
9
|
const ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 15l6-6"/><path d="M11 6l.463-.536a5 5 0 0 1 7.071 7.072L18 13"/><path d="M13 18l-.397.534a5.068 5.068 0 0 1-7.127 0 4.972 4.972 0 0 1 0-7.071L6 11"/></svg>'
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { sanitizeHtml } from '../../core/sanitize.js'
|
|
2
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
3
2
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
4
3
|
import { mapTextFields } from './mapTextFields.js'
|
|
5
4
|
import { validateListData } from '../../shared/blockDataValidators.js'
|
|
6
5
|
import { normalizeTextValue } from '../../shared/textFormat.js'
|
|
7
6
|
|
|
8
|
-
const editorStyles =
|
|
7
|
+
const editorStyles = new URL('./list.css', import.meta.url).href
|
|
9
8
|
|
|
10
9
|
// Tabler icon: list
|
|
11
10
|
const ICON_UL = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6h11"/><path d="M9 12h11"/><path d="M9 18h11"/><path d="M5 6v.01"/><path d="M5 12v.01"/><path d="M5 18v.01"/></svg>'
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { sanitizeHtml } from '../../core/sanitize.js'
|
|
2
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
3
2
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
4
3
|
import { mapTextFields } from './mapTextFields.js'
|
|
5
4
|
import { validateParagraphData } from '../../shared/blockDataValidators.js'
|
|
6
5
|
import { normalizeTextAlign, normalizeTextValue } from '../../shared/textFormat.js'
|
|
7
6
|
|
|
8
|
-
const editorStyles =
|
|
7
|
+
const editorStyles = new URL('./paragraph.css', import.meta.url).href
|
|
9
8
|
|
|
10
9
|
// Tabler icon: letter-t
|
|
11
10
|
const ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l12 0"/><path d="M12 4l0 16"/></svg>'
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { sanitizeHtml } from '../../core/sanitize.js'
|
|
2
2
|
import { CropperDialog, cropperStylesUrl } from '@shelamkoff/cropper'
|
|
3
3
|
import { resolveSocialIcon, SOCIAL_ICONS } from './socialResolver.js'
|
|
4
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
5
4
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
6
5
|
import { sanitizeUrl, setSafeUrlAttribute } from '../../shared/sanitize/sanitizeUrl.js'
|
|
7
6
|
import { validatePersonData } from '../../shared/blockDataValidators.js'
|
|
8
7
|
import { normalizeTextValue } from '../../shared/textFormat.js'
|
|
9
8
|
|
|
10
|
-
const editorStyles =
|
|
9
|
+
const editorStyles = new URL('./person.css', import.meta.url).href
|
|
11
10
|
const cropperStyles = cropperStylesUrl
|
|
12
11
|
|
|
13
12
|
// Tabler icon: user-circle
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { sanitizeHtml } from '../../core/sanitize.js'
|
|
2
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
3
2
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
4
3
|
import { uid } from '../../core/uid.js'
|
|
5
4
|
import {
|
|
@@ -11,7 +10,7 @@ import {
|
|
|
11
10
|
} from '../../shared/pollData.js'
|
|
12
11
|
import { setSafeUrlAttribute } from '../../shared/sanitize/sanitizeUrl.js'
|
|
13
12
|
|
|
14
|
-
const editorStyles =
|
|
13
|
+
const editorStyles = new URL('./poll.css', import.meta.url).href
|
|
15
14
|
|
|
16
15
|
// Tabler icon: chart-bar
|
|
17
16
|
const ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/><path d="M9 8m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/><path d="M15 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/><path d="M4 20h14"/></svg>'
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
// =============================================================================
|
|
4
4
|
|
|
5
5
|
import { sanitizeHtml } from '../../core/sanitize.js'
|
|
6
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
7
6
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
8
7
|
import { mapTextFields } from './mapTextFields.js'
|
|
9
8
|
import { validateQuoteData } from '../../shared/blockDataValidators.js'
|
|
10
9
|
import { normalizeTextValue } from '../../shared/textFormat.js'
|
|
11
10
|
|
|
12
|
-
const editorStyles =
|
|
11
|
+
const editorStyles = new URL('./quote.css', import.meta.url).href
|
|
13
12
|
|
|
14
13
|
// Tabler icon: blockquote
|
|
15
14
|
const ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M9 5a2 2 0 0 1 2 2v6c0 3.13 -1.65 5.193 -4.757 5.97a1 1 0 1 1 -.486 -1.94c2.227 -.557 3.243 -1.827 3.243 -4.03v-1h-3a2 2 0 0 1 -1.995 -1.85l-.005 -.15v-3a2 2 0 0 1 2 -2z"/><path d="M18 5a2 2 0 0 1 2 2v6c0 3.13 -1.65 5.193 -4.757 5.97a1 1 0 1 1 -.486 -1.94c2.227 -.557 3.243 -1.827 3.243 -4.03v-1h-3a2 2 0 0 1 -1.995 -1.85l-.005 -.15v-3a2 2 0 0 1 2 -2z"/></svg>'
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { resolvePath } from '../../shared/resolvePath.js'
|
|
2
1
|
import { BlockPluginAbstract } from '../BlockPluginAbstract.js'
|
|
3
2
|
import { validateRawData } from '../../shared/blockDataValidators.js'
|
|
4
3
|
import { normalizeTextValue } from '../../shared/textFormat.js'
|
|
5
4
|
import { sanitizeRawHtml } from '../../shared/sanitize/sanitizeRawHtml.js'
|
|
6
5
|
|
|
7
|
-
const editorStyles =
|
|
6
|
+
const editorStyles = new URL('./raw.css', import.meta.url).href
|
|
8
7
|
|
|
9
8
|
const ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 16v-8l2 5l2-5v8"/><path d="M1 16v-8"/><path d="M5 8v8"/><path d="M1 12h4"/><path d="M7 8h4"/><path d="M9 8v8"/><path d="M20 8v8h3"/></svg>'
|
|
10
9
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* @property {string} afterText Localized text after the upload link.
|
|
21
21
|
* @property {() => void} onUploadClick Opens the file input.
|
|
22
22
|
* @property {(dataTransfer: DataTransfer) => void} onDrop Handles dropped files.
|
|
23
|
+
* @property {Array<{ label: string, prefix?: string, onSelect: () => void }>} [inlineActions]
|
|
23
24
|
* @property {Array<{ icon?: string, label: string, onSelect: () => void }>} [actions]
|
|
24
25
|
* @property {boolean} [readOnly] Render a non-interactive empty state.
|
|
25
26
|
* @property {string} [emptyText] Text shown for an empty read-only block.
|
|
@@ -90,6 +91,11 @@ export type DropzoneConfig = {
|
|
|
90
91
|
* Handles dropped files.
|
|
91
92
|
*/
|
|
92
93
|
onDrop: (dataTransfer: DataTransfer) => void;
|
|
94
|
+
inlineActions?: Array<{
|
|
95
|
+
label: string;
|
|
96
|
+
prefix?: string;
|
|
97
|
+
onSelect: () => void;
|
|
98
|
+
}>;
|
|
93
99
|
actions?: Array<{
|
|
94
100
|
icon?: string;
|
|
95
101
|
label: string;
|
|
@@ -5,38 +5,39 @@
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {Object} DropzoneCssMap
|
|
8
|
-
* @property {string} select Wrapper class.
|
|
9
|
-
* @property {string} selectIcon Icon container class.
|
|
10
|
-
* @property {string} selectText Text container class.
|
|
11
|
-
* @property {string} selectLink Upload link class.
|
|
12
|
-
* @property {string} dropzoneActive Class added during dragover.
|
|
13
|
-
* @property {string} filled Class removed from the wrapper during rendering.
|
|
14
|
-
* @property {string} [selectActions] Optional application-source list class.
|
|
15
|
-
* @property {string} [selectAction] Optional application-source button class.
|
|
8
|
+
* @property {string} select Wrapper class.
|
|
9
|
+
* @property {string} selectIcon Icon container class.
|
|
10
|
+
* @property {string} selectText Text container class.
|
|
11
|
+
* @property {string} selectLink Upload link class.
|
|
12
|
+
* @property {string} dropzoneActive Class added during dragover.
|
|
13
|
+
* @property {string} filled Class removed from the wrapper during rendering.
|
|
14
|
+
* @property {string} [selectActions] Optional application-source list class.
|
|
15
|
+
* @property {string} [selectAction] Optional application-source button class.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* @typedef {Object} DropzoneConfig
|
|
20
|
-
* @property {string} iconHtml SVG icon markup.
|
|
21
|
-
* @property {string} uploadText Localized upload-link text.
|
|
22
|
-
* @property {string} afterText Localized text after the upload link.
|
|
23
|
-
* @property {() => void} onUploadClick Opens the file input.
|
|
24
|
-
* @property {(dataTransfer: DataTransfer) => void} onDrop Handles dropped files.
|
|
25
|
-
* @property {Array<{
|
|
26
|
-
* @property {
|
|
27
|
-
* @property {
|
|
20
|
+
* @property {string} iconHtml SVG icon markup.
|
|
21
|
+
* @property {string} uploadText Localized upload-link text.
|
|
22
|
+
* @property {string} afterText Localized text after the upload link.
|
|
23
|
+
* @property {() => void} onUploadClick Opens the file input.
|
|
24
|
+
* @property {(dataTransfer: DataTransfer) => void} onDrop Handles dropped files.
|
|
25
|
+
* @property {Array<{ label: string, prefix?: string, onSelect: () => void }>} [inlineActions]
|
|
26
|
+
* @property {Array<{ icon?: string, label: string, onSelect: () => void }>} [actions]
|
|
27
|
+
* @property {boolean} [readOnly] Render a non-interactive empty state.
|
|
28
|
+
* @property {string} [emptyText] Text shown for an empty read-only block.
|
|
28
29
|
*/
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Render a dropzone (empty-state) view into a wrapper element.
|
|
32
33
|
* Clears the wrapper, removes the `filled` class, and wires drag-and-drop.
|
|
33
34
|
*
|
|
34
|
-
* @param {HTMLElement} wrapper Block wrapper element.
|
|
35
|
-
* @param {AbortSignal} signal Enables automatic listener cleanup.
|
|
36
|
-
* @param {DropzoneCssMap} css — plugin-specific CSS class names
|
|
37
|
-
* @param {DropzoneConfig} config — icon, text, and callbacks
|
|
38
|
-
* @returns {void}
|
|
39
|
-
*/
|
|
35
|
+
* @param {HTMLElement} wrapper Block wrapper element.
|
|
36
|
+
* @param {AbortSignal} signal Enables automatic listener cleanup.
|
|
37
|
+
* @param {DropzoneCssMap} css — plugin-specific CSS class names
|
|
38
|
+
* @param {DropzoneConfig} config — icon, text, and callbacks
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
40
41
|
export function renderDropzone(wrapper, signal, css, config) {
|
|
41
42
|
wrapper.innerHTML = ''
|
|
42
43
|
wrapper.classList.remove(css.filled)
|
|
@@ -48,17 +49,17 @@ export function renderDropzone(wrapper, signal, css, config) {
|
|
|
48
49
|
icon.className = css.selectIcon
|
|
49
50
|
icon.innerHTML = config.iconHtml
|
|
50
51
|
|
|
51
|
-
const text = document.createElement('div')
|
|
52
|
-
text.className = css.selectText
|
|
53
|
-
|
|
54
|
-
if (config.readOnly) {
|
|
55
|
-
text.textContent = config.emptyText || config.afterText
|
|
56
|
-
select.append(icon, text)
|
|
57
|
-
wrapper.appendChild(select)
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const uploadLink = document.createElement('button')
|
|
52
|
+
const text = document.createElement('div')
|
|
53
|
+
text.className = css.selectText
|
|
54
|
+
|
|
55
|
+
if (config.readOnly) {
|
|
56
|
+
text.textContent = config.emptyText || config.afterText
|
|
57
|
+
select.append(icon, text)
|
|
58
|
+
wrapper.appendChild(select)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const uploadLink = document.createElement('button')
|
|
62
63
|
uploadLink.type = 'button'
|
|
63
64
|
uploadLink.className = css.selectLink
|
|
64
65
|
uploadLink.textContent = config.uploadText
|
|
@@ -67,9 +68,19 @@ export function renderDropzone(wrapper, signal, css, config) {
|
|
|
67
68
|
config.onUploadClick()
|
|
68
69
|
}, { signal })
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
text.append(uploadLink, document.createTextNode(' ' + config.afterText))
|
|
72
|
+
for (const action of config.inlineActions || []) {
|
|
73
|
+
if (action.prefix) text.append(document.createTextNode(' ' + action.prefix + ' '))
|
|
74
|
+
const button = document.createElement('button')
|
|
75
|
+
button.type = 'button'
|
|
76
|
+
button.className = css.selectLink
|
|
77
|
+
button.textContent = action.label
|
|
78
|
+
button.addEventListener('click', (event) => {
|
|
79
|
+
event.stopPropagation()
|
|
80
|
+
action.onSelect()
|
|
81
|
+
}, { signal })
|
|
82
|
+
text.appendChild(button)
|
|
83
|
+
}
|
|
73
84
|
select.append(icon, text)
|
|
74
85
|
|
|
75
86
|
if (css.selectActions && css.selectAction && config.actions?.length) {
|