@shwfed/nuxt 0.1.55 → 0.1.57

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/module.d.mts CHANGED
@@ -62,10 +62,17 @@ interface ModuleOptions {
62
62
  /**
63
63
  * 导航栏右侧显示的内容,可以使用 Markdown (DSL)
64
64
  *
65
+ * 通常显示用户名
66
+ *
65
67
  * @type Markdown
66
68
  */
67
69
  name: string;
68
70
  }>;
71
+ /**
72
+ * 默认展示的应用标题
73
+ *
74
+ * 如果当前路由没有匹配到导航项,则显示该标题
75
+ */
69
76
  title: string;
70
77
  }
71
78
  interface Env {
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shwfed/nuxt",
3
3
  "configKey": "shwfed",
4
- "version": "0.1.55",
4
+ "version": "0.1.57",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -2,10 +2,37 @@ import type { Scope } from 'effect';
2
2
  import { Effect } from 'effect';
3
3
  type Item = Readonly<{
4
4
  id: string;
5
+ /**
6
+ * 命令的标题
7
+ *
8
+ * 调用者处理 i18n
9
+ *
10
+ * @type string
11
+ */
5
12
  title: string;
13
+ /**
14
+ * 命令的图标
15
+ *
16
+ * @type string
17
+ */
6
18
  icon?: string;
19
+ /**
20
+ * 命令是否禁用
21
+ *
22
+ * @type DSL
23
+ */
7
24
  disabled?: boolean | string;
25
+ /**
26
+ * 命令是否隐藏
27
+ *
28
+ * @type DSL | boolean
29
+ */
8
30
  hidden?: boolean | string;
31
+ /**
32
+ * 命令的关键词
33
+ *
34
+ * @type string[]
35
+ */
9
36
  keywords?: ReadonlyArray<string>;
10
37
  }>;
11
38
  type CommandPosition = 'profile' | 'command-palette';
@@ -14,7 +41,17 @@ interface Command extends Item {
14
41
  effect: Effect.Effect<void, never, Scope.Scope>;
15
42
  }
16
43
  interface Navigation extends Item {
44
+ /**
45
+ * 导航项的跳转目标
46
+ *
47
+ * @type string
48
+ */
17
49
  target: string;
50
+ /**
51
+ * 导航项是否为外部链接
52
+ *
53
+ * @type DSL
54
+ */
18
55
  external: string;
19
56
  }
20
57
  type __VLS_Props = {
@@ -27,6 +64,12 @@ type __VLS_Props = {
27
64
  icon?: string;
28
65
  children: ReadonlyArray<Navigation>;
29
66
  }>>;
67
+ /**
68
+ * 应用内命令列表,用于命令面板(Cmd+K)和/或顶栏个人资料下拉菜单。
69
+ * 每项为「单个命令」或「分组」:单个命令含 positions、effect;分组含 id、title、可选 icon、children(Command 数组)。
70
+ * positions 控制展示位置:'command-palette' 出现在命令面板,'profile' 出现在个人资料菜单;可同时包含两者。
71
+ * 用户选择某命令时执行其 effect(带 Scope)。可选 disabled、hidden(CEL 表达式)、keywords 与导航等用法一致。
72
+ */
30
73
  commands?: ReadonlyArray<Command | Readonly<{
31
74
  id: string;
32
75
  title: string;
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
- import { useHead, useNuxtApp, useRouter, useRuntimeConfig } from "#app";
3
- import { computed, ref } from "vue";
2
+ import { useHead, useNuxtApp, useRoute, useRouter, useRuntimeConfig } from "#app";
3
+ import { computed, onMounted, ref } from "vue";
4
4
  import { CommandDialog, CommandInput, CommandList, CommandGroup, CommandEmpty, CommandItem } from "./ui/command";
5
5
  import { TooltipProvider } from "./ui/tooltip";
6
6
  import { Toaster } from "vue-sonner";
@@ -14,7 +14,12 @@ import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu,
14
14
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";
15
15
  import Logo from "./logo.vue";
16
16
  const { $dsl } = useNuxtApp();
17
- const config = useRuntimeConfig().public.shwfed;
17
+ const {
18
+ public: {
19
+ shwfed: config
20
+ },
21
+ app
22
+ } = useRuntimeConfig();
18
23
  const props = defineProps({
19
24
  navigation: { type: Array, required: false },
20
25
  commands: { type: Array, required: false },
@@ -39,6 +44,7 @@ whenever(() => meta_k?.value, () => {
39
44
  isCommandOpen.value = !isCommandOpen.value;
40
45
  });
41
46
  const router = useRouter();
47
+ const route = useRoute();
42
48
  setGlobalDslContext(await props.dsl?.pipe(Effect.scoped).pipe(Effect.runPromise) ?? {});
43
49
  function toNavigationKey(groupId, itemId) {
44
50
  return groupId ? `${groupId}::${itemId}` : `::${itemId}`;
@@ -62,6 +68,25 @@ function findNavigationByKey(navList, key) {
62
68
  }
63
69
  return void 0;
64
70
  }
71
+ function normalizePathForMatch(p) {
72
+ const trimmed = p.replace(/\/$/, "");
73
+ return trimmed === "" ? "/" : trimmed;
74
+ }
75
+ function findNavigationKeyByPath(navList, path) {
76
+ const normalized = normalizePathForMatch(path);
77
+ for (const el of navList) {
78
+ if (el.type === "item") {
79
+ if (el.external === true) continue;
80
+ if (normalizePathForMatch(el.target) === normalized) return toNavigationKey("", el.id);
81
+ } else {
82
+ for (const child of el.children) {
83
+ if (child.external === true) continue;
84
+ if (normalizePathForMatch(child.target) === normalized) return toNavigationKey(el.id, child.id);
85
+ }
86
+ }
87
+ }
88
+ return void 0;
89
+ }
65
90
  const palette = computed(() => {
66
91
  const items = [];
67
92
  for (const navigation2 of props.navigation ?? []) {
@@ -288,6 +313,24 @@ const tabs = useSessionStorage("navigation-tabs", /* @__PURE__ */ new Set(), {
288
313
  const activeTab = useSessionStorage("navigation-active-tab", void 0, {
289
314
  writeDefaults: false
290
315
  });
316
+ onMounted(() => {
317
+ const baseRaw = app.baseURL ?? "/";
318
+ const base = baseRaw === "" || baseRaw === "/" ? "/" : baseRaw.replace(/\/*$/, "") + "/";
319
+ let pathToMatch;
320
+ if (base === "/") {
321
+ pathToMatch = route.path;
322
+ } else if (route.fullPath.startsWith(base)) {
323
+ const remainder = route.fullPath.slice(base.length) || "/";
324
+ pathToMatch = remainder.startsWith("/") ? remainder : `/${remainder}`;
325
+ } else {
326
+ pathToMatch = route.path;
327
+ }
328
+ const key = findNavigationKeyByPath(navigation.value, pathToMatch);
329
+ if (key !== void 0) {
330
+ tabs.value.add(key);
331
+ activeTab.value = key;
332
+ }
333
+ });
291
334
  useHead({
292
335
  title: () => activeTab.value ? findNavigationByKey(navigation.value, activeTab.value)?.title ?? config.title : config.title
293
336
  });
@@ -649,6 +692,7 @@ useHead({
649
692
  >
650
693
  {{ findNavigationByKey(navigation, tab)?.title ?? tab ?? "Unknown" }}
651
694
  <button
695
+ v-if="tabs.size > 1"
652
696
  type="button"
653
697
  class="opacity-80 group-hover/tab:opacity-100 transition-opacity cursor-pointer text-zinc-500 hover:text-(--primary) duration-180"
654
698
  @click.stop="() => {
@@ -2,10 +2,37 @@ import type { Scope } from 'effect';
2
2
  import { Effect } from 'effect';
3
3
  type Item = Readonly<{
4
4
  id: string;
5
+ /**
6
+ * 命令的标题
7
+ *
8
+ * 调用者处理 i18n
9
+ *
10
+ * @type string
11
+ */
5
12
  title: string;
13
+ /**
14
+ * 命令的图标
15
+ *
16
+ * @type string
17
+ */
6
18
  icon?: string;
19
+ /**
20
+ * 命令是否禁用
21
+ *
22
+ * @type DSL
23
+ */
7
24
  disabled?: boolean | string;
25
+ /**
26
+ * 命令是否隐藏
27
+ *
28
+ * @type DSL | boolean
29
+ */
8
30
  hidden?: boolean | string;
31
+ /**
32
+ * 命令的关键词
33
+ *
34
+ * @type string[]
35
+ */
9
36
  keywords?: ReadonlyArray<string>;
10
37
  }>;
11
38
  type CommandPosition = 'profile' | 'command-palette';
@@ -14,7 +41,17 @@ interface Command extends Item {
14
41
  effect: Effect.Effect<void, never, Scope.Scope>;
15
42
  }
16
43
  interface Navigation extends Item {
44
+ /**
45
+ * 导航项的跳转目标
46
+ *
47
+ * @type string
48
+ */
17
49
  target: string;
50
+ /**
51
+ * 导航项是否为外部链接
52
+ *
53
+ * @type DSL
54
+ */
18
55
  external: string;
19
56
  }
20
57
  type __VLS_Props = {
@@ -27,6 +64,12 @@ type __VLS_Props = {
27
64
  icon?: string;
28
65
  children: ReadonlyArray<Navigation>;
29
66
  }>>;
67
+ /**
68
+ * 应用内命令列表,用于命令面板(Cmd+K)和/或顶栏个人资料下拉菜单。
69
+ * 每项为「单个命令」或「分组」:单个命令含 positions、effect;分组含 id、title、可选 icon、children(Command 数组)。
70
+ * positions 控制展示位置:'command-palette' 出现在命令面板,'profile' 出现在个人资料菜单;可同时包含两者。
71
+ * 用户选择某命令时执行其 effect(带 Scope)。可选 disabled、hidden(CEL 表达式)、keywords 与导航等用法一致。
72
+ */
30
73
  commands?: ReadonlyArray<Command | Readonly<{
31
74
  id: string;
32
75
  title: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shwfed/nuxt",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "type": "module",