supaslidev 0.3.1 → 0.3.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/app/app.config.ts +2 -2
- package/app/assets/css/main.css +23 -0
- package/app/components/AppHeader.vue +7 -19
- package/app/components/PresentationCard.vue +1 -1
- package/app/components/SettingsColorPicker.vue +37 -0
- package/app/composables/useSettings.ts +78 -0
- package/app/layouts/default.vue +6 -0
- package/app/pages/index.vue +11 -10
- package/app/pages/settings.vue +127 -0
- package/dist/cli/index.js +2 -2
- package/package.json +2 -2
- package/src/cli/commands/dev.ts +1 -1
package/app/app.config.ts
CHANGED
package/app/assets/css/main.css
CHANGED
|
@@ -6,17 +6,40 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
:root {
|
|
9
|
+
--ui-primary: var(--color-teal-700);
|
|
10
|
+
--ui-success: var(--color-green-700);
|
|
9
11
|
--supaslidev-border: #d1d5db;
|
|
10
12
|
--supaslidev-header-bg: rgba(0, 0, 0, 0.02);
|
|
11
13
|
--supaslidev-text-muted: #6b7280;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
.dark {
|
|
17
|
+
--ui-primary: var(--color-teal-500);
|
|
18
|
+
--ui-success: var(--color-green-500);
|
|
15
19
|
--supaslidev-border: #4b5563;
|
|
16
20
|
--supaslidev-header-bg: rgba(255, 255, 255, 0.03);
|
|
17
21
|
--supaslidev-text-muted: #9ca3af;
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
a,
|
|
25
|
+
button,
|
|
26
|
+
[role='button'],
|
|
27
|
+
select,
|
|
28
|
+
summary {
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.settings-icon {
|
|
33
|
+
transition:
|
|
34
|
+
transform 0.3s ease,
|
|
35
|
+
color 0.3s ease;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.settings-btn:hover .settings-icon {
|
|
39
|
+
transform: rotate(45deg);
|
|
40
|
+
color: var(--ui-primary);
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
*,
|
|
21
44
|
*::before,
|
|
22
45
|
*::after {
|
|
@@ -16,13 +16,7 @@ const emit = defineEmits<{
|
|
|
16
16
|
'execute-command': [command: string];
|
|
17
17
|
}>();
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
const isDark = computed({
|
|
21
|
-
get: () => colorMode.value === 'dark',
|
|
22
|
-
set: (value: boolean) => {
|
|
23
|
-
colorMode.preference = value ? 'dark' : 'light';
|
|
24
|
-
},
|
|
25
|
-
});
|
|
19
|
+
const router = useRouter();
|
|
26
20
|
|
|
27
21
|
const isMac = computed(() => {
|
|
28
22
|
if (typeof navigator === 'undefined') return true;
|
|
@@ -152,13 +146,15 @@ defineExpose({ focusInput, inputRef });
|
|
|
152
146
|
</div>
|
|
153
147
|
|
|
154
148
|
<UButton
|
|
155
|
-
|
|
149
|
+
icon="i-lucide-settings"
|
|
156
150
|
color="neutral"
|
|
157
151
|
variant="ghost"
|
|
158
152
|
size="md"
|
|
159
|
-
class="
|
|
160
|
-
|
|
161
|
-
|
|
153
|
+
class="settings-btn"
|
|
154
|
+
aria-label="Open settings"
|
|
155
|
+
title="Settings"
|
|
156
|
+
:ui="{ leadingIcon: 'settings-icon' }"
|
|
157
|
+
@click="router.push('/settings')"
|
|
162
158
|
/>
|
|
163
159
|
</div>
|
|
164
160
|
</div>
|
|
@@ -375,14 +371,6 @@ defineExpose({ focusInput, inputRef });
|
|
|
375
371
|
opacity: 0.85;
|
|
376
372
|
}
|
|
377
373
|
|
|
378
|
-
.theme-toggle {
|
|
379
|
-
transition: all 0.2s ease;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
.theme-toggle:hover {
|
|
383
|
-
box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
374
|
.dropdown {
|
|
387
375
|
border-top: 1px solid var(--supaslidev-border);
|
|
388
376
|
max-height: 240px;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
colors: readonly { name: string; value: string }[];
|
|
4
|
+
modelValue: string | undefined;
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}>();
|
|
8
|
+
|
|
9
|
+
const emit = defineEmits<{
|
|
10
|
+
'update:modelValue': [value: string];
|
|
11
|
+
}>();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<fieldset
|
|
16
|
+
class="flex items-center gap-4 rounded-xl w-fit p-2 -m-2 has-[input:focus-visible]:outline-[0.375rem] has-[input:focus-visible]:outline-double has-[input:focus-visible]:outline-primary has-[input:focus-visible]:shadow-[0_0_0_0.25rem_white]"
|
|
17
|
+
>
|
|
18
|
+
<legend class="sr-only">{{ label }}</legend>
|
|
19
|
+
<label
|
|
20
|
+
v-for="color in colors"
|
|
21
|
+
:key="color.name"
|
|
22
|
+
class="size-6 rounded-full cursor-pointer transition-transform duration-150 hover:scale-110 has-checked:outline-2 has-checked:outline-black dark:has-checked:outline-white has-checked:outline-offset-2"
|
|
23
|
+
:style="{ backgroundColor: color.value }"
|
|
24
|
+
:title="color.name"
|
|
25
|
+
>
|
|
26
|
+
<input
|
|
27
|
+
type="radio"
|
|
28
|
+
:name="name"
|
|
29
|
+
class="sr-only"
|
|
30
|
+
:checked="modelValue === color.name"
|
|
31
|
+
:aria-label="color.name"
|
|
32
|
+
:value="color.name"
|
|
33
|
+
@change="emit('update:modelValue', color.name)"
|
|
34
|
+
/>
|
|
35
|
+
</label>
|
|
36
|
+
</fieldset>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const ACCENT_COLORS = ['blue', 'green', 'red', 'orange', 'teal', 'indigo', 'violet'] as const;
|
|
2
|
+
const NEUTRAL_COLORS = ['slate', 'gray', 'zinc', 'neutral', 'stone'] as const;
|
|
3
|
+
|
|
4
|
+
type AccentColor = (typeof ACCENT_COLORS)[number];
|
|
5
|
+
type NeutralColor = (typeof NEUTRAL_COLORS)[number];
|
|
6
|
+
|
|
7
|
+
interface AppSettings {
|
|
8
|
+
accentColor: AccentColor;
|
|
9
|
+
neutralColor: NeutralColor;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STORAGE_KEY = 'supaslidev-settings';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_SETTINGS: AppSettings = {
|
|
15
|
+
accentColor: 'teal',
|
|
16
|
+
neutralColor: 'stone',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function loadSettings(): AppSettings {
|
|
20
|
+
if (import.meta.server) return { ...DEFAULT_SETTINGS };
|
|
21
|
+
try {
|
|
22
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
23
|
+
if (raw) {
|
|
24
|
+
const parsed = JSON.parse(raw) as Partial<AppSettings>;
|
|
25
|
+
return {
|
|
26
|
+
accentColor:
|
|
27
|
+
parsed.accentColor && ACCENT_COLORS.includes(parsed.accentColor as AccentColor)
|
|
28
|
+
? (parsed.accentColor as AccentColor)
|
|
29
|
+
: DEFAULT_SETTINGS.accentColor,
|
|
30
|
+
neutralColor:
|
|
31
|
+
parsed.neutralColor && NEUTRAL_COLORS.includes(parsed.neutralColor as NeutralColor)
|
|
32
|
+
? (parsed.neutralColor as NeutralColor)
|
|
33
|
+
: DEFAULT_SETTINGS.neutralColor,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Ignore localStorage errors
|
|
38
|
+
}
|
|
39
|
+
return { ...DEFAULT_SETTINGS };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const _settings = ref<AppSettings>(loadSettings());
|
|
43
|
+
let watcherInitialized = false;
|
|
44
|
+
|
|
45
|
+
function useSettings() {
|
|
46
|
+
if (!watcherInitialized) {
|
|
47
|
+
watcherInitialized = true;
|
|
48
|
+
watch(
|
|
49
|
+
_settings,
|
|
50
|
+
(value) => {
|
|
51
|
+
try {
|
|
52
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`Failed to persist settings to "${STORAGE_KEY}":`, error, value);
|
|
55
|
+
}
|
|
56
|
+
updateAppConfig({
|
|
57
|
+
ui: { colors: { primary: value.accentColor, neutral: value.neutralColor } },
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
{ deep: true },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Apply on first use
|
|
65
|
+
updateAppConfig({
|
|
66
|
+
ui: {
|
|
67
|
+
colors: {
|
|
68
|
+
primary: _settings.value.accentColor,
|
|
69
|
+
neutral: _settings.value.neutralColor,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return { settings: _settings };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { ACCENT_COLORS, DEFAULT_SETTINGS, NEUTRAL_COLORS, useSettings };
|
|
78
|
+
export type { AccentColor, NeutralColor, AppSettings };
|
package/app/layouts/default.vue
CHANGED
package/app/pages/index.vue
CHANGED
|
@@ -13,7 +13,6 @@ const {
|
|
|
13
13
|
} = useServers();
|
|
14
14
|
const { deployMode, showDeployDemoToast } = useDeployMode();
|
|
15
15
|
const toast = useToast();
|
|
16
|
-
const colorMode = useColorMode();
|
|
17
16
|
const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
|
|
18
17
|
|
|
19
18
|
function handleExportError(message: string) {
|
|
@@ -203,9 +202,11 @@ function handleImportCommand() {
|
|
|
203
202
|
isImportDialogOpen.value = true;
|
|
204
203
|
}
|
|
205
204
|
|
|
206
|
-
|
|
205
|
+
const router = useRouter();
|
|
206
|
+
|
|
207
|
+
function handleSettingsCommand() {
|
|
207
208
|
isCommandPaletteOpen.value = false;
|
|
208
|
-
|
|
209
|
+
router.push('/settings');
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
function handleToggleViewModeCommand() {
|
|
@@ -330,10 +331,10 @@ const commandPaletteGroups = computed<CommandPaletteGroup[]>(() => {
|
|
|
330
331
|
onSelect: handleImportCommand,
|
|
331
332
|
},
|
|
332
333
|
{
|
|
333
|
-
label: '
|
|
334
|
-
suffix:
|
|
335
|
-
icon:
|
|
336
|
-
onSelect:
|
|
334
|
+
label: 'Settings',
|
|
335
|
+
suffix: 'Customize appearance',
|
|
336
|
+
icon: 'i-lucide-settings',
|
|
337
|
+
onSelect: handleSettingsCommand,
|
|
337
338
|
},
|
|
338
339
|
{
|
|
339
340
|
label: 'Toggle view',
|
|
@@ -401,9 +402,9 @@ const commandOptions = computed(() => {
|
|
|
401
402
|
onSelect: handleImportCommand,
|
|
402
403
|
},
|
|
403
404
|
{
|
|
404
|
-
label: '
|
|
405
|
-
description:
|
|
406
|
-
onSelect:
|
|
405
|
+
label: 'Settings',
|
|
406
|
+
description: 'Customize appearance',
|
|
407
|
+
onSelect: handleSettingsCommand,
|
|
407
408
|
},
|
|
408
409
|
{
|
|
409
410
|
label: 'Toggle view',
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AccentColor, NeutralColor } from '../composables/useSettings';
|
|
3
|
+
import {
|
|
4
|
+
ACCENT_COLORS,
|
|
5
|
+
DEFAULT_SETTINGS,
|
|
6
|
+
NEUTRAL_COLORS,
|
|
7
|
+
useSettings,
|
|
8
|
+
} from '../composables/useSettings';
|
|
9
|
+
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const colorMode = useColorMode();
|
|
12
|
+
const { settings } = useSettings();
|
|
13
|
+
|
|
14
|
+
const ACCENT_HEX: Record<string, string> = {
|
|
15
|
+
blue: '#3b82f6',
|
|
16
|
+
green: '#22c55e',
|
|
17
|
+
red: '#ef4444',
|
|
18
|
+
orange: '#f97316',
|
|
19
|
+
teal: '#14b8a6',
|
|
20
|
+
indigo: '#6366f1',
|
|
21
|
+
violet: '#8b5cf6',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const NEUTRAL_HEX: Record<string, string> = {
|
|
25
|
+
slate: '#64748b',
|
|
26
|
+
gray: '#6b7280',
|
|
27
|
+
zinc: '#71717a',
|
|
28
|
+
neutral: '#737373',
|
|
29
|
+
stone: '#78716c',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const accentColorSwatches = ACCENT_COLORS.map((name: string) => ({
|
|
33
|
+
name,
|
|
34
|
+
value: ACCENT_HEX[name] ?? '#000000',
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
const neutralColorSwatches = NEUTRAL_COLORS.map((name: string) => ({
|
|
38
|
+
name,
|
|
39
|
+
value: NEUTRAL_HEX[name] ?? '#000000',
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const themeOptions = [
|
|
43
|
+
{ label: 'System', value: 'system' },
|
|
44
|
+
{ label: 'Light', value: 'light' },
|
|
45
|
+
{ label: 'Dark', value: 'dark' },
|
|
46
|
+
];
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<div>
|
|
51
|
+
<header class="mb-10">
|
|
52
|
+
<div class="flex items-center justify-between gap-4 mb-4">
|
|
53
|
+
<h1 class="text-2xl sm:text-3xl font-semibold font-mono">$ settings</h1>
|
|
54
|
+
<UButton
|
|
55
|
+
color="neutral"
|
|
56
|
+
variant="ghost"
|
|
57
|
+
icon="i-lucide-arrow-left"
|
|
58
|
+
class="cursor-pointer"
|
|
59
|
+
@click="router.push('/')"
|
|
60
|
+
>
|
|
61
|
+
Back
|
|
62
|
+
</UButton>
|
|
63
|
+
</div>
|
|
64
|
+
<p class="text-sm font-mono" style="color: var(--supaslidev-text-muted)">
|
|
65
|
+
Customize the dashboard appearance.
|
|
66
|
+
</p>
|
|
67
|
+
</header>
|
|
68
|
+
|
|
69
|
+
<div class="max-w-2xl space-y-8">
|
|
70
|
+
<section>
|
|
71
|
+
<h2
|
|
72
|
+
class="text-xs uppercase tracking-wider mb-4 font-mono"
|
|
73
|
+
style="color: var(--supaslidev-text-muted)"
|
|
74
|
+
>
|
|
75
|
+
Appearance
|
|
76
|
+
</h2>
|
|
77
|
+
<div class="settings-card space-y-6">
|
|
78
|
+
<div class="space-y-2">
|
|
79
|
+
<label for="theme-select" class="block text-sm font-medium font-mono"> Theme </label>
|
|
80
|
+
<USelect
|
|
81
|
+
id="theme-select"
|
|
82
|
+
v-model="colorMode.preference"
|
|
83
|
+
:items="themeOptions"
|
|
84
|
+
value-key="value"
|
|
85
|
+
class="max-w-48 cursor-pointer"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="space-y-3">
|
|
90
|
+
<span class="block text-sm font-medium font-mono"> Accent color </span>
|
|
91
|
+
<SettingsColorPicker
|
|
92
|
+
:colors="accentColorSwatches"
|
|
93
|
+
:model-value="settings.accentColor"
|
|
94
|
+
label="Accent color"
|
|
95
|
+
name="accent-color"
|
|
96
|
+
@update:model-value="
|
|
97
|
+
settings.accentColor = ($event ?? DEFAULT_SETTINGS.accentColor) as AccentColor
|
|
98
|
+
"
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="space-y-3">
|
|
103
|
+
<span class="block text-sm font-medium font-mono"> Background shade </span>
|
|
104
|
+
<SettingsColorPicker
|
|
105
|
+
:colors="neutralColorSwatches"
|
|
106
|
+
:model-value="settings.neutralColor"
|
|
107
|
+
label="Background shade"
|
|
108
|
+
name="background-shade"
|
|
109
|
+
@update:model-value="
|
|
110
|
+
settings.neutralColor = ($event ?? DEFAULT_SETTINGS.neutralColor) as NeutralColor
|
|
111
|
+
"
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</section>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<style scoped>
|
|
121
|
+
.settings-card {
|
|
122
|
+
background: var(--ui-bg-elevated);
|
|
123
|
+
border: 1px solid var(--supaslidev-border);
|
|
124
|
+
border-radius: 0.75rem;
|
|
125
|
+
padding: 1.5rem;
|
|
126
|
+
}
|
|
127
|
+
</style>
|
package/dist/cli/index.js
CHANGED
|
@@ -9028,7 +9028,7 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
9028
9028
|
exports.visitAsync = visit.visitAsync;
|
|
9029
9029
|
}));
|
|
9030
9030
|
//#endregion
|
|
9031
|
-
//#region ../../node_modules/.pnpm/tsdown@0.21.
|
|
9031
|
+
//#region ../../node_modules/.pnpm/tsdown@0.21.6_oxc-resolver@11.19.1_typescript@6.0.2_vue-tsc@3.2.6_typescript@6.0.2_/node_modules/tsdown/esm-shims.js
|
|
9032
9032
|
var getFilename, getDirname, __dirname, __filename;
|
|
9033
9033
|
var init_esm_shims = __esmMin((() => {
|
|
9034
9034
|
getFilename = () => fileURLToPath(import.meta.url);
|
|
@@ -186210,7 +186210,7 @@ var require_ts_morph = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
186210
186210
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
186211
186211
|
require_dist$2();
|
|
186212
186212
|
require_ts_morph();
|
|
186213
|
-
const CLI_VERSION = "0.3.
|
|
186213
|
+
const CLI_VERSION = "0.3.2";
|
|
186214
186214
|
const PACKAGE_NAME = "@supaslidev/cli";
|
|
186215
186215
|
const CACHE_DIR = join(tmpdir(), "supaslidev-cli");
|
|
186216
186216
|
const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "supaslidev",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "CLI toolkit for managing Supaslidev presentations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"slidev",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"typescript": "^6.0.0",
|
|
65
65
|
"vitest": "^4.0.0",
|
|
66
66
|
"vue-tsc": "^3.0.0",
|
|
67
|
-
"create-supaslidev": "^0.3.
|
|
67
|
+
"create-supaslidev": "^0.3.2"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"dev": "nuxt dev",
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { findProjectRoot } from '../utils.js';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
function findSupaslidevPackageRoot(): string {
|
|
8
8
|
let dir = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
|
|
10
10
|
while (dir !== dirname(dir)) {
|