supaslidev 0.1.4 → 0.2.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.
Files changed (67) hide show
  1. package/app/app.config.ts +9 -0
  2. package/app/assets/css/main.css +90 -0
  3. package/app/components/AppHeader.vue +429 -0
  4. package/app/components/CreatePresentationDialog.vue +236 -0
  5. package/app/components/EmptyState.vue +37 -0
  6. package/app/components/ImportPresentationDialog.vue +865 -0
  7. package/app/components/PresentationCard.vue +343 -0
  8. package/app/components/PresentationListItem.vue +242 -0
  9. package/app/composables/useServers.ts +148 -0
  10. package/app/layouts/default.vue +49 -0
  11. package/app/pages/index.vue +542 -0
  12. package/dist/cli/index.js +183751 -137
  13. package/dist/config.d.ts +8 -0
  14. package/dist/config.js +16 -0
  15. package/dist/index.d.ts +21 -0
  16. package/dist/index.js +3 -0
  17. package/dist/module.d.ts +6 -0
  18. package/dist/module.js +9168 -0
  19. package/dist/prompt.js +847 -0
  20. package/nuxt.config.ts +53 -0
  21. package/package.json +26 -19
  22. package/server/api/export/[id].post.ts +67 -0
  23. package/server/api/open-editor/[id].post.ts +28 -0
  24. package/server/api/presentations/import.post.ts +139 -0
  25. package/server/api/presentations/index.get.ts +18 -0
  26. package/server/api/presentations/index.post.ts +175 -0
  27. package/server/api/presentations/upload.post.ts +174 -0
  28. package/server/api/presentations/validate.post.ts +14 -0
  29. package/server/api/servers/[id].delete.ts +15 -0
  30. package/server/api/servers/[id].post.ts +17 -0
  31. package/server/api/servers/index.delete.ts +5 -0
  32. package/server/api/servers/index.get.ts +5 -0
  33. package/server/api/servers/stop-all.post.ts +5 -0
  34. package/server/plugins/generate.ts +12 -0
  35. package/server/plugins/shutdown.ts +16 -0
  36. package/server/routes/exports/[...path].get.ts +25 -0
  37. package/server/utils/config.ts +13 -0
  38. package/server/utils/process-manager.ts +119 -0
  39. package/src/cli/commands/create.ts +125 -0
  40. package/src/cli/commands/deploy.ts +90 -0
  41. package/src/cli/commands/dev.ts +116 -0
  42. package/src/cli/commands/export.ts +63 -0
  43. package/src/cli/commands/import.ts +178 -0
  44. package/src/cli/commands/present.ts +111 -0
  45. package/src/cli/index.ts +87 -0
  46. package/src/cli/utils.ts +94 -0
  47. package/src/config.ts +21 -0
  48. package/src/index.ts +2 -0
  49. package/src/module.ts +12 -0
  50. package/src/shared/catalog.ts +94 -0
  51. package/src/shared/copy.ts +28 -0
  52. package/src/shared/index.ts +29 -0
  53. package/{scripts/generate-presentations.mjs → src/shared/presentations.ts} +23 -46
  54. package/src/shared/types.ts +29 -0
  55. package/src/shared/validation.ts +111 -0
  56. package/dist/assets/index-BerY9FcI.js +0 -49
  57. package/dist/assets/index-CVzsY-on.css +0 -1
  58. package/dist/index.html +0 -24
  59. package/server/api.js +0 -1225
  60. /package/{dist → public}/apple-touch-icon.png +0 -0
  61. /package/{dist → public}/favicon-96x96.png +0 -0
  62. /package/{dist → public}/favicon.ico +0 -0
  63. /package/{dist → public}/favicon.svg +0 -0
  64. /package/{dist → public}/site.webmanifest +0 -0
  65. /package/{dist → public}/ssl-logo.png +0 -0
  66. /package/{dist → public}/web-app-manifest-192x192.png +0 -0
  67. /package/{dist → public}/web-app-manifest-512x512.png +0 -0
@@ -0,0 +1,236 @@
1
+ <script setup lang="ts">
2
+ import type { Presentation } from '../composables/useServers';
3
+
4
+ interface Template {
5
+ value: string;
6
+ label: string;
7
+ description: string;
8
+ icon: string;
9
+ }
10
+
11
+ const templates: Template[] = [
12
+ {
13
+ value: 'default',
14
+ label: 'Default',
15
+ description: 'Clean and minimal starter template',
16
+ icon: 'i-lucide-layout-template',
17
+ },
18
+ {
19
+ value: 'seriph',
20
+ label: 'Seriph',
21
+ description: 'Elegant theme with serif typography',
22
+ icon: 'i-lucide-type',
23
+ },
24
+ {
25
+ value: 'apple-basic',
26
+ label: 'Apple Basic',
27
+ description: 'Minimalist Apple-inspired design',
28
+ icon: 'i-lucide-apple',
29
+ },
30
+ ];
31
+
32
+ const props = defineProps<{
33
+ open: boolean;
34
+ }>();
35
+
36
+ const emit = defineEmits<{
37
+ close: [];
38
+ created: [presentation: Presentation];
39
+ }>();
40
+
41
+ const name = ref('');
42
+ const title = ref('');
43
+ const description = ref('');
44
+ const selectedTemplate = ref('default');
45
+ const isSubmitting = ref(false);
46
+ const nameError = ref('');
47
+ const touched = ref(false);
48
+
49
+ const isValid = computed(() => {
50
+ if (!name.value.trim()) return false;
51
+ if (nameError.value) return false;
52
+ return true;
53
+ });
54
+
55
+ function validateName(value: string) {
56
+ if (!touched.value) return;
57
+
58
+ if (!value.trim()) {
59
+ nameError.value = 'Name is required';
60
+ return;
61
+ }
62
+
63
+ const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
64
+ if (!slugRegex.test(value)) {
65
+ nameError.value = 'Use lowercase letters, numbers, and hyphens only';
66
+ return;
67
+ }
68
+
69
+ nameError.value = '';
70
+ }
71
+
72
+ watch(name, (value) => {
73
+ if (touched.value) {
74
+ validateName(value);
75
+ }
76
+ });
77
+
78
+ function handleBlur() {
79
+ touched.value = true;
80
+ validateName(name.value);
81
+ }
82
+
83
+ function resetForm() {
84
+ name.value = '';
85
+ title.value = '';
86
+ description.value = '';
87
+ selectedTemplate.value = 'default';
88
+ nameError.value = '';
89
+ isSubmitting.value = false;
90
+ touched.value = false;
91
+ }
92
+
93
+ function handleClose() {
94
+ resetForm();
95
+ emit('close');
96
+ }
97
+
98
+ async function handleSubmit() {
99
+ touched.value = true;
100
+ validateName(name.value);
101
+ if (!isValid.value || isSubmitting.value) return;
102
+
103
+ isSubmitting.value = true;
104
+
105
+ try {
106
+ const response = await fetch('/api/presentations', {
107
+ method: 'POST',
108
+ headers: { 'Content-Type': 'application/json' },
109
+ body: JSON.stringify({
110
+ name: name.value,
111
+ title: title.value || undefined,
112
+ description: description.value || undefined,
113
+ template: selectedTemplate.value,
114
+ }),
115
+ });
116
+
117
+ if (!response.ok) {
118
+ const error = await response.json();
119
+ if (error.field === 'name') {
120
+ nameError.value = error.message;
121
+ }
122
+ return;
123
+ }
124
+
125
+ const result = await response.json();
126
+ emit('created', result.presentation ?? result);
127
+ handleClose();
128
+ } catch {
129
+ nameError.value = 'Failed to create presentation';
130
+ } finally {
131
+ isSubmitting.value = false;
132
+ }
133
+ }
134
+ </script>
135
+
136
+ <template>
137
+ <UModal :open="props.open" :ui="{ content: 'sm:max-w-xl' }" @close="handleClose">
138
+ <template #header>
139
+ <div class="flex items-center gap-3">
140
+ <div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
141
+ <UIcon name="i-lucide-presentation" class="w-5 h-5 text-primary" />
142
+ </div>
143
+ <div>
144
+ <h2 class="text-lg font-semibold">Create New Presentation</h2>
145
+ <p class="text-sm text-muted">Set up your new Slidev presentation</p>
146
+ </div>
147
+ </div>
148
+ </template>
149
+
150
+ <template #body>
151
+ <form class="flex flex-col gap-6" @submit.prevent="handleSubmit">
152
+ <UFormField
153
+ label="Presentation Name"
154
+ required
155
+ :error="nameError || undefined"
156
+ hint="This will be used as the folder name"
157
+ >
158
+ <UInput
159
+ v-model="name"
160
+ placeholder="my-presentation"
161
+ :color="nameError ? 'error' : undefined"
162
+ :ui="{ base: 'font-mono' }"
163
+ class="w-full"
164
+ autocomplete="off"
165
+ @blur="handleBlur"
166
+ />
167
+ </UFormField>
168
+
169
+ <UFormField label="Title" hint="Display title for the presentation">
170
+ <UInput
171
+ v-model="title"
172
+ placeholder="Welcome to My Presentation"
173
+ class="w-full"
174
+ autocomplete="off"
175
+ />
176
+ </UFormField>
177
+
178
+ <UFormField label="Description" hint="Brief description of the presentation">
179
+ <UTextarea
180
+ v-model="description"
181
+ placeholder="A presentation about..."
182
+ class="w-full"
183
+ :rows="2"
184
+ autoresize
185
+ />
186
+ </UFormField>
187
+
188
+ <UFormField label="Template">
189
+ <URadioGroup
190
+ v-model="selectedTemplate"
191
+ :items="templates"
192
+ variant="card"
193
+ orientation="horizontal"
194
+ indicator="hidden"
195
+ :ui="{ item: 'flex-1 items-start', wrapper: 'text-left' }"
196
+ >
197
+ <template #label="{ item }">
198
+ <div class="flex items-center gap-2">
199
+ <UIcon :name="(item as Template).icon" class="w-4 h-4 text-muted" />
200
+ <span>{{ (item as Template).label }}</span>
201
+ </div>
202
+ </template>
203
+ <template #description="{ item }">
204
+ <span class="text-xs">{{ (item as Template).description }}</span>
205
+ </template>
206
+ </URadioGroup>
207
+ </UFormField>
208
+
209
+ <div class="flex items-start gap-3 p-3 rounded-lg bg-muted/50">
210
+ <UIcon name="i-lucide-info" class="w-4 h-4 text-muted mt-0.5 shrink-0" />
211
+ <p class="text-sm text-muted">
212
+ You can customize the title, theme, and content by editing
213
+ <code class="font-mono text-xs px-1.5 py-0.5 rounded bg-muted">slides.md</code>
214
+ after creation.
215
+ </p>
216
+ </div>
217
+ </form>
218
+ </template>
219
+
220
+ <template #footer>
221
+ <div class="flex gap-3 justify-end">
222
+ <UButton color="neutral" variant="ghost" :disabled="isSubmitting" @click="handleClose">
223
+ Cancel
224
+ </UButton>
225
+ <UButton
226
+ :disabled="!isValid || isSubmitting"
227
+ :loading="isSubmitting"
228
+ icon="i-lucide-plus"
229
+ @click="handleSubmit"
230
+ >
231
+ {{ isSubmitting ? 'Creating...' : 'Create Presentation' }}
232
+ </UButton>
233
+ </div>
234
+ </template>
235
+ </UModal>
236
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ icon: string;
4
+ title: string;
5
+ description: string;
6
+ }>();
7
+ </script>
8
+
9
+ <template>
10
+ <div class="empty-state flex flex-col items-center justify-center py-16 sm:py-24 text-center">
11
+ <div class="empty-state-icon mb-4">
12
+ <UIcon :name="icon" class="w-12 h-12 text-muted" />
13
+ </div>
14
+ <h3 class="font-mono text-lg font-semibold text-default mb-2">{{ title }}</h3>
15
+ <p class="font-mono text-sm text-muted mb-6 max-w-sm">{{ description }}</p>
16
+ <slot />
17
+ </div>
18
+ </template>
19
+
20
+ <style scoped>
21
+ .empty-state {
22
+ border: 1px dashed var(--supaslidev-border);
23
+ border-radius: 0.75rem;
24
+ background: var(--ui-bg);
25
+ }
26
+
27
+ .empty-state-icon {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ width: 4rem;
32
+ height: 4rem;
33
+ border-radius: 50%;
34
+ background: var(--supaslidev-header-bg);
35
+ border: 1px solid var(--supaslidev-border);
36
+ }
37
+ </style>