@dickpy/dsh-imagegen 1.0.3 → 1.0.4
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 +7 -7
- package/docs/images/image-generation-studio.png +0 -0
- package/docs/images/plugin-settings.png +0 -0
- package/lib/client.js +307 -111
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/client/ImageGenPanel.tsx +110 -19
- package/src/client/locales.ts +16 -0
- package/src/client/panel.module.css +157 -12
- package/src/protocol.ts +1 -1
package/lib/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { spawn } from "node:child_process";
|
|
|
14
14
|
/** Settings namespace this plugin owns (host settings seam + bridge). */
|
|
15
15
|
const IMAGEGEN_SETTINGS_NAMESPACE = "dsh-imagegen";
|
|
16
16
|
/** Published package version shared by the host updater and the client UI. */
|
|
17
|
-
const PLUGIN_VERSION = "1.0.
|
|
17
|
+
const PLUGIN_VERSION = "1.0.4";
|
|
18
18
|
/** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
|
|
19
19
|
const SETTINGS_API = {
|
|
20
20
|
describe: "/api/dsh-imagegen/settings/describe",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dickpy/dsh-imagegen",
|
|
3
3
|
"description": "AI 生图 (image generation) plugin for the dsh web GUI: text-to-image and image-to-image through a configurable OpenAI-compatible endpoint (gpt-image-2 / gpt-image-1 / dall-e-3), with a settings card for api_url / api_key and a sidebar entry opening a split-pane generation studio.",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -42,6 +42,13 @@ const DETAILS = ['', 'standard', 'high'] as const
|
|
|
42
42
|
|
|
43
43
|
const PROMPT_MAX = 2000
|
|
44
44
|
const REF_IMAGE_MAX_BYTES = 10 * 1024 * 1024
|
|
45
|
+
const PREVIEW_SCALE_MIN = 0.5
|
|
46
|
+
const PREVIEW_SCALE_MAX = 3
|
|
47
|
+
const PREVIEW_SCALE_STEP = 0.25
|
|
48
|
+
|
|
49
|
+
function clampPreviewScale(scale: number): number {
|
|
50
|
+
return Math.min(PREVIEW_SCALE_MAX, Math.max(PREVIEW_SCALE_MIN, scale))
|
|
51
|
+
}
|
|
45
52
|
|
|
46
53
|
/** Read the current config from the settings scope snapshot. */
|
|
47
54
|
function useConfig(scope: ImageGenScope): ImageGenConfig | undefined {
|
|
@@ -133,6 +140,8 @@ export function ImageGenPanel(props: {
|
|
|
133
140
|
const [history, setHistory] = useState<HistoryEntry[]>([])
|
|
134
141
|
const [viewingHistoryId, setViewingHistoryId] = useState<string | null>(null)
|
|
135
142
|
const [preview, setPreview] = useState<{ images: GeneratedImage[]; index: number } | null>(null)
|
|
143
|
+
const [previewScale, setPreviewScale] = useState(1)
|
|
144
|
+
const [promptCopied, setPromptCopied] = useState(false)
|
|
136
145
|
const [update, setUpdate] = useState<UpdateInfo | null>(null)
|
|
137
146
|
const [updating, setUpdating] = useState(false)
|
|
138
147
|
const [updateMessage, setUpdateMessage] = useState<string | null>(null)
|
|
@@ -242,10 +251,20 @@ export function ImageGenPanel(props: {
|
|
|
242
251
|
/** Open the full-screen image preview at a given index. */
|
|
243
252
|
const openPreview = (previewImages: GeneratedImage[], index: number): void => {
|
|
244
253
|
setPreview({ images: previewImages, index })
|
|
254
|
+
setPreviewScale(1)
|
|
255
|
+
setPromptCopied(false)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const closePreview = (): void => {
|
|
259
|
+
setPreview(null)
|
|
260
|
+
setPreviewScale(1)
|
|
261
|
+
setPromptCopied(false)
|
|
245
262
|
}
|
|
246
263
|
|
|
247
264
|
/** Step the preview by ±1, wrapping around. */
|
|
248
265
|
const stepPreview = (delta: number): void => {
|
|
266
|
+
setPreviewScale(1)
|
|
267
|
+
setPromptCopied(false)
|
|
249
268
|
setPreview(current => {
|
|
250
269
|
if (current === null) return null
|
|
251
270
|
const total = current.images.length
|
|
@@ -257,9 +276,12 @@ export function ImageGenPanel(props: {
|
|
|
257
276
|
useEffect(() => {
|
|
258
277
|
if (preview === null) return
|
|
259
278
|
const onKey = (event: KeyboardEvent): void => {
|
|
260
|
-
if (event.key === 'Escape')
|
|
279
|
+
if (event.key === 'Escape') closePreview()
|
|
261
280
|
else if (event.key === 'ArrowLeft') stepPreview(-1)
|
|
262
281
|
else if (event.key === 'ArrowRight') stepPreview(1)
|
|
282
|
+
else if (event.key === '+' || event.key === '=') setPreviewScale(current => clampPreviewScale(current + PREVIEW_SCALE_STEP))
|
|
283
|
+
else if (event.key === '-') setPreviewScale(current => clampPreviewScale(current - PREVIEW_SCALE_STEP))
|
|
284
|
+
else if (event.key === '0') setPreviewScale(1)
|
|
263
285
|
}
|
|
264
286
|
window.addEventListener('keydown', onKey)
|
|
265
287
|
return () => window.removeEventListener('keydown', onKey)
|
|
@@ -322,6 +344,40 @@ export function ImageGenPanel(props: {
|
|
|
322
344
|
const viewingEntry = viewingHistoryId === null ? null : history.find(entry => entry.id === viewingHistoryId) ?? null
|
|
323
345
|
const previewImage = preview === null ? null : preview.images[preview.index] ?? null
|
|
324
346
|
|
|
347
|
+
const copyPreviewPrompt = async (text: string): Promise<void> => {
|
|
348
|
+
try {
|
|
349
|
+
if (navigator.clipboard?.writeText !== undefined) {
|
|
350
|
+
await navigator.clipboard.writeText(text)
|
|
351
|
+
} else {
|
|
352
|
+
const textarea = document.createElement('textarea')
|
|
353
|
+
textarea.value = text
|
|
354
|
+
textarea.style.position = 'fixed'
|
|
355
|
+
textarea.style.opacity = '0'
|
|
356
|
+
document.body.appendChild(textarea)
|
|
357
|
+
textarea.select()
|
|
358
|
+
const copied = document.execCommand('copy')
|
|
359
|
+
textarea.remove()
|
|
360
|
+
if (!copied) throw new Error('copy failed')
|
|
361
|
+
}
|
|
362
|
+
setPromptCopied(true)
|
|
363
|
+
window.setTimeout(() => { setPromptCopied(false) }, 1800)
|
|
364
|
+
} catch {
|
|
365
|
+
setPromptCopied(false)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const addPreviewToEdit = (): void => {
|
|
370
|
+
if (previewImage === null || preview === null) return
|
|
371
|
+
setMode('edit')
|
|
372
|
+
setRefImage({
|
|
373
|
+
dataUrl: srcOf(previewImage),
|
|
374
|
+
name: `dsh-image-${preview.index + 1}.${extensionOf(previewImage.mime)}`,
|
|
375
|
+
})
|
|
376
|
+
if (prompt.trim() === '' && previewImage.revisedPrompt !== undefined) setPrompt(previewImage.revisedPrompt)
|
|
377
|
+
setError(null)
|
|
378
|
+
closePreview()
|
|
379
|
+
}
|
|
380
|
+
|
|
325
381
|
return (
|
|
326
382
|
<div className={css.panel}>
|
|
327
383
|
<header className={css.panelHeader}>
|
|
@@ -569,7 +625,7 @@ export function ImageGenPanel(props: {
|
|
|
569
625
|
<span className={css.canvasHistoryTag}>{tt('history.viewing', { time: formatTime(viewingEntry.createdAt) })}</span>
|
|
570
626
|
) : null}
|
|
571
627
|
</div>
|
|
572
|
-
<div className={css.grid}>
|
|
628
|
+
<div className={css.grid} data-count={images.length}>
|
|
573
629
|
{images.map((image, index) => (
|
|
574
630
|
<figure
|
|
575
631
|
key={index}
|
|
@@ -677,9 +733,9 @@ export function ImageGenPanel(props: {
|
|
|
677
733
|
role="dialog"
|
|
678
734
|
aria-modal="true"
|
|
679
735
|
aria-label={tt('preview.title')}
|
|
680
|
-
onClick={
|
|
736
|
+
onClick={closePreview}
|
|
681
737
|
>
|
|
682
|
-
<button type="button" className={css.lightboxClose} aria-label={tt('preview.close')}
|
|
738
|
+
<button type="button" className={css.lightboxClose} aria-label={tt('preview.close')} title={tt('preview.close')} onClick={closePreview}>
|
|
683
739
|
<svg viewBox="0 0 16 16" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg>
|
|
684
740
|
</button>
|
|
685
741
|
{preview.images.length > 1 ? (
|
|
@@ -693,25 +749,60 @@ export function ImageGenPanel(props: {
|
|
|
693
749
|
</>
|
|
694
750
|
) : null}
|
|
695
751
|
<figure className={css.lightboxFigure} onClick={(event) => { event.stopPropagation() }}>
|
|
696
|
-
<
|
|
697
|
-
className={css.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
752
|
+
<div
|
|
753
|
+
className={css.lightboxStage}
|
|
754
|
+
onWheel={(event) => {
|
|
755
|
+
event.preventDefault()
|
|
756
|
+
setPreviewScale(current => clampPreviewScale(current + (event.deltaY < 0 ? PREVIEW_SCALE_STEP : -PREVIEW_SCALE_STEP)))
|
|
757
|
+
}}
|
|
758
|
+
>
|
|
759
|
+
<img
|
|
760
|
+
className={css.lightboxImage}
|
|
761
|
+
style={{ transform: `scale(${previewScale})` }}
|
|
762
|
+
src={srcOf(previewImage)}
|
|
763
|
+
alt={previewImage.revisedPrompt ?? tt('preview.title')}
|
|
764
|
+
/>
|
|
765
|
+
</div>
|
|
766
|
+
<div className={css.lightboxTools} role="group" aria-label={tt('preview.zoomControls')}>
|
|
767
|
+
<button type="button" className={css.lightboxTool} aria-label={tt('preview.zoomOut')} title={tt('preview.zoomOut')} onClick={() => { setPreviewScale(current => clampPreviewScale(current - PREVIEW_SCALE_STEP)) }}>
|
|
768
|
+
<svg viewBox="0 0 16 16" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="M4.8 7h4.4M13 13l-2.8-2.8"/></svg>
|
|
769
|
+
</button>
|
|
770
|
+
<button type="button" className={css.lightboxZoomLevel} aria-label={tt('preview.zoomReset')} title={tt('preview.zoomReset')} onClick={() => { setPreviewScale(1) }}>
|
|
771
|
+
{tt('preview.zoomLevel', { percent: Math.round(previewScale * 100) })}
|
|
772
|
+
</button>
|
|
773
|
+
<button type="button" className={css.lightboxTool} aria-label={tt('preview.zoomIn')} title={tt('preview.zoomIn')} onClick={() => { setPreviewScale(current => clampPreviewScale(current + PREVIEW_SCALE_STEP)) }}>
|
|
774
|
+
<svg viewBox="0 0 16 16" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="M7 4.8v4.4M4.8 7h4.4M13 13l-2.8-2.8"/></svg>
|
|
775
|
+
</button>
|
|
776
|
+
</div>
|
|
701
777
|
{previewImage.revisedPrompt !== undefined ? (
|
|
702
|
-
<
|
|
703
|
-
{
|
|
704
|
-
|
|
778
|
+
<div className={css.lightboxCaptionRow}>
|
|
779
|
+
<figcaption className={css.lightboxCaption} title={previewImage.revisedPrompt}>
|
|
780
|
+
{tt('revisedPrompt', { prompt: previewImage.revisedPrompt })}
|
|
781
|
+
</figcaption>
|
|
782
|
+
<button type="button" className={css.lightboxCopy} aria-label={tt(promptCopied ? 'preview.copied' : 'preview.copyPrompt')} title={tt(promptCopied ? 'preview.copied' : 'preview.copyPrompt')} onClick={() => { void copyPreviewPrompt(previewImage.revisedPrompt!) }}>
|
|
783
|
+
{promptCopied ? (
|
|
784
|
+
<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M3 8l3 3 7-7"/></svg>
|
|
785
|
+
) : (
|
|
786
|
+
<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="5" y="5" width="7" height="8" rx="1"/><path d="M3 10V3.8c0-.44.36-.8.8-.8H9"/></svg>
|
|
787
|
+
)}
|
|
788
|
+
<span>{tt(promptCopied ? 'preview.copied' : 'preview.copyPrompt')}</span>
|
|
789
|
+
</button>
|
|
790
|
+
</div>
|
|
705
791
|
) : null}
|
|
706
792
|
<div className={css.lightboxMeta}>
|
|
707
793
|
<span className={css.lightboxIndex}>{tt('preview.index', { index: preview.index + 1, total: preview.images.length })}</span>
|
|
708
|
-
<
|
|
709
|
-
className={css.
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
794
|
+
<span className={css.lightboxActions}>
|
|
795
|
+
<button type="button" className={css.lightboxEdit} onClick={addPreviewToEdit}>
|
|
796
|
+
{tt('preview.addToEdit')}
|
|
797
|
+
</button>
|
|
798
|
+
<a
|
|
799
|
+
className={css.lightboxDownload}
|
|
800
|
+
href={srcOf(previewImage)}
|
|
801
|
+
download={`dsh-image-${preview.index + 1}.${extensionOf(previewImage.mime)}`}
|
|
802
|
+
>
|
|
803
|
+
{tt('download')}
|
|
804
|
+
</a>
|
|
805
|
+
</span>
|
|
715
806
|
</div>
|
|
716
807
|
</figure>
|
|
717
808
|
</div>,
|
package/src/client/locales.ts
CHANGED
|
@@ -72,6 +72,14 @@ export const zh = {
|
|
|
72
72
|
'preview.prev': '上一张',
|
|
73
73
|
'preview.next': '下一张',
|
|
74
74
|
'preview.index': '{index} / {total}',
|
|
75
|
+
'preview.zoomControls': '图片缩放控制',
|
|
76
|
+
'preview.zoomIn': '放大',
|
|
77
|
+
'preview.zoomOut': '缩小',
|
|
78
|
+
'preview.zoomReset': '重置缩放',
|
|
79
|
+
'preview.zoomLevel': '{percent}%',
|
|
80
|
+
'preview.copyPrompt': '复制提示词',
|
|
81
|
+
'preview.copied': '已复制',
|
|
82
|
+
'preview.addToEdit': '添加到图生图',
|
|
75
83
|
// config banner
|
|
76
84
|
'config.missing': '尚未配置 API:请前往「设置 → 插件 → 可配置」为 AI 生图填写 api_url 与 api_key。',
|
|
77
85
|
'config.configured': '已连接 {url}',
|
|
@@ -178,6 +186,14 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
178
186
|
'preview.prev': 'Previous',
|
|
179
187
|
'preview.next': 'Next',
|
|
180
188
|
'preview.index': '{index} / {total}',
|
|
189
|
+
'preview.zoomControls': 'Image zoom controls',
|
|
190
|
+
'preview.zoomIn': 'Zoom in',
|
|
191
|
+
'preview.zoomOut': 'Zoom out',
|
|
192
|
+
'preview.zoomReset': 'Reset zoom',
|
|
193
|
+
'preview.zoomLevel': '{percent}%',
|
|
194
|
+
'preview.copyPrompt': 'Copy prompt',
|
|
195
|
+
'preview.copied': 'Copied',
|
|
196
|
+
'preview.addToEdit': 'Add to image to image',
|
|
181
197
|
'config.missing': 'API not configured: open "Settings → Plugins → Configurable" and fill in api_url and api_key for AI Image.',
|
|
182
198
|
'config.configured': 'Connected to {url}',
|
|
183
199
|
'config.disabled': 'The plugin is disabled — re-enable it in Settings.',
|
|
@@ -798,9 +798,29 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
798
798
|
|
|
799
799
|
.grid {
|
|
800
800
|
display: grid;
|
|
801
|
-
|
|
801
|
+
flex: 1;
|
|
802
|
+
min-height: 0;
|
|
802
803
|
gap: 14px;
|
|
803
|
-
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
.grid[data-count='1'] {
|
|
807
|
+
grid-template-columns: minmax(0, 1fr);
|
|
808
|
+
grid-template-rows: minmax(0, 1fr);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.grid[data-count='2'] {
|
|
812
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
813
|
+
grid-template-rows: minmax(0, 1fr);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.grid[data-count='3'] {
|
|
817
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
818
|
+
grid-template-rows: minmax(0, 1fr);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.grid[data-count='4'] {
|
|
822
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
823
|
+
grid-template-rows: repeat(2, minmax(0, 1fr));
|
|
804
824
|
}
|
|
805
825
|
|
|
806
826
|
.imageCard {
|
|
@@ -813,6 +833,7 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
813
833
|
overflow: hidden;
|
|
814
834
|
background: var(--dsw-alias-bg-layer-2);
|
|
815
835
|
cursor: zoom-in;
|
|
836
|
+
min-height: 0;
|
|
816
837
|
}
|
|
817
838
|
|
|
818
839
|
.imageCard:focus-visible {
|
|
@@ -822,9 +843,10 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
822
843
|
|
|
823
844
|
.image {
|
|
824
845
|
display: block;
|
|
846
|
+
flex: 1;
|
|
847
|
+
min-height: 0;
|
|
825
848
|
width: 100%;
|
|
826
|
-
|
|
827
|
-
object-fit: cover;
|
|
849
|
+
object-fit: contain;
|
|
828
850
|
background: var(--dsw-alias-bg-base);
|
|
829
851
|
}
|
|
830
852
|
|
|
@@ -971,11 +993,11 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
971
993
|
}
|
|
972
994
|
|
|
973
995
|
.lightboxNav[data-dir='prev'] {
|
|
974
|
-
left:
|
|
996
|
+
left: max(20px, calc(50% - 640px));
|
|
975
997
|
}
|
|
976
998
|
|
|
977
999
|
.lightboxNav[data-dir='next'] {
|
|
978
|
-
right:
|
|
1000
|
+
right: max(20px, calc(50% - 640px));
|
|
979
1001
|
}
|
|
980
1002
|
|
|
981
1003
|
.lightboxFigure {
|
|
@@ -984,19 +1006,84 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
984
1006
|
gap: 10px;
|
|
985
1007
|
margin: 0;
|
|
986
1008
|
max-width: min(1100px, calc(100vw - 160px));
|
|
987
|
-
|
|
1009
|
+
width: min(1100px, calc(100vw - 160px));
|
|
1010
|
+
height: min(820px, calc(100vh - 48px));
|
|
1011
|
+
min-height: 0;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.lightboxStage {
|
|
1015
|
+
flex: 1;
|
|
1016
|
+
min-height: 0;
|
|
1017
|
+
display: grid;
|
|
1018
|
+
place-items: center;
|
|
1019
|
+
overflow: auto;
|
|
1020
|
+
border-radius: 10px;
|
|
1021
|
+
background: rgba(255, 255, 255, 0.04);
|
|
988
1022
|
}
|
|
989
1023
|
|
|
990
1024
|
.lightboxImage {
|
|
991
|
-
max-width: 100
|
|
992
|
-
max-height: calc(
|
|
1025
|
+
max-width: calc(100% - 4px);
|
|
1026
|
+
max-height: calc(100% - 4px);
|
|
993
1027
|
object-fit: contain;
|
|
994
1028
|
border-radius: 10px;
|
|
995
1029
|
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
|
|
996
|
-
|
|
1030
|
+
transition: transform 120ms ease;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.lightboxTools {
|
|
1034
|
+
display: flex;
|
|
1035
|
+
align-items: center;
|
|
1036
|
+
justify-content: center;
|
|
1037
|
+
gap: 6px;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.lightboxTool,
|
|
1041
|
+
.lightboxZoomLevel,
|
|
1042
|
+
.lightboxCopy {
|
|
1043
|
+
display: inline-flex;
|
|
1044
|
+
align-items: center;
|
|
1045
|
+
justify-content: center;
|
|
1046
|
+
color: #fff;
|
|
1047
|
+
background: rgba(255, 255, 255, 0.14);
|
|
1048
|
+
border: 1px solid rgba(255, 255, 255, 0.28);
|
|
1049
|
+
cursor: pointer;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.lightboxTool,
|
|
1053
|
+
.lightboxZoomLevel {
|
|
1054
|
+
height: 32px;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
.lightboxTool {
|
|
1058
|
+
width: 32px;
|
|
1059
|
+
border-radius: 50%;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.lightboxZoomLevel {
|
|
1063
|
+
min-width: 58px;
|
|
1064
|
+
padding: 0 9px;
|
|
1065
|
+
border-radius: 999px;
|
|
1066
|
+
font: inherit;
|
|
1067
|
+
font-size: 12px;
|
|
1068
|
+
font-variant-numeric: tabular-nums;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
.lightboxTool:hover,
|
|
1072
|
+
.lightboxZoomLevel:hover,
|
|
1073
|
+
.lightboxCopy:hover {
|
|
1074
|
+
background: rgba(255, 255, 255, 0.26);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.lightboxCaptionRow {
|
|
1078
|
+
display: flex;
|
|
1079
|
+
align-items: flex-start;
|
|
1080
|
+
gap: 8px;
|
|
1081
|
+
min-width: 0;
|
|
997
1082
|
}
|
|
998
1083
|
|
|
999
1084
|
.lightboxCaption {
|
|
1085
|
+
flex: 1;
|
|
1086
|
+
min-width: 0;
|
|
1000
1087
|
font-size: 12px;
|
|
1001
1088
|
line-height: 1.6;
|
|
1002
1089
|
color: rgba(255, 255, 255, 0.9);
|
|
@@ -1006,6 +1093,17 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
1006
1093
|
overflow: hidden;
|
|
1007
1094
|
}
|
|
1008
1095
|
|
|
1096
|
+
.lightboxCopy {
|
|
1097
|
+
flex: none;
|
|
1098
|
+
gap: 5px;
|
|
1099
|
+
min-height: 28px;
|
|
1100
|
+
padding: 4px 9px;
|
|
1101
|
+
border-radius: 999px;
|
|
1102
|
+
font: inherit;
|
|
1103
|
+
font-size: 12px;
|
|
1104
|
+
white-space: nowrap;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1009
1107
|
.lightboxMeta {
|
|
1010
1108
|
display: flex;
|
|
1011
1109
|
align-items: center;
|
|
@@ -1019,8 +1117,16 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
1019
1117
|
font-variant-numeric: tabular-nums;
|
|
1020
1118
|
}
|
|
1021
1119
|
|
|
1022
|
-
.
|
|
1120
|
+
.lightboxActions {
|
|
1121
|
+
display: inline-flex;
|
|
1122
|
+
align-items: center;
|
|
1123
|
+
gap: 8px;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.lightboxDownload,
|
|
1127
|
+
.lightboxEdit {
|
|
1023
1128
|
padding: 4px 14px;
|
|
1129
|
+
font: inherit;
|
|
1024
1130
|
font-size: 12.5px;
|
|
1025
1131
|
font-weight: 500;
|
|
1026
1132
|
color: #fff;
|
|
@@ -1028,12 +1134,51 @@ html[data-dsh-imagegen-active]:not([data-dsh-taskboard-active]):not([data-dsh-ss
|
|
|
1028
1134
|
border: 1px solid rgba(255, 255, 255, 0.28);
|
|
1029
1135
|
border-radius: 999px;
|
|
1030
1136
|
text-decoration: none;
|
|
1137
|
+
cursor: pointer;
|
|
1031
1138
|
}
|
|
1032
1139
|
|
|
1033
|
-
.lightboxDownload:hover
|
|
1140
|
+
.lightboxDownload:hover,
|
|
1141
|
+
.lightboxEdit:hover {
|
|
1034
1142
|
background: rgba(255, 255, 255, 0.26);
|
|
1035
1143
|
}
|
|
1036
1144
|
|
|
1145
|
+
.lightboxEdit {
|
|
1146
|
+
color: #fff;
|
|
1147
|
+
background: rgba(255, 255, 255, 0.14);
|
|
1148
|
+
border: 1px solid rgba(255, 255, 255, 0.28);
|
|
1149
|
+
border-radius: 999px;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
@media (max-width: 720px) {
|
|
1153
|
+
.lightbox {
|
|
1154
|
+
padding: 16px;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.lightboxFigure {
|
|
1158
|
+
width: calc(100vw - 32px);
|
|
1159
|
+
max-width: none;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.lightboxNav[data-dir='prev'] {
|
|
1163
|
+
left: 20px;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.lightboxNav[data-dir='next'] {
|
|
1167
|
+
right: 20px;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.lightboxCaptionRow,
|
|
1171
|
+
.lightboxMeta {
|
|
1172
|
+
align-items: stretch;
|
|
1173
|
+
flex-direction: column;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.lightboxCopy,
|
|
1177
|
+
.lightboxActions {
|
|
1178
|
+
align-self: flex-end;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1037
1182
|
@keyframes dshImageGenSpin {
|
|
1038
1183
|
to { transform: rotate(360deg); }
|
|
1039
1184
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
export const IMAGEGEN_SETTINGS_NAMESPACE = 'dsh-imagegen'
|
|
9
9
|
|
|
10
10
|
/** Published package version shared by the host updater and the client UI. */
|
|
11
|
-
export const PLUGIN_VERSION = '1.0.
|
|
11
|
+
export const PLUGIN_VERSION = '1.0.4'
|
|
12
12
|
|
|
13
13
|
/** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
|
|
14
14
|
export const SETTINGS_API = {
|