@shelamkoff/rector 1.0.1 → 1.0.3
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 -24
- 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/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 -920
- 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 -858
- 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/gallery/README.md +92 -92
- package/dist/plugins/gallery/README.ru.md +17 -17
- package/dist/plugins/gallery/gallery.css +44 -19
- package/dist/plugins/gallery/index.js +375 -354
- 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/settings.js +15 -10
- 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 +84 -72
- package/dist/plugins/image/image.css +7 -2
- package/dist/plugins/image/index.js +365 -345
- 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/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/renderer/renderers/carousel/index.js +24 -23
- package/dist/renderer/renderers/carousel/styles.css +8 -2
- package/dist/renderer/renderers/gallery/README.md +41 -41
- package/dist/renderer/renderers/gallery/README.ru.md +41 -41
- package/dist/renderer/renderers/gallery/index.js +100 -85
- package/dist/renderer/renderers/gallery/styles.css +417 -401
- package/dist/shared/galleryMasonry.d.ts +34 -0
- package/dist/shared/galleryMasonry.js +152 -0
- package/package.json +29 -28
|
@@ -6,6 +6,14 @@ declare const _default: {
|
|
|
6
6
|
'plugin.gallery.dropzone': string;
|
|
7
7
|
'plugin.gallery.dropzoneUpload': string;
|
|
8
8
|
'plugin.gallery.dropzoneText': string;
|
|
9
|
+
'plugin.gallery.dropzoneUrlPrefix': string;
|
|
10
|
+
'plugin.gallery.dropzoneUrl': string;
|
|
11
|
+
'plugin.gallery.urlEditorTitle': string;
|
|
12
|
+
'plugin.gallery.urlEditorLabel': string;
|
|
13
|
+
'plugin.gallery.urlEditorPlaceholder': string;
|
|
14
|
+
'plugin.gallery.sourceSubmit': string;
|
|
15
|
+
'plugin.gallery.sourceCancel': string;
|
|
16
|
+
'plugin.gallery.invalidUrl': string;
|
|
9
17
|
'plugin.gallery.addMore': string;
|
|
10
18
|
'plugin.gallery.delete': string;
|
|
11
19
|
'plugin.gallery.deleteAll': string;
|
|
@@ -6,6 +6,14 @@ export default {
|
|
|
6
6
|
'plugin.gallery.dropzone': 'or drag and drop files here',
|
|
7
7
|
'plugin.gallery.dropzoneUpload': 'Upload',
|
|
8
8
|
'plugin.gallery.dropzoneText': 'images from your device or drag and drop them here',
|
|
9
|
+
'plugin.gallery.dropzoneUrlPrefix': 'or',
|
|
10
|
+
'plugin.gallery.dropzoneUrl': 'insert by URL',
|
|
11
|
+
'plugin.gallery.urlEditorTitle': 'Insert image by URL',
|
|
12
|
+
'plugin.gallery.urlEditorLabel': 'Image URL',
|
|
13
|
+
'plugin.gallery.urlEditorPlaceholder': 'https://example.com/image.jpg',
|
|
14
|
+
'plugin.gallery.sourceSubmit': 'Insert',
|
|
15
|
+
'plugin.gallery.sourceCancel': 'Cancel',
|
|
16
|
+
'plugin.gallery.invalidUrl': 'Enter a valid image URL.',
|
|
9
17
|
'plugin.gallery.addMore': 'Add more',
|
|
10
18
|
'plugin.gallery.delete': 'Delete',
|
|
11
19
|
'plugin.gallery.deleteAll': 'Delete all',
|
|
@@ -30,6 +38,6 @@ export default {
|
|
|
30
38
|
'plugin.gallery.layoutAuto': 'Automatic layout',
|
|
31
39
|
'plugin.gallery.layoutMasonry': 'Masonry layout',
|
|
32
40
|
'plugin.gallery.layoutTriptych': 'Triptych layout',
|
|
33
|
-
'plugin.gallery.layoutTemplate': 'Layout template',
|
|
34
|
-
'plugin.gallery.emptyReadonly': 'No images',
|
|
35
|
-
}
|
|
41
|
+
'plugin.gallery.layoutTemplate': 'Layout template',
|
|
42
|
+
'plugin.gallery.emptyReadonly': 'No images',
|
|
43
|
+
}
|
|
@@ -6,6 +6,14 @@ declare const _default: {
|
|
|
6
6
|
'plugin.gallery.dropzone': string;
|
|
7
7
|
'plugin.gallery.dropzoneUpload': string;
|
|
8
8
|
'plugin.gallery.dropzoneText': string;
|
|
9
|
+
'plugin.gallery.dropzoneUrlPrefix': string;
|
|
10
|
+
'plugin.gallery.dropzoneUrl': string;
|
|
11
|
+
'plugin.gallery.urlEditorTitle': string;
|
|
12
|
+
'plugin.gallery.urlEditorLabel': string;
|
|
13
|
+
'plugin.gallery.urlEditorPlaceholder': string;
|
|
14
|
+
'plugin.gallery.sourceSubmit': string;
|
|
15
|
+
'plugin.gallery.sourceCancel': string;
|
|
16
|
+
'plugin.gallery.invalidUrl': string;
|
|
9
17
|
'plugin.gallery.addMore': string;
|
|
10
18
|
'plugin.gallery.delete': string;
|
|
11
19
|
'plugin.gallery.deleteAll': string;
|
|
@@ -6,6 +6,14 @@ export default {
|
|
|
6
6
|
'plugin.gallery.dropzone': 'или перетащите файлы сюда',
|
|
7
7
|
'plugin.gallery.dropzoneUpload': 'Загрузите',
|
|
8
8
|
'plugin.gallery.dropzoneText': 'изображения с устройства или перетащите их сюда',
|
|
9
|
+
'plugin.gallery.dropzoneUrlPrefix': 'или',
|
|
10
|
+
'plugin.gallery.dropzoneUrl': 'вставьте по URL',
|
|
11
|
+
'plugin.gallery.urlEditorTitle': 'Вставка изображения по URL',
|
|
12
|
+
'plugin.gallery.urlEditorLabel': 'URL изображения',
|
|
13
|
+
'plugin.gallery.urlEditorPlaceholder': 'https://example.com/image.jpg',
|
|
14
|
+
'plugin.gallery.sourceSubmit': 'Вставить',
|
|
15
|
+
'plugin.gallery.sourceCancel': 'Отмена',
|
|
16
|
+
'plugin.gallery.invalidUrl': 'Введите корректный URL изображения.',
|
|
9
17
|
'plugin.gallery.addMore': 'Добавить ещё',
|
|
10
18
|
'plugin.gallery.delete': 'Удалить',
|
|
11
19
|
'plugin.gallery.deleteAll': 'Удалить всё',
|
|
@@ -30,6 +38,6 @@ export default {
|
|
|
30
38
|
'plugin.gallery.layoutAuto': 'Автоматический макет',
|
|
31
39
|
'plugin.gallery.layoutMasonry': 'Кладочный макет',
|
|
32
40
|
'plugin.gallery.layoutTriptych': 'Триптих',
|
|
33
|
-
'plugin.gallery.layoutTemplate': 'Шаблон макета',
|
|
34
|
-
'plugin.gallery.emptyReadonly': 'Нет изображений',
|
|
35
|
-
}
|
|
41
|
+
'plugin.gallery.layoutTemplate': 'Шаблон макета',
|
|
42
|
+
'plugin.gallery.emptyReadonly': 'Нет изображений',
|
|
43
|
+
}
|
|
@@ -153,20 +153,25 @@ function buildStylesGroup(wrapper, state, deps, signal) {
|
|
|
153
153
|
const group = createGroup(deps.t('styles', 'Styles'))
|
|
154
154
|
const styles = state.data.styles
|
|
155
155
|
|
|
156
|
-
const makeStyleInput = (/** @type {string} */ key, /** @type {string} */ value) => {
|
|
157
|
-
const input = document.createElement('input')
|
|
158
|
-
input.type = 'text'
|
|
159
|
-
input.className = CSS.styleInput
|
|
160
|
-
input.value = value || ''
|
|
156
|
+
const makeStyleInput = (/** @type {string} */ key, /** @type {string} */ value) => {
|
|
157
|
+
const input = document.createElement('input')
|
|
158
|
+
input.type = 'text'
|
|
159
|
+
input.className = CSS.styleInput
|
|
160
|
+
input.value = value || ''
|
|
161
161
|
input.addEventListener('input', () => {
|
|
162
162
|
deps.mutate(() => {
|
|
163
163
|
if (input.value) styles[key] = input.value
|
|
164
164
|
else delete styles[key]
|
|
165
|
-
applyGalleryStyles(wrapper, state)
|
|
166
|
-
})
|
|
167
|
-
}, { signal })
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
applyGalleryStyles(wrapper, state)
|
|
166
|
+
})
|
|
167
|
+
}, { signal })
|
|
168
|
+
if (key === 'gap') {
|
|
169
|
+
input.addEventListener('change', () => {
|
|
170
|
+
if (state.data.layout === 'masonry') deps.reRender()
|
|
171
|
+
}, { signal })
|
|
172
|
+
}
|
|
173
|
+
return input
|
|
174
|
+
}
|
|
170
175
|
|
|
171
176
|
const makeRow = (/** @type {string} */ label, /** @type {HTMLElement} */ input) => {
|
|
172
177
|
const row = document.createElement('div')
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @property {(key: string, fallback: string) => string} t
|
|
4
4
|
* @property {boolean} readOnly
|
|
5
5
|
* @property {() => void} onUploadClick
|
|
6
|
+
* @property {() => void} onOpenUrlEditor
|
|
6
7
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null> }>} customActions
|
|
7
8
|
* @property {(handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null>) => Promise<void>} runCustomAction
|
|
8
9
|
* @property {(files: File[]) => void} onFilesDropped
|
|
@@ -21,6 +22,7 @@ export type EmptyViewDeps = {
|
|
|
21
22
|
t: (key: string, fallback: string) => string;
|
|
22
23
|
readOnly: boolean;
|
|
23
24
|
onUploadClick: () => void;
|
|
25
|
+
onOpenUrlEditor: () => void;
|
|
24
26
|
customActions: Array<{
|
|
25
27
|
icon?: string;
|
|
26
28
|
label: string;
|
|
@@ -1,13 +1,14 @@
|
|
|
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 {() => void} onUploadClick
|
|
11
|
+
* @property {() => void} onOpenUrlEditor
|
|
11
12
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null> }>} customActions
|
|
12
13
|
* @property {(handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null>) => Promise<void>} runCustomAction
|
|
13
14
|
* @property {(files: File[]) => void} onFilesDropped
|
|
@@ -17,11 +18,11 @@ import { ICON_SELECT } from './icons.js'
|
|
|
17
18
|
* Render the empty-state dropzone for the Gallery plugin.
|
|
18
19
|
* Replaces wrapper contents and removes the `filled` class.
|
|
19
20
|
*
|
|
20
|
-
* @param {HTMLElement} wrapper
|
|
21
|
-
* @param {import('./state.js').GalleryState} state
|
|
22
|
-
* @param {EmptyViewDeps} deps
|
|
23
|
-
* @returns {void}
|
|
24
|
-
*/
|
|
21
|
+
* @param {HTMLElement} wrapper
|
|
22
|
+
* @param {import('./state.js').GalleryState} state
|
|
23
|
+
* @param {EmptyViewDeps} deps
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
25
26
|
export function renderEmptyView(wrapper, state, deps) {
|
|
26
27
|
state.resetTransient()
|
|
27
28
|
|
|
@@ -38,18 +39,23 @@ export function renderEmptyView(wrapper, state, deps) {
|
|
|
38
39
|
filled: CSS.filled,
|
|
39
40
|
}, {
|
|
40
41
|
iconHtml: ICON_SELECT,
|
|
41
|
-
uploadText: deps.t('dropzoneUpload', 'Upload'),
|
|
42
|
-
afterText: deps.t('dropzoneText', 'images from your device or drag and drop them here'),
|
|
43
|
-
readOnly: deps.readOnly,
|
|
44
|
-
emptyText: deps.t('emptyReadonly', 'No images'),
|
|
42
|
+
uploadText: deps.t('dropzoneUpload', 'Upload'),
|
|
43
|
+
afterText: deps.t('dropzoneText', 'images from your device or drag and drop them here'),
|
|
44
|
+
readOnly: deps.readOnly,
|
|
45
|
+
emptyText: deps.t('emptyReadonly', 'No images'),
|
|
45
46
|
onUploadClick: deps.onUploadClick,
|
|
47
|
+
inlineActions: [{
|
|
48
|
+
prefix: deps.t('dropzoneUrlPrefix', 'or'),
|
|
49
|
+
label: deps.t('dropzoneUrl', 'insert by URL'),
|
|
50
|
+
onSelect: deps.onOpenUrlEditor,
|
|
51
|
+
}],
|
|
46
52
|
actions: deps.customActions.map(action => ({
|
|
47
53
|
icon: action.icon,
|
|
48
54
|
label: action.label,
|
|
49
55
|
onSelect: () => { void deps.runCustomAction(action.handler) },
|
|
50
56
|
})),
|
|
51
57
|
onDrop: (dt) => {
|
|
52
|
-
const files = [...(dt.files || [])].filter(isSupportedImageFile)
|
|
58
|
+
const files = [...(dt.files || [])].filter(isSupportedImageFile)
|
|
53
59
|
if (files.length > 0) deps.onFilesDropped(files)
|
|
54
60
|
},
|
|
55
61
|
})
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @property {(operation: () => void) => void} mutate
|
|
10
10
|
* @property {(files: File[]) => void} onFilesDropped
|
|
11
11
|
* @property {() => void} onTriggerFileInput
|
|
12
|
-
* @property {() => void}
|
|
12
|
+
* @property {() => void} onOpenUrlEditor
|
|
13
13
|
* @property {() => void} onDeleteAll
|
|
14
14
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null> }>} customActions
|
|
15
15
|
* @property {(handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null>) => Promise<void>} runCustomAction
|
|
@@ -34,7 +34,7 @@ export type FilledViewDeps = {
|
|
|
34
34
|
mutate: (operation: () => void) => void;
|
|
35
35
|
onFilesDropped: (files: File[]) => void;
|
|
36
36
|
onTriggerFileInput: () => void;
|
|
37
|
-
|
|
37
|
+
onOpenUrlEditor: () => void;
|
|
38
38
|
onDeleteAll: () => void;
|
|
39
39
|
customActions: Array<{
|
|
40
40
|
icon?: string;
|
|
@@ -10,14 +10,16 @@ import {
|
|
|
10
10
|
} from './layout.js'
|
|
11
11
|
import { applyGalleryStyles } from './styles.js'
|
|
12
12
|
import { buildSettingsPanel } from './settings.js'
|
|
13
|
-
import {
|
|
14
|
-
attachExternalDrop, createEmptySlot, createFilledSlot, createOverflowItem,
|
|
15
|
-
} from './slot.js'
|
|
13
|
+
import {
|
|
14
|
+
attachExternalDrop, createEmptySlot, createFilledSlot, createOverflowItem,
|
|
15
|
+
} from './slot.js'
|
|
16
|
+
import { createPluginLayer } from '../shared/layer.js'
|
|
17
|
+
import { mountGalleryMasonry } from '../../shared/galleryMasonry.js'
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
|
-
* @typedef {Object} FilledViewDeps
|
|
19
|
-
* @property {(key: string, fallback: string) => string} t
|
|
20
|
-
* @property {boolean} readOnly
|
|
20
|
+
* @typedef {Object} FilledViewDeps
|
|
21
|
+
* @property {(key: string, fallback: string) => string} t
|
|
22
|
+
* @property {boolean} readOnly
|
|
21
23
|
* @property {() => void} syncCaptions
|
|
22
24
|
* @property {() => import('./state.js').GalleryState | undefined} getState
|
|
23
25
|
* @property {() => void} reRender
|
|
@@ -25,7 +27,7 @@ import {
|
|
|
25
27
|
* @property {(operation: () => void) => void} mutate
|
|
26
28
|
* @property {(files: File[]) => void} onFilesDropped
|
|
27
29
|
* @property {() => void} onTriggerFileInput
|
|
28
|
-
* @property {() => void}
|
|
30
|
+
* @property {() => void} onOpenUrlEditor
|
|
29
31
|
* @property {() => void} onDeleteAll
|
|
30
32
|
* @property {Array<{ icon?: string, label: string, handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null> }>} customActions
|
|
31
33
|
* @property {(handler: (context: { signal: AbortSignal }) => Promise<Array<{url: string, alt?: string}> | null>) => Promise<void>} runCustomAction
|
|
@@ -35,11 +37,11 @@ import {
|
|
|
35
37
|
* Render the filled-state Gallery view: template grid (or masonry) +
|
|
36
38
|
* optional overflow row + action bar.
|
|
37
39
|
*
|
|
38
|
-
* @param {HTMLElement} wrapper
|
|
39
|
-
* @param {import('./state.js').GalleryState} state
|
|
40
|
-
* @param {FilledViewDeps} deps
|
|
41
|
-
* @returns {void}
|
|
42
|
-
*/
|
|
40
|
+
* @param {HTMLElement} wrapper
|
|
41
|
+
* @param {import('./state.js').GalleryState} state
|
|
42
|
+
* @param {FilledViewDeps} deps
|
|
43
|
+
* @returns {void}
|
|
44
|
+
*/
|
|
43
45
|
export function renderFilledView(wrapper, state, deps) {
|
|
44
46
|
state.resetTransient()
|
|
45
47
|
wrapper.innerHTML = ''
|
|
@@ -54,7 +56,7 @@ export function renderFilledView(wrapper, state, deps) {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
applyGalleryStyles(wrapper, state)
|
|
57
|
-
if (!deps.readOnly) wrapper.appendChild(renderActions(wrapper, state, deps, signal))
|
|
59
|
+
if (!deps.readOnly) wrapper.appendChild(renderActions(wrapper, state, deps, signal))
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
@@ -65,21 +67,27 @@ export function renderFilledView(wrapper, state, deps) {
|
|
|
65
67
|
* @param {FilledViewDeps} deps
|
|
66
68
|
* @param {AbortSignal} signal
|
|
67
69
|
*/
|
|
68
|
-
function renderMasonry(wrapper, state, deps, signal) {
|
|
69
|
-
const grid = document.createElement('div')
|
|
70
|
-
grid.className = `${CSS.grid} eg--masonry`
|
|
71
|
-
|
|
72
|
-
const slotDeps = makeSlotDeps(deps)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
function renderMasonry(wrapper, state, deps, signal) {
|
|
71
|
+
const grid = document.createElement('div')
|
|
72
|
+
grid.className = `${CSS.grid} eg--masonry`
|
|
73
|
+
|
|
74
|
+
const slotDeps = makeSlotDeps(deps)
|
|
75
|
+
/** @type {HTMLElement[]} */
|
|
76
|
+
const slots = []
|
|
77
|
+
|
|
78
|
+
state.data.images.forEach((img, i) => {
|
|
79
|
+
const slot = createFilledSlot(img, i, signal, slotDeps)
|
|
80
|
+
slot.dataset.index = String(i)
|
|
81
|
+
const image = /** @type {HTMLImageElement | null} */ (slot.querySelector(`.${CSS.slotImg}`))
|
|
82
|
+
if (image) image.loading = 'eager'
|
|
83
|
+
slots.push(slot)
|
|
84
|
+
grid.appendChild(slot)
|
|
85
|
+
})
|
|
86
|
+
|
|
80
87
|
if (!deps.readOnly) attachExternalDrop(grid, signal, deps.onFilesDropped)
|
|
81
|
-
wrapper.appendChild(grid)
|
|
82
|
-
}
|
|
88
|
+
wrapper.appendChild(grid)
|
|
89
|
+
mountGalleryMasonry(grid, slots, { signal })
|
|
90
|
+
}
|
|
83
91
|
|
|
84
92
|
/**
|
|
85
93
|
* Slot-based: fixed template + optional overflow row.
|
|
@@ -125,20 +133,20 @@ function renderSlotBased(wrapper, state, deps, signal) {
|
|
|
125
133
|
const slot = createFilledSlot(img, idx, signal, slotDeps)
|
|
126
134
|
slot.dataset.slot = String(idx)
|
|
127
135
|
|
|
128
|
-
const imgEl = /** @type {HTMLImageElement | null} */ (slot.querySelector(`.${CSS.slotImg}`))
|
|
129
|
-
if (imgEl) {
|
|
130
|
-
let ready = false
|
|
131
|
-
const onReady = () => {
|
|
132
|
-
if (ready) return
|
|
133
|
-
ready = true
|
|
134
|
-
orientations[idx] = classifyOrientation(imgEl.naturalWidth, imgEl.naturalHeight)
|
|
135
|
-
loadedCount++
|
|
136
|
-
if (loadedCount >= slotCount) onAllLoaded()
|
|
137
|
-
}
|
|
138
|
-
imgEl.addEventListener('load', onReady, { once: true, signal })
|
|
139
|
-
imgEl.addEventListener('error', onReady, { once: true, signal })
|
|
140
|
-
if (imgEl.complete) queueMicrotask(onReady)
|
|
141
|
-
}
|
|
136
|
+
const imgEl = /** @type {HTMLImageElement | null} */ (slot.querySelector(`.${CSS.slotImg}`))
|
|
137
|
+
if (imgEl) {
|
|
138
|
+
let ready = false
|
|
139
|
+
const onReady = () => {
|
|
140
|
+
if (ready) return
|
|
141
|
+
ready = true
|
|
142
|
+
orientations[idx] = classifyOrientation(imgEl.naturalWidth, imgEl.naturalHeight)
|
|
143
|
+
loadedCount++
|
|
144
|
+
if (loadedCount >= slotCount) onAllLoaded()
|
|
145
|
+
}
|
|
146
|
+
imgEl.addEventListener('load', onReady, { once: true, signal })
|
|
147
|
+
imgEl.addEventListener('error', onReady, { once: true, signal })
|
|
148
|
+
if (imgEl.complete) queueMicrotask(onReady)
|
|
149
|
+
}
|
|
142
150
|
grid.appendChild(slot)
|
|
143
151
|
}
|
|
144
152
|
} else {
|
|
@@ -155,7 +163,7 @@ function renderSlotBased(wrapper, state, deps, signal) {
|
|
|
155
163
|
}
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
if (!deps.readOnly) attachExternalDrop(grid, signal, deps.onFilesDropped)
|
|
166
|
+
if (!deps.readOnly) attachExternalDrop(grid, signal, deps.onFilesDropped)
|
|
159
167
|
wrapper.appendChild(grid)
|
|
160
168
|
|
|
161
169
|
if (overflowImages.length > 0) {
|
|
@@ -167,7 +175,7 @@ function renderSlotBased(wrapper, state, deps, signal) {
|
|
|
167
175
|
overflow.appendChild(createOverflowItem(img, globalIdx, signal, slotDeps))
|
|
168
176
|
})
|
|
169
177
|
|
|
170
|
-
if (!deps.readOnly) attachExternalDrop(overflow, signal, deps.onFilesDropped)
|
|
178
|
+
if (!deps.readOnly) attachExternalDrop(overflow, signal, deps.onFilesDropped)
|
|
171
179
|
wrapper.appendChild(overflow)
|
|
172
180
|
}
|
|
173
181
|
}
|
|
@@ -180,9 +188,9 @@ function renderSlotBased(wrapper, state, deps, signal) {
|
|
|
180
188
|
* @returns {import('./slot.js').SlotDeps}
|
|
181
189
|
*/
|
|
182
190
|
function makeSlotDeps(deps) {
|
|
183
|
-
return {
|
|
184
|
-
t: deps.t,
|
|
185
|
-
readOnly: deps.readOnly,
|
|
191
|
+
return {
|
|
192
|
+
t: deps.t,
|
|
193
|
+
readOnly: deps.readOnly,
|
|
186
194
|
syncCaptions: deps.syncCaptions,
|
|
187
195
|
getState: deps.getState,
|
|
188
196
|
onRemoveImage: (index) => {
|
|
@@ -229,37 +237,41 @@ function renderActions(wrapper, state, deps, signal) {
|
|
|
229
237
|
dropdown.className = CSS.dropdown
|
|
230
238
|
|
|
231
239
|
const settingsBtn = document.createElement('button')
|
|
232
|
-
settingsBtn.type = 'button'
|
|
233
|
-
settingsBtn.className = CSS.actionBtn
|
|
234
|
-
settingsBtn.innerHTML = `${ICON_SETTINGS} ${deps.t('settings', 'Settings')}`
|
|
235
|
-
settingsBtn.setAttribute('aria-haspopup', 'true')
|
|
236
|
-
settingsBtn.setAttribute('aria-expanded', 'false')
|
|
237
|
-
|
|
238
|
-
const panel = buildSettingsPanel(wrapper, state, settingsDeps)
|
|
239
|
-
panel.setAttribute('role', 'group')
|
|
240
|
-
panel.setAttribute('aria-label', deps.t('settings', 'Settings'))
|
|
240
|
+
settingsBtn.type = 'button'
|
|
241
|
+
settingsBtn.className = CSS.actionBtn
|
|
242
|
+
settingsBtn.innerHTML = `${ICON_SETTINGS} ${deps.t('settings', 'Settings')}`
|
|
243
|
+
settingsBtn.setAttribute('aria-haspopup', 'true')
|
|
244
|
+
settingsBtn.setAttribute('aria-expanded', 'false')
|
|
245
|
+
|
|
246
|
+
const panel = buildSettingsPanel(wrapper, state, settingsDeps)
|
|
247
|
+
panel.setAttribute('role', 'group')
|
|
248
|
+
panel.setAttribute('aria-label', deps.t('settings', 'Settings'))
|
|
249
|
+
const settingsLayer = createPluginLayer(wrapper, signal)
|
|
250
|
+
const setSettingsOpen = (open) => {
|
|
251
|
+
dropdown.classList.toggle(CSS.dropdownOpen, open)
|
|
252
|
+
settingsBtn.setAttribute('aria-expanded', String(open))
|
|
253
|
+
if (open) settingsLayer.open()
|
|
254
|
+
else settingsLayer.close()
|
|
255
|
+
}
|
|
241
256
|
|
|
242
257
|
settingsBtn.addEventListener('click', (e) => {
|
|
243
258
|
e.stopPropagation()
|
|
244
|
-
|
|
245
|
-
settingsBtn.setAttribute('aria-expanded', String(open))
|
|
259
|
+
setSettingsOpen(!dropdown.classList.contains(CSS.dropdownOpen))
|
|
246
260
|
}, { signal })
|
|
247
261
|
|
|
248
262
|
dropdown.append(settingsBtn, panel)
|
|
249
263
|
|
|
250
264
|
document.addEventListener('click', (e) => {
|
|
251
265
|
if (!dropdown.contains(/** @type {Node} */ (e.target))) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
settingsBtn.focus()
|
|
262
|
-
}, { signal })
|
|
266
|
+
setSettingsOpen(false)
|
|
267
|
+
}
|
|
268
|
+
}, { signal })
|
|
269
|
+
dropdown.addEventListener('keydown', (event) => {
|
|
270
|
+
if (event.key !== 'Escape' || !dropdown.classList.contains(CSS.dropdownOpen)) return
|
|
271
|
+
event.preventDefault()
|
|
272
|
+
setSettingsOpen(false)
|
|
273
|
+
settingsBtn.focus()
|
|
274
|
+
}, { signal })
|
|
263
275
|
|
|
264
276
|
// Main view container (for drill-down hide/show)
|
|
265
277
|
const mainView = document.createElement('div')
|
|
@@ -283,9 +295,9 @@ function renderActions(wrapper, state, deps, signal) {
|
|
|
283
295
|
// Delete all
|
|
284
296
|
const deleteAllBtn = document.createElement('button')
|
|
285
297
|
deleteAllBtn.type = 'button'
|
|
286
|
-
deleteAllBtn.className = `${CSS.actionBtn} ${CSS.actionBtnDanger}`
|
|
287
|
-
deleteAllBtn.innerHTML = ICON_TRASH
|
|
288
|
-
deleteAllBtn.setAttribute('aria-label', deps.t('deleteAll', 'Delete all'))
|
|
298
|
+
deleteAllBtn.className = `${CSS.actionBtn} ${CSS.actionBtnDanger}`
|
|
299
|
+
deleteAllBtn.innerHTML = ICON_TRASH
|
|
300
|
+
deleteAllBtn.setAttribute('aria-label', deps.t('deleteAll', 'Delete all'))
|
|
289
301
|
deleteAllBtn.addEventListener('mousedown', (e) => e.preventDefault(), { signal })
|
|
290
302
|
deleteAllBtn.addEventListener('click', (e) => {
|
|
291
303
|
e.stopPropagation()
|
|
@@ -335,7 +347,7 @@ function showAddView(actions, mainView, deps, signal) {
|
|
|
335
347
|
|
|
336
348
|
view.appendChild(makeActionBtn(
|
|
337
349
|
`${ICON_URL} URL`,
|
|
338
|
-
() => { deps.
|
|
350
|
+
() => { deps.onOpenUrlEditor(); restore() },
|
|
339
351
|
signal,
|
|
340
352
|
))
|
|
341
353
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.oe-image {
|
|
2
|
+
position: relative;
|
|
2
3
|
border-radius: 0.5rem;
|
|
3
4
|
overflow-anchor: none;
|
|
4
5
|
text-align: center;
|
|
@@ -239,7 +240,7 @@
|
|
|
239
240
|
border-radius: 8px;
|
|
240
241
|
box-shadow: var(--oe-toolbar-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));
|
|
241
242
|
padding: 12px;
|
|
242
|
-
z-index:
|
|
243
|
+
z-index: var(--oe-plugin-panel-z-index, 1100);
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
.oe-image__dropdown--open .oe-image__dropdown-panel {
|
|
@@ -377,7 +378,7 @@
|
|
|
377
378
|
border: 1px solid var(--oe-border-hover, #2a2a2a);
|
|
378
379
|
border-radius: 6px;
|
|
379
380
|
box-shadow: var(--oe-toolbar-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));
|
|
380
|
-
z-index:
|
|
381
|
+
z-index: var(--oe-plugin-popover-z-index, 1120);
|
|
381
382
|
padding: 3px;
|
|
382
383
|
max-height: 10rem;
|
|
383
384
|
overflow-y: auto;
|
|
@@ -516,6 +517,10 @@
|
|
|
516
517
|
color: var(--oe-text-1, #1a1a1a);
|
|
517
518
|
}
|
|
518
519
|
|
|
520
|
+
.oe-theme-light .oe-image__action-btn:hover {
|
|
521
|
+
color: var(--oe-text-on-accent, #fff);
|
|
522
|
+
}
|
|
523
|
+
|
|
519
524
|
.oe-theme-light .oe-image__dropdown-panel {
|
|
520
525
|
background: var(--oe-card, #fff);
|
|
521
526
|
border-color: var(--oe-border-hover, #d4d4d4);
|