@open-slide/core 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-DSqSio-T.js → build-_276DMmJ.js} +2 -2
- package/dist/cli/bin.js +5 -5
- package/dist/{config-KdiYeWtK.js → config-BAwKWNtW.js} +888 -229
- package/dist/{config-C7vMYzFD.d.ts → config-D9cZ1A0X.d.ts} +2 -1
- package/dist/{dev-B_GVbr11.js → dev-BoqeVXVq.js} +2 -2
- package/dist/en-CDKzoZvf.js +351 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +229 -39
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +166 -326
- package/dist/{preview-D_mxhj7w.js → preview-BLPxspc9.js} +2 -2
- package/dist/sync-j9_QPovT.js +3 -0
- package/dist/{types-DYgVpIGo.d.ts → types-JYG1cmwC.d.ts} +59 -5
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +2 -2
- package/package.json +9 -1
- package/skills/create-slide/SKILL.md +1 -1
- package/skills/create-theme/SKILL.md +60 -12
- package/skills/current-slide/SKILL.md +110 -0
- package/skills/slide-authoring/SKILL.md +59 -1
- package/src/app/app.tsx +11 -1
- package/src/app/components/asset-view.tsx +1 -13
- package/src/app/components/image-placeholder.tsx +123 -1
- package/src/app/components/inspector/image-crop-dialog.tsx +64 -20
- package/src/app/components/inspector/inspector-panel.tsx +163 -19
- package/src/app/components/inspector/inspector-provider.tsx +60 -7
- package/src/app/components/notes-drawer.tsx +117 -0
- package/src/app/components/player.tsx +11 -7
- package/src/app/components/present/overview-grid.tsx +2 -2
- package/src/app/components/sidebar/folder-item.tsx +16 -5
- package/src/app/components/sidebar/mobile-pill.tsx +34 -0
- package/src/app/components/sidebar/sidebar.tsx +10 -0
- package/src/app/components/themes/theme-detail.tsx +300 -0
- package/src/app/components/themes/themes-gallery.tsx +146 -0
- package/src/app/components/thumbnail-rail.tsx +136 -29
- package/src/app/components/ui/context-menu.tsx +237 -0
- package/src/app/lib/assets.ts +55 -2
- package/src/app/lib/inspector/use-notes.ts +134 -0
- package/src/app/lib/sdk.ts +1 -0
- package/src/app/lib/slides.ts +10 -1
- package/src/app/lib/themes.ts +22 -0
- package/src/app/lib/use-agent-socket.ts +18 -0
- package/src/app/routes/home-shell.tsx +173 -0
- package/src/app/routes/home.tsx +108 -204
- package/src/app/routes/slide.tsx +333 -68
- package/src/app/routes/themes.tsx +34 -0
- package/src/app/virtual.d.ts +20 -0
- package/src/locale/en.ts +61 -7
- package/src/locale/ja.ts +62 -7
- package/src/locale/types.ts +62 -5
- package/src/locale/zh-cn.ts +61 -7
- package/src/locale/zh-tw.ts +61 -7
- package/dist/sync-B4eLo2H6.js +0 -3
- /package/dist/{design-C13iz9_4.js → design-cpzS8aud.js} +0 -0
- /package/dist/{sync-3oqN1WyK.js → sync-BCJDRIqo.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Locale } from "./types-
|
|
1
|
+
import { Locale } from "./types-JYG1cmwC.js";
|
|
2
2
|
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
type OpenSlideBuildConfig = {
|
|
@@ -8,6 +8,7 @@ type OpenSlideBuildConfig = {
|
|
|
8
8
|
};
|
|
9
9
|
type OpenSlideConfig = {
|
|
10
10
|
slidesDir?: string;
|
|
11
|
+
themesDir?: string;
|
|
11
12
|
port?: number;
|
|
12
13
|
locale?: Locale;
|
|
13
14
|
build?: OpenSlideBuildConfig;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
//#region src/locale/en.ts
|
|
2
|
+
const en = {
|
|
3
|
+
id: "en",
|
|
4
|
+
common: {
|
|
5
|
+
cancel: "Cancel",
|
|
6
|
+
save: "Save",
|
|
7
|
+
saving: "Saving",
|
|
8
|
+
saved: "Saved",
|
|
9
|
+
discard: "Discard",
|
|
10
|
+
delete: "Delete",
|
|
11
|
+
rename: "Rename",
|
|
12
|
+
move: "Move",
|
|
13
|
+
close: "Close",
|
|
14
|
+
loading: "Loading",
|
|
15
|
+
loadFailed: "Load failed",
|
|
16
|
+
failedToLoadSlide: "Failed to load slide",
|
|
17
|
+
home: "Home",
|
|
18
|
+
backToHome: "Back to home",
|
|
19
|
+
preview: "Preview",
|
|
20
|
+
add: "Add",
|
|
21
|
+
done: "Done",
|
|
22
|
+
tryAgain: "Try again",
|
|
23
|
+
undo: "Undo",
|
|
24
|
+
redo: "Redo",
|
|
25
|
+
light: "Light",
|
|
26
|
+
dark: "Dark",
|
|
27
|
+
system: "System",
|
|
28
|
+
selected: "Selected"
|
|
29
|
+
},
|
|
30
|
+
notFound: {
|
|
31
|
+
eyebrow: "404 · not found",
|
|
32
|
+
title: "Page not found"
|
|
33
|
+
},
|
|
34
|
+
home: {
|
|
35
|
+
appTitle: "open-slide",
|
|
36
|
+
draft: "Draft",
|
|
37
|
+
themes: "Themes",
|
|
38
|
+
folders: "Folders",
|
|
39
|
+
newFolder: "New folder",
|
|
40
|
+
folderName: "Folder name",
|
|
41
|
+
changeIcon: "Change icon",
|
|
42
|
+
iconEmojiTab: "Emoji",
|
|
43
|
+
iconColorTab: "Color",
|
|
44
|
+
folderActions: "Folder actions",
|
|
45
|
+
searchPlaceholder: "Search slides",
|
|
46
|
+
clearSearch: "Clear search",
|
|
47
|
+
noMatches: "No matches",
|
|
48
|
+
nothingMatchesPrefix: "Nothing matches ",
|
|
49
|
+
nothingMatchesSuffix: " in this folder.",
|
|
50
|
+
noSlidesYet: "No slides yet",
|
|
51
|
+
createSlideHintPrefix: "Run ",
|
|
52
|
+
createSlideHintSuffix: " in your agent to scaffold one.",
|
|
53
|
+
folderEmptyTitle: "{name} is empty",
|
|
54
|
+
folderEmptyHint: "Drag a slide from Draft into this folder in the sidebar.",
|
|
55
|
+
slideActions: "Slide actions",
|
|
56
|
+
moveToFolder: "Move to folder…",
|
|
57
|
+
renameDialogEyebrow: "Rename",
|
|
58
|
+
renameDialogTitle: "Rename slide",
|
|
59
|
+
renameDialogDescription: "Give this slide a new display name.",
|
|
60
|
+
slideNamePlaceholder: "Slide name",
|
|
61
|
+
moveDialogEyebrow: "Move",
|
|
62
|
+
moveDialogTitle: "Move slide",
|
|
63
|
+
moveDialogDescriptionPrefix: "Choose a folder for ",
|
|
64
|
+
moveDialogDescriptionSuffix: ".",
|
|
65
|
+
deleteDialogEyebrow: "Destructive",
|
|
66
|
+
deleteDialogTitle: "Delete slide?",
|
|
67
|
+
deleteDialogDescriptionPrefix: "This permanently removes ",
|
|
68
|
+
deleteDialogDescriptionMid: " and its files from disk. ",
|
|
69
|
+
deleteDialogDescriptionSuffix: "This action cannot be undone.",
|
|
70
|
+
toastFolderCreated: "Created folder “{name}”",
|
|
71
|
+
toastFolderCreateFailed: "Failed to create folder",
|
|
72
|
+
toastSlideMoved: "Moved “{slide}” to {folder}",
|
|
73
|
+
toastSlideMoveFailed: "Failed to move slide",
|
|
74
|
+
toastFolderDeleted: "Deleted folder “{name}”",
|
|
75
|
+
toastFolderDeleteFailed: "Failed to delete folder",
|
|
76
|
+
pickIcon: "Pick icon"
|
|
77
|
+
},
|
|
78
|
+
slide: {
|
|
79
|
+
home: "Home",
|
|
80
|
+
backToHome: "Back to home",
|
|
81
|
+
agentConnected: "Agent connected",
|
|
82
|
+
agentConnectedTooltip: "The dev server is publishing your current slide and inspector selection to your agent. Ask \"this slide\" or \"this element\" in chat and it will resolve. Disappears in production builds.",
|
|
83
|
+
agentDisconnected: "Agent disconnected",
|
|
84
|
+
agentDisconnectedTooltip: "Lost connection to the dev server, so your agent can no longer see the current slide or inspector selection. Restart the dev server to restore the connection.",
|
|
85
|
+
download: "Download",
|
|
86
|
+
exportAsHtml: "Export as HTML",
|
|
87
|
+
exportAsPdf: "Export as PDF",
|
|
88
|
+
pdfExportFailed: "PDF export failed",
|
|
89
|
+
present: "Present",
|
|
90
|
+
slidesTab: "Slides",
|
|
91
|
+
assetsTab: "Assets",
|
|
92
|
+
renameSlide: "Rename slide",
|
|
93
|
+
loadingEyebrow: "Loading",
|
|
94
|
+
emptyEyebrow: "Empty",
|
|
95
|
+
nothingToShow: "Nothing to show.",
|
|
96
|
+
emptyHintPrefix: "",
|
|
97
|
+
emptyHintMust: " must ",
|
|
98
|
+
emptyHintSuffix: " a non-empty array of components."
|
|
99
|
+
},
|
|
100
|
+
presenter: {
|
|
101
|
+
eyebrow: "Presenter",
|
|
102
|
+
notLinked: "Not linked",
|
|
103
|
+
nowShowing: "Now showing",
|
|
104
|
+
upNext: "Up next",
|
|
105
|
+
lastSlide: "Last slide",
|
|
106
|
+
endOfDeck: "End of deck",
|
|
107
|
+
speakerNotes: "Speaker notes",
|
|
108
|
+
noNotesPrefix: "No speaker notes for this slide. Add ",
|
|
109
|
+
noNotesSuffix: " to your slide module to see notes here.",
|
|
110
|
+
blackScreen: "Black screen",
|
|
111
|
+
whiteScreen: "White screen",
|
|
112
|
+
prev: "Prev",
|
|
113
|
+
next: "Next",
|
|
114
|
+
black: "Black",
|
|
115
|
+
white: "White",
|
|
116
|
+
reset: "Reset",
|
|
117
|
+
resetTimer: "Reset timer",
|
|
118
|
+
currentTime: "Current time",
|
|
119
|
+
elapsed: "Elapsed",
|
|
120
|
+
jump: "Jump",
|
|
121
|
+
loadingSlide: "Loading {slideId}…"
|
|
122
|
+
},
|
|
123
|
+
present: {
|
|
124
|
+
prevSlideAria: "Previous slide (←)",
|
|
125
|
+
nextSlideAria: "Next slide (→)",
|
|
126
|
+
overviewAria: "Slide overview (O)",
|
|
127
|
+
blackoutAria: "Black screen (B)",
|
|
128
|
+
whiteoutAria: "White screen (W)",
|
|
129
|
+
laserAria: "Laser pointer (L)",
|
|
130
|
+
presenterAria: "Presenter view (P)",
|
|
131
|
+
helpAria: "Keyboard shortcuts (?)",
|
|
132
|
+
exitAria: "Exit (Esc)",
|
|
133
|
+
elapsedTime: "Elapsed time",
|
|
134
|
+
helpEyebrow: "Present mode",
|
|
135
|
+
helpTitle: "Keyboard shortcuts",
|
|
136
|
+
shortcutNext: "Next slide",
|
|
137
|
+
shortcutPrev: "Previous slide",
|
|
138
|
+
shortcutFirstLast: "First / last slide",
|
|
139
|
+
shortcutJump: "Jump to slide",
|
|
140
|
+
shortcutOverview: "Slide overview",
|
|
141
|
+
shortcutBlack: "Black screen",
|
|
142
|
+
shortcutWhite: "White screen",
|
|
143
|
+
shortcutLaser: "Laser pointer",
|
|
144
|
+
shortcutPresenter: "Open Presenter View",
|
|
145
|
+
shortcutToggleHelp: "Toggle this help",
|
|
146
|
+
shortcutCloseExit: "Close overlay / exit",
|
|
147
|
+
overviewDialogAria: "Slide overview",
|
|
148
|
+
overviewEyebrow: "Overview",
|
|
149
|
+
overviewGoToAria: "Go to slide {n}",
|
|
150
|
+
nowBadge: "Now"
|
|
151
|
+
},
|
|
152
|
+
inspector: {
|
|
153
|
+
inspect: "Inspect",
|
|
154
|
+
deselect: "Deselect",
|
|
155
|
+
agentWatching: "Agent is watching",
|
|
156
|
+
agentWatchingTooltip: "Your agent already sees the selected element via the dev server — just ask it in chat. Leave comments here only when you want to queue a few before asking.",
|
|
157
|
+
agentNotWatching: "Agent not watching",
|
|
158
|
+
agentNotWatchingTooltip: "Lost connection to the dev server, so your agent can no longer see the selected element. Restart the dev server to restore the connection.",
|
|
159
|
+
contentSection: "Content",
|
|
160
|
+
typographySection: "Typography",
|
|
161
|
+
colorSection: "Color",
|
|
162
|
+
textColor: "Text",
|
|
163
|
+
backgroundColor: "Background",
|
|
164
|
+
imageSection: "Image",
|
|
165
|
+
imagePlaceholderSection: "Image placeholder",
|
|
166
|
+
elementTextPlaceholder: "Element text",
|
|
167
|
+
sizeLabel: "Size",
|
|
168
|
+
weightLabel: "Weight",
|
|
169
|
+
weightLight: "Light · 300",
|
|
170
|
+
weightRegular: "Regular · 400",
|
|
171
|
+
weightMedium: "Medium · 500",
|
|
172
|
+
weightSemibold: "Semibold · 600",
|
|
173
|
+
weightBold: "Bold · 700",
|
|
174
|
+
weightExtrabold: "Extrabold · 800",
|
|
175
|
+
styleLabel: "Style",
|
|
176
|
+
boldAria: "Bold",
|
|
177
|
+
italicAria: "Italic",
|
|
178
|
+
lineHeightLabel: "Line height",
|
|
179
|
+
trackingLabel: "Tracking",
|
|
180
|
+
alignLabel: "Align",
|
|
181
|
+
clearAria: "Clear",
|
|
182
|
+
replace: "Replace…",
|
|
183
|
+
replaceImageDialogTitle: "Replace image",
|
|
184
|
+
replaceImageDescription: "Pick an asset from {path}.",
|
|
185
|
+
pickerLoading: "Loading…",
|
|
186
|
+
pickerEmpty: "No images in this slide's assets folder yet. Add some from the Assets tab.",
|
|
187
|
+
placeholderHintLabel: "Hint:",
|
|
188
|
+
crop: "Crop…",
|
|
189
|
+
cropDialogTitle: "Crop image",
|
|
190
|
+
cropDialogDescription: "Drag the frame to choose what stays visible.",
|
|
191
|
+
cropFitCover: "Fill",
|
|
192
|
+
cropFitContain: "Fit",
|
|
193
|
+
cropApply: "Apply",
|
|
194
|
+
cropResetAria: "Reset crop",
|
|
195
|
+
leaveComment: "Leave a comment",
|
|
196
|
+
commentPlaceholder: "Describe a change for the agent…",
|
|
197
|
+
commentShortcutHint: "⌘↵ to add",
|
|
198
|
+
addComment: "Add comment",
|
|
199
|
+
unsavedChanges: {
|
|
200
|
+
one: "{count} unsaved change",
|
|
201
|
+
other: "{count} unsaved changes"
|
|
202
|
+
},
|
|
203
|
+
commentsCount: {
|
|
204
|
+
one: "{count} comment",
|
|
205
|
+
other: "{count} comments"
|
|
206
|
+
},
|
|
207
|
+
commentLineLabel: "line {n}",
|
|
208
|
+
commentsEmpty: "No comments yet. Toggle Inspect and click a slide element.",
|
|
209
|
+
commentsApplyHintPrefix: "Run ",
|
|
210
|
+
commentsApplyHintSuffix: " in your agent to apply these.",
|
|
211
|
+
commentDeleteAria: "Delete",
|
|
212
|
+
saveFailed: "Couldn't save:"
|
|
213
|
+
},
|
|
214
|
+
stylePanel: {
|
|
215
|
+
designTokens: "Design tokens",
|
|
216
|
+
draftBadge: "draft",
|
|
217
|
+
unsavedTitle: "Unsaved",
|
|
218
|
+
closePanelAria: "Close design panel",
|
|
219
|
+
colorsSection: "Colors",
|
|
220
|
+
typographySection: "Typography",
|
|
221
|
+
shapeSection: "Shape",
|
|
222
|
+
backgroundLabel: "Background",
|
|
223
|
+
textLabel: "Text",
|
|
224
|
+
accentLabel: "Accent",
|
|
225
|
+
displayFontLabel: "Display",
|
|
226
|
+
bodyFontLabel: "Body",
|
|
227
|
+
heroLabel: "Hero",
|
|
228
|
+
bodyLabel: "Body",
|
|
229
|
+
radiusLabel: "Radius",
|
|
230
|
+
designToggle: "Design",
|
|
231
|
+
designToggleTitle: "Design tokens",
|
|
232
|
+
fontPresetCustom: "Custom…",
|
|
233
|
+
shuffleAria: "Shuffle design",
|
|
234
|
+
shuffleTitle: "Shuffle for inspiration"
|
|
235
|
+
},
|
|
236
|
+
asset: {
|
|
237
|
+
devOnlyMessage: "Asset management is only available in dev mode.",
|
|
238
|
+
sectionAria: "Slide assets",
|
|
239
|
+
eyebrow: "Assets",
|
|
240
|
+
fileCount: {
|
|
241
|
+
one: "{count} file",
|
|
242
|
+
other: "{count} files"
|
|
243
|
+
},
|
|
244
|
+
searchLogos: "Search logos",
|
|
245
|
+
upload: "Upload",
|
|
246
|
+
dropToUpload: "Drop to upload",
|
|
247
|
+
loading: "Loading…",
|
|
248
|
+
noAssetsYet: "No assets yet",
|
|
249
|
+
noAssetsHintPrefix: "Drop files anywhere here, or use ",
|
|
250
|
+
noAssetsHintSuffix: ".",
|
|
251
|
+
nameAlreadyExists: "A file with that name already exists.",
|
|
252
|
+
previewAria: "Preview {name}",
|
|
253
|
+
actionsAria: "Actions for {name}",
|
|
254
|
+
previewMenuItem: "Preview",
|
|
255
|
+
renameMenuItem: "Rename",
|
|
256
|
+
deleteMenuItem: "Delete",
|
|
257
|
+
conflictTitle: "File already exists",
|
|
258
|
+
conflictDescription: "{name} is already in this slide's assets folder.",
|
|
259
|
+
conflictReplace: "Replace",
|
|
260
|
+
conflictRenameCopy: "Rename copy",
|
|
261
|
+
deleteAssetTitle: "Delete asset",
|
|
262
|
+
deleteAssetDescription: "Delete {name}? Imports referencing this file in the slide will break.",
|
|
263
|
+
noPreview: "No preview available",
|
|
264
|
+
importHintComment: "import asset from ",
|
|
265
|
+
importHintSemi: ";",
|
|
266
|
+
logoSearchTitle: "Search logos",
|
|
267
|
+
logoSearchPoweredByPrefix: "Powered by ",
|
|
268
|
+
logoSearchPlaceholder: "Search by brand…",
|
|
269
|
+
logoSearchErrorTitle: "Couldn't reach svgl",
|
|
270
|
+
logoSearchErrorBody: "Check your connection and try again.",
|
|
271
|
+
logoSearchNoResults: "No logos for \"{query}\"",
|
|
272
|
+
logoSearchEmpty: "No logos available",
|
|
273
|
+
logoSearchEmptyHintPrefix: "Try a different brand name, or browse the full catalog at ",
|
|
274
|
+
logoSearchEmptyHintSuffix: ".",
|
|
275
|
+
logoVariantLight: "Light",
|
|
276
|
+
logoVariantDark: "Dark",
|
|
277
|
+
toastUploadFailed: "Upload failed ({status})",
|
|
278
|
+
toastReplaced: "Replaced {name}",
|
|
279
|
+
toastUploadedAs: "Uploaded as {name}",
|
|
280
|
+
toastUploaded: "Uploaded {name}",
|
|
281
|
+
toastRenameFailed: "Rename failed ({status})",
|
|
282
|
+
toastRenamed: "Renamed to {name}",
|
|
283
|
+
toastDeleteFailed: "Delete failed ({status})",
|
|
284
|
+
toastDeleted: "Deleted {name}",
|
|
285
|
+
toastDownloadFailed: "Failed to download logo",
|
|
286
|
+
toastSearchFailed: "Search failed"
|
|
287
|
+
},
|
|
288
|
+
thumbnailRail: {
|
|
289
|
+
pages: "Pages",
|
|
290
|
+
goToPageAria: "Go to page {n}",
|
|
291
|
+
duplicatePage: "Duplicate",
|
|
292
|
+
deletePage: "Delete",
|
|
293
|
+
pageActionsAria: "Page {n} actions",
|
|
294
|
+
toastDuplicated: "Duplicated page {n}",
|
|
295
|
+
toastDeleted: "Deleted page {n}",
|
|
296
|
+
toastDuplicateFailed: "Could not duplicate page",
|
|
297
|
+
toastDeleteFailed: "Could not delete page",
|
|
298
|
+
resizeRail: "Resize thumbnail rail"
|
|
299
|
+
},
|
|
300
|
+
pdfToast: {
|
|
301
|
+
title: "Exporting PDF",
|
|
302
|
+
processing: "Processing page {current} of {total}",
|
|
303
|
+
printing: "Opening print dialog…",
|
|
304
|
+
done: "Done"
|
|
305
|
+
},
|
|
306
|
+
themeToggle: {
|
|
307
|
+
toggleAria: "Toggle theme",
|
|
308
|
+
title: "Theme",
|
|
309
|
+
light: "Light",
|
|
310
|
+
dark: "Dark",
|
|
311
|
+
system: "System"
|
|
312
|
+
},
|
|
313
|
+
clickNav: {
|
|
314
|
+
prevAria: "Previous page",
|
|
315
|
+
nextAria: "Next page"
|
|
316
|
+
},
|
|
317
|
+
imagePlaceholder: {
|
|
318
|
+
dropOverlay: "Drop image to use here",
|
|
319
|
+
uploading: "Uploading…",
|
|
320
|
+
uploadFailed: "Couldn't upload image"
|
|
321
|
+
},
|
|
322
|
+
notesDrawer: {
|
|
323
|
+
toggle: "Notes",
|
|
324
|
+
pageLabel: "page {n}/{total}",
|
|
325
|
+
placeholder: "Write speaker notes for this slide…",
|
|
326
|
+
statusSaving: "Saving…",
|
|
327
|
+
statusSaved: "Saved",
|
|
328
|
+
statusError: "Save failed: {msg}"
|
|
329
|
+
},
|
|
330
|
+
themes: {
|
|
331
|
+
title: "Themes",
|
|
332
|
+
noThemesTitle: "No themes yet",
|
|
333
|
+
noThemesHintPrefix: "Run ",
|
|
334
|
+
noThemesHintSuffix: " to author one — a markdown file under themes/ plus a sibling demo slide.",
|
|
335
|
+
noDemoYet: "No demo yet",
|
|
336
|
+
noDemoHintPrefix: "Re-run ",
|
|
337
|
+
noDemoHintSuffix: " for this theme to generate a preview slide.",
|
|
338
|
+
backToGallery: "Back to themes",
|
|
339
|
+
pageOf: "page {n}/{total}",
|
|
340
|
+
nextPageAria: "Next page",
|
|
341
|
+
prevPageAria: "Previous page",
|
|
342
|
+
openThemeAria: "Open theme {name}",
|
|
343
|
+
usedBy: "Slides using this theme",
|
|
344
|
+
usedByEmpty: "No slides use this theme yet.",
|
|
345
|
+
expandPromptAria: "Expand prompt",
|
|
346
|
+
collapsePromptAria: "Collapse prompt"
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
export { en };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Locale, Plural } from "./types-
|
|
2
|
-
import { OpenSlideConfig } from "./config-
|
|
3
|
-
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
1
|
+
import { Locale, Plural } from "./types-JYG1cmwC.js";
|
|
2
|
+
import { OpenSlideConfig } from "./config-D9cZ1A0X.js";
|
|
4
3
|
import { CSSProperties, ComponentType, HTMLAttributes } from "react";
|
|
4
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
5
5
|
|
|
6
6
|
//#region src/app/components/image-placeholder.d.ts
|
|
7
7
|
type ImagePlaceholderProps = {
|
|
@@ -50,6 +50,7 @@ declare const defaultDesign: DesignSystem;
|
|
|
50
50
|
type Page = ComponentType;
|
|
51
51
|
type SlideMeta = {
|
|
52
52
|
title?: string;
|
|
53
|
+
theme?: string;
|
|
53
54
|
};
|
|
54
55
|
type SlideModule = {
|
|
55
56
|
default: Page[];
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,137 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { en } from "./en-CDKzoZvf.js";
|
|
2
|
+
import { cssVarsToString, defaultDesign, designToCssVars } from "./design-cpzS8aud.js";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import config from "virtual:open-slide/config";
|
|
2
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
7
|
|
|
8
|
+
//#region src/app/lib/assets.ts
|
|
9
|
+
async function listAssets(slideId) {
|
|
10
|
+
const res = await fetch(`/__assets/${slideId}`);
|
|
11
|
+
if (!res.ok) throw new Error(`GET /__assets/${slideId} ${res.status}`);
|
|
12
|
+
const data = await res.json();
|
|
13
|
+
return data.assets ?? [];
|
|
14
|
+
}
|
|
15
|
+
async function uploadAsset(slideId, file, opts = {}) {
|
|
16
|
+
const qs = opts.overwrite ? "?overwrite=1" : "";
|
|
17
|
+
return fetch(`/__assets/${slideId}/${encodeURIComponent(file.name)}${qs}`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
"content-type": file.type || "application/octet-stream",
|
|
21
|
+
"content-length": String(file.size)
|
|
22
|
+
},
|
|
23
|
+
body: file
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async function uploadWithAutoRename(slideId, file) {
|
|
27
|
+
let uploaded = lowercaseExtension(file);
|
|
28
|
+
let res = await uploadAsset(slideId, uploaded);
|
|
29
|
+
if (res.status === 409) {
|
|
30
|
+
const list = await listAssets(slideId);
|
|
31
|
+
const taken = new Set(list.map((a) => a.name));
|
|
32
|
+
uploaded = renamedCopy(uploaded, taken);
|
|
33
|
+
res = await uploadAsset(slideId, uploaded);
|
|
34
|
+
}
|
|
35
|
+
if (!res.ok) return {
|
|
36
|
+
ok: false,
|
|
37
|
+
status: res.status,
|
|
38
|
+
entry: null
|
|
39
|
+
};
|
|
40
|
+
const body = await res.json().catch(() => null);
|
|
41
|
+
const entry = {
|
|
42
|
+
name: body?.name ?? uploaded.name,
|
|
43
|
+
size: body?.size ?? uploaded.size,
|
|
44
|
+
mtime: body?.mtime ?? Date.now(),
|
|
45
|
+
mime: body?.mime ?? uploaded.type ?? "application/octet-stream",
|
|
46
|
+
url: body?.url ?? `/__assets/${slideId}/${encodeURIComponent(uploaded.name)}`
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
status: res.status,
|
|
51
|
+
entry
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function lowercaseExtension(file) {
|
|
55
|
+
const dot = file.name.lastIndexOf(".");
|
|
56
|
+
if (dot <= 0) return file;
|
|
57
|
+
const ext = file.name.slice(dot);
|
|
58
|
+
const lower = ext.toLowerCase();
|
|
59
|
+
if (ext === lower) return file;
|
|
60
|
+
return new File([file], file.name.slice(0, dot) + lower, {
|
|
61
|
+
type: file.type,
|
|
62
|
+
lastModified: file.lastModified
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function renamedCopy(file, taken) {
|
|
66
|
+
const dot = file.name.lastIndexOf(".");
|
|
67
|
+
const stem = dot > 0 ? file.name.slice(0, dot) : file.name;
|
|
68
|
+
const ext = dot > 0 ? file.name.slice(dot) : "";
|
|
69
|
+
let i = 1;
|
|
70
|
+
let next = `${stem}-${i}${ext}`;
|
|
71
|
+
while (taken.has(next)) {
|
|
72
|
+
i += 1;
|
|
73
|
+
next = `${stem}-${i}${ext}`;
|
|
74
|
+
}
|
|
75
|
+
return new File([file], next, {
|
|
76
|
+
type: file.type,
|
|
77
|
+
lastModified: file.lastModified
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/app/lib/use-locale.ts
|
|
83
|
+
const resolved = config.locale ?? en;
|
|
84
|
+
function useLocale() {
|
|
85
|
+
return resolved;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
4
89
|
//#region src/app/components/image-placeholder.tsx
|
|
5
90
|
function ImagePlaceholder({ hint, width, height, style, className,...rest }) {
|
|
6
91
|
const dims = width && height ? `${width} × ${height}` : null;
|
|
92
|
+
const [dragActive, setDragActive] = useState(false);
|
|
93
|
+
const [uploading, setUploading] = useState(false);
|
|
94
|
+
const dragDepth = useRef(0);
|
|
95
|
+
const t = useLocale();
|
|
96
|
+
const dndProps = import.meta.env.DEV ? {
|
|
97
|
+
onDragEnter: (e) => {
|
|
98
|
+
if (uploading || !hasImageFile(e)) return;
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
dragDepth.current += 1;
|
|
101
|
+
setDragActive(true);
|
|
102
|
+
},
|
|
103
|
+
onDragOver: (e) => {
|
|
104
|
+
if (uploading || !hasImageFile(e)) return;
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
e.dataTransfer.dropEffect = "copy";
|
|
107
|
+
},
|
|
108
|
+
onDragLeave: () => {
|
|
109
|
+
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
110
|
+
if (dragDepth.current === 0) setDragActive(false);
|
|
111
|
+
},
|
|
112
|
+
onDrop: (e) => {
|
|
113
|
+
if (uploading || !hasImageFile(e)) return;
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
dragDepth.current = 0;
|
|
116
|
+
setDragActive(false);
|
|
117
|
+
const file = pickImageFile(e.dataTransfer.files);
|
|
118
|
+
if (!file) return;
|
|
119
|
+
const root = e.currentTarget;
|
|
120
|
+
const slideId = root.closest("[data-slide-id]")?.dataset.slideId;
|
|
121
|
+
const loc = root.dataset.slideLoc;
|
|
122
|
+
if (!slideId || !loc) return;
|
|
123
|
+
const idx = loc.indexOf(":");
|
|
124
|
+
if (idx <= 0) return;
|
|
125
|
+
const line = Number(loc.slice(0, idx));
|
|
126
|
+
const column = Number(loc.slice(idx + 1));
|
|
127
|
+
if (!Number.isFinite(line) || !Number.isFinite(column)) return;
|
|
128
|
+
setUploading(true);
|
|
129
|
+
handleDrop(slideId, file, line, column).catch(() => toast.error(t.imagePlaceholder.uploadFailed)).finally(() => setUploading(false));
|
|
130
|
+
}
|
|
131
|
+
} : null;
|
|
7
132
|
return /* @__PURE__ */ jsxs("div", {
|
|
8
133
|
...rest,
|
|
134
|
+
...dndProps,
|
|
9
135
|
"data-slide-placeholder": hint,
|
|
10
136
|
"data-placeholder-w": width,
|
|
11
137
|
"data-placeholder-h": height,
|
|
@@ -32,47 +158,111 @@ function ImagePlaceholder({ hint, width, height, style, className,...rest }) {
|
|
|
32
158
|
...style
|
|
33
159
|
},
|
|
34
160
|
className,
|
|
35
|
-
children: [
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ jsx(PlaceholderIcon, {}),
|
|
163
|
+
/* @__PURE__ */ jsxs("div", {
|
|
164
|
+
style: {
|
|
165
|
+
display: "flex",
|
|
166
|
+
flexDirection: "column",
|
|
167
|
+
alignItems: "center",
|
|
168
|
+
gap: 6,
|
|
169
|
+
maxWidth: "85%"
|
|
170
|
+
},
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ jsx("span", {
|
|
173
|
+
style: {
|
|
174
|
+
fontSize: 11,
|
|
175
|
+
fontWeight: 600,
|
|
176
|
+
letterSpacing: "0.14em",
|
|
177
|
+
textTransform: "uppercase",
|
|
178
|
+
opacity: .55
|
|
179
|
+
},
|
|
180
|
+
children: "Image"
|
|
181
|
+
}),
|
|
182
|
+
/* @__PURE__ */ jsx("span", {
|
|
183
|
+
style: {
|
|
184
|
+
fontSize: 16,
|
|
185
|
+
fontWeight: 500,
|
|
186
|
+
lineHeight: 1.4,
|
|
187
|
+
color: "rgba(60, 60, 70, 0.85)"
|
|
188
|
+
},
|
|
189
|
+
children: hint
|
|
190
|
+
}),
|
|
191
|
+
dims && /* @__PURE__ */ jsx("span", {
|
|
192
|
+
style: {
|
|
193
|
+
fontSize: 11,
|
|
194
|
+
fontVariantNumeric: "tabular-nums",
|
|
195
|
+
fontFamily: "ui-monospace, \"SF Mono\", Menlo, Consolas, monospace",
|
|
196
|
+
opacity: .5,
|
|
197
|
+
marginTop: 2
|
|
198
|
+
},
|
|
199
|
+
children: dims
|
|
200
|
+
})
|
|
201
|
+
]
|
|
202
|
+
}),
|
|
203
|
+
import.meta.env.DEV && (dragActive || uploading) && /* @__PURE__ */ jsx(DropOverlay, { label: uploading ? t.imagePlaceholder.uploading : t.imagePlaceholder.dropOverlay })
|
|
204
|
+
]
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function DropOverlay({ label }) {
|
|
208
|
+
return /* @__PURE__ */ jsx("div", {
|
|
209
|
+
"aria-hidden": true,
|
|
210
|
+
style: {
|
|
211
|
+
position: "absolute",
|
|
212
|
+
inset: 0,
|
|
213
|
+
pointerEvents: "none",
|
|
214
|
+
borderRadius: 12,
|
|
215
|
+
border: "2px dashed oklch(0.62 0.18 250)",
|
|
216
|
+
background: "oklch(0.62 0.18 250 / 0.08)",
|
|
217
|
+
display: "flex",
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
justifyContent: "center"
|
|
220
|
+
},
|
|
221
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
36
222
|
style: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
223
|
+
fontSize: 12,
|
|
224
|
+
fontWeight: 600,
|
|
225
|
+
letterSpacing: "0.02em",
|
|
226
|
+
color: "oklch(0.45 0.16 250)",
|
|
227
|
+
background: "rgba(255,255,255,0.92)",
|
|
228
|
+
padding: "6px 10px",
|
|
229
|
+
borderRadius: 6,
|
|
230
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.08)"
|
|
42
231
|
},
|
|
43
|
-
children:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
})
|
|
232
|
+
children: label
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function hasImageFile(e) {
|
|
237
|
+
const types = e.dataTransfer?.types;
|
|
238
|
+
if (!types) return false;
|
|
239
|
+
for (let i = 0; i < types.length; i++) if (types[i] === "Files") return true;
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
function pickImageFile(files) {
|
|
243
|
+
for (let i = 0; i < files.length; i++) {
|
|
244
|
+
const f = files[i];
|
|
245
|
+
if (f.type.startsWith("image/")) return f;
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
async function handleDrop(slideId, file, line, column) {
|
|
250
|
+
const { ok, entry } = await uploadWithAutoRename(slideId, file);
|
|
251
|
+
if (!ok || !entry) throw new Error("upload failed");
|
|
252
|
+
const res = await fetch("/__edit", {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: { "content-type": "application/json" },
|
|
255
|
+
body: JSON.stringify({
|
|
256
|
+
slideId,
|
|
257
|
+
line,
|
|
258
|
+
column,
|
|
259
|
+
ops: [{
|
|
260
|
+
kind: "replace-placeholder-with-image",
|
|
261
|
+
assetPath: `./assets/${entry.name}`
|
|
262
|
+
}]
|
|
263
|
+
})
|
|
75
264
|
});
|
|
265
|
+
if (!res.ok) throw new Error(`edit failed (${res.status})`);
|
|
76
266
|
}
|
|
77
267
|
function PlaceholderIcon() {
|
|
78
268
|
return /* @__PURE__ */ jsxs("svg", {
|
package/dist/locale/index.d.ts
CHANGED