huhaa-myskills 0.2.13 → 0.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/bin/huhaa-myskills.mjs +71 -5
- package/package.json +1 -1
- package/packages/web/dist/assets/index-CaQOPbDU.css +1 -0
- package/packages/web/dist/assets/index-Dlvh4-4K.js +36 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/src/App.vue +239 -265
- package/packages/web/src/components/DetailPanel.vue +551 -0
- package/packages/web/src/stores/i18n.js +6 -0
- package/packages/web/src/stores/skills.js +127 -0
- package/packages/web/src/styles.css +922 -275
- package/packages/web/dist/assets/index-BPepSb3q.js +0 -36
- package/packages/web/dist/assets/index-CIGkgT5w.css +0 -1
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
<link rel="manifest" href="/site.webmanifest">
|
|
16
16
|
<meta name="theme-color" content="#7c3aed">
|
|
17
17
|
<meta name="description" content="一个高效的技能、规则和提示管理工具">
|
|
18
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
18
|
+
<script type="module" crossorigin src="/assets/index-Dlvh4-4K.js"></script>
|
|
19
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CaQOPbDU.css">
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
22
22
|
<div id="app"></div>
|
package/packages/web/src/App.vue
CHANGED
|
@@ -12,6 +12,11 @@ const i18n = useI18nStore();
|
|
|
12
12
|
const t = i18n.t;
|
|
13
13
|
const md = new MarkdownIt({ html: false, linkify: true, typographer: true });
|
|
14
14
|
const viewMode = ref('list');
|
|
15
|
+
const sidebarWidth = ref(180); // 默认 180px(更合理的宽度)
|
|
16
|
+
const isResizing = ref(false);
|
|
17
|
+
const startX = ref(0);
|
|
18
|
+
const startWidth = ref(0);
|
|
19
|
+
const sidebarCollapsed = ref(false); // 侧边栏折叠状态
|
|
15
20
|
|
|
16
21
|
const viewModeLabel = computed(() => {
|
|
17
22
|
const labels = {
|
|
@@ -23,7 +28,15 @@ const viewModeLabel = computed(() => {
|
|
|
23
28
|
return labels[viewMode.value] || '列表';
|
|
24
29
|
});
|
|
25
30
|
|
|
26
|
-
onMounted(() =>
|
|
31
|
+
onMounted(() => {
|
|
32
|
+
store.load();
|
|
33
|
+
window.addEventListener('keydown', handleKeydown);
|
|
34
|
+
// 从 localStorage 加载侧栏宽度
|
|
35
|
+
const saved = localStorage.getItem('huhaa-sidebar-width');
|
|
36
|
+
if (saved) {
|
|
37
|
+
sidebarWidth.value = parseInt(saved, 10);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
27
40
|
|
|
28
41
|
const detailHtml = computed(() => {
|
|
29
42
|
const raw = store.selected?.raw || store.selected?.preview || '';
|
|
@@ -164,180 +177,134 @@ function formatBytes(n) {
|
|
|
164
177
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
165
178
|
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
166
179
|
}
|
|
167
|
-
</script>
|
|
168
180
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<div>
|
|
175
|
-
<h1>HuHaa-MySkills</h1>
|
|
176
|
-
<p>{{ t('appSubtitle') }}</p>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
181
|
+
function handleKeydown(e) {
|
|
182
|
+
if (e.key === 'Escape' && store.selected) {
|
|
183
|
+
store.selectedId = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
179
186
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
function startResize(e) {
|
|
188
|
+
isResizing.value = true;
|
|
189
|
+
startX.value = e.clientX;
|
|
190
|
+
startWidth.value = sidebarWidth.value;
|
|
191
|
+
document.addEventListener('mousemove', onResize);
|
|
192
|
+
document.addEventListener('mouseup', stopResize);
|
|
193
|
+
}
|
|
185
194
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
function onResize(e) {
|
|
196
|
+
if (!isResizing.value) return;
|
|
197
|
+
const diff = e.clientX - startX.value;
|
|
198
|
+
const newWidth = Math.max(80, Math.min(300, startWidth.value + diff));
|
|
199
|
+
sidebarWidth.value = newWidth;
|
|
200
|
+
}
|
|
190
201
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
function stopResize() {
|
|
203
|
+
isResizing.value = false;
|
|
204
|
+
document.removeEventListener('mousemove', onResize);
|
|
205
|
+
document.removeEventListener('mouseup', stopResize);
|
|
206
|
+
// 保存到 localStorage
|
|
207
|
+
localStorage.setItem('huhaa-sidebar-width', sidebarWidth.value.toString());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
onMounted(() => {
|
|
211
|
+
window.addEventListener('keydown', handleKeydown);
|
|
212
|
+
});
|
|
213
|
+
</script>
|
|
198
214
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
<template>
|
|
216
|
+
<div class="shell" :style="{ gridTemplateColumns: `${sidebarCollapsed ? 50 : sidebarWidth}px 1fr 380px` }">
|
|
217
|
+
<!-- LEFT SIDEBAR - 筛选项(支持拖拽) -->
|
|
218
|
+
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }">
|
|
219
|
+
<div class="sidebar-toggle" @click="sidebarCollapsed = !sidebarCollapsed" :title="sidebarCollapsed ? '展开筛选' : '折叠筛选'">
|
|
220
|
+
{{ sidebarCollapsed ? '▶' : '◀' }}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div class="sidebar-content" v-show="!sidebarCollapsed">
|
|
224
|
+
<!-- 筛选项 -->
|
|
225
|
+
<div class="filter-compact">
|
|
226
|
+
<label class="filter-item">
|
|
227
|
+
<span>类型</span>
|
|
228
|
+
<select v-model="store.filters.kind" class="filter-select-compact">
|
|
229
|
+
<option value="">全部</option>
|
|
230
|
+
<option v-for="o in store.kinds" :key="o.name" :value="o.name">{{ o.name }}</option>
|
|
231
|
+
</select>
|
|
232
|
+
</label>
|
|
233
|
+
|
|
234
|
+
<label class="filter-item">
|
|
235
|
+
<span>来源</span>
|
|
236
|
+
<select v-model="store.filters.source" class="filter-select-compact">
|
|
237
|
+
<option value="">全部</option>
|
|
238
|
+
<option v-for="o in store.filterSources" :key="o.name" :value="o.name">{{ o.name }}</option>
|
|
239
|
+
</select>
|
|
240
|
+
</label>
|
|
241
|
+
|
|
242
|
+
<label class="filter-item">
|
|
243
|
+
<span>产品</span>
|
|
244
|
+
<select v-model="store.filters.product" class="filter-select-compact">
|
|
245
|
+
<option value="">全部</option>
|
|
246
|
+
<option v-for="o in store.products" :key="o.name" :value="o.name">{{ o.name }}</option>
|
|
247
|
+
</select>
|
|
248
|
+
</label>
|
|
249
|
+
|
|
250
|
+
<label class="filter-item">
|
|
251
|
+
<span>品牌</span>
|
|
252
|
+
<select v-model="store.filters.brand" class="filter-select-compact">
|
|
253
|
+
<option value="">全部</option>
|
|
254
|
+
<option v-for="o in store.brands" :key="o.name" :value="o.name">{{ o.name }}</option>
|
|
255
|
+
</select>
|
|
256
|
+
</label>
|
|
257
|
+
|
|
258
|
+
<label class="filter-item">
|
|
259
|
+
<span>排序</span>
|
|
260
|
+
<select v-model="store.sortKey" class="filter-select-compact">
|
|
261
|
+
<option value="default">默认</option>
|
|
262
|
+
<option value="name">名称</option>
|
|
263
|
+
<option value="updated">最近更新</option>
|
|
264
|
+
<option value="kind">类型</option>
|
|
265
|
+
<option value="source">来源</option>
|
|
266
|
+
<option value="product">产品</option>
|
|
267
|
+
</select>
|
|
268
|
+
</label>
|
|
203
269
|
</div>
|
|
204
|
-
|
|
205
|
-
class="pill"
|
|
206
|
-
:class="{ active: !store.filters.editor }"
|
|
207
|
-
@click="store.setFilter('editor', '')"
|
|
208
|
-
>
|
|
209
|
-
<span>{{ t('all') }}</span><b>{{ store.stats?.total || 0 }}</b>
|
|
210
|
-
</button>
|
|
211
|
-
<button
|
|
212
|
-
v-for="e in store.editors"
|
|
213
|
-
:key="e.name"
|
|
214
|
-
class="pill"
|
|
215
|
-
:class="{ active: store.filters.editor === e.name }"
|
|
216
|
-
@click="store.setFilter('editor', e.name)"
|
|
217
|
-
>
|
|
218
|
-
<span>{{ e.name }}</span><b>{{ e.count }}</b>
|
|
219
|
-
</button>
|
|
220
|
-
</section>
|
|
270
|
+
</div>
|
|
221
271
|
|
|
272
|
+
<!-- 拖拽调整把手 -->
|
|
273
|
+
<div class="sidebar-resize-handle" @mousedown="startResize" title="拖拽调整宽度"></div>
|
|
222
274
|
</aside>
|
|
223
275
|
|
|
276
|
+
<!-- MAIN CONTENT -->
|
|
224
277
|
<main class="main">
|
|
278
|
+
<!-- TOPBAR -->
|
|
225
279
|
<header class="topbar">
|
|
226
|
-
<div class="
|
|
227
|
-
<div class="
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
/>
|
|
235
|
-
</div>
|
|
236
|
-
<button class="btn soft" @click="store.clearFilters">{{ t('clear') }}</button>
|
|
237
|
-
<button class="btn primary" :disabled="store.reloadState?.scanning" @click="store.reload({ done: t('reloadDone') })">
|
|
238
|
-
{{ store.reloadState?.scanning ? t('reloading') : t('reload') }}
|
|
239
|
-
</button>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<div class="toolbar-facets">
|
|
243
|
-
<label class="facet">
|
|
244
|
-
<span>{{ t('kind') }}</span>
|
|
245
|
-
<select v-model="store.filters.kind">
|
|
246
|
-
<option value="">{{ t('all') }}</option>
|
|
247
|
-
<option v-for="o in store.kinds" :key="o.name" :value="o.name">{{ o.name }} ({{ o.count }})</option>
|
|
248
|
-
</select>
|
|
249
|
-
</label>
|
|
250
|
-
<label class="facet">
|
|
251
|
-
<span>{{ t('source') }}</span>
|
|
252
|
-
<select v-model="store.filters.source">
|
|
253
|
-
<option value="">{{ t('all') }}</option>
|
|
254
|
-
<option v-for="o in store.filterSources" :key="o.name" :value="o.name">{{ o.name }} ({{ o.count }})</option>
|
|
255
|
-
</select>
|
|
256
|
-
</label>
|
|
257
|
-
<label class="facet">
|
|
258
|
-
<span>{{ t('product') }}</span>
|
|
259
|
-
<select v-model="store.filters.product">
|
|
260
|
-
<option value="">{{ t('all') }}</option>
|
|
261
|
-
<option v-for="o in store.products" :key="o.name" :value="o.name">{{ o.name }} ({{ o.count }})</option>
|
|
262
|
-
</select>
|
|
263
|
-
</label>
|
|
264
|
-
<label class="facet">
|
|
265
|
-
<span>{{ t('brand') }}</span>
|
|
266
|
-
<select v-model="store.filters.brand">
|
|
267
|
-
<option value="">{{ t('all') }}</option>
|
|
268
|
-
<option v-for="o in store.brands" :key="o.name" :value="o.name">{{ o.name }} ({{ o.count }})</option>
|
|
269
|
-
</select>
|
|
270
|
-
</label>
|
|
271
|
-
<label class="facet compact">
|
|
272
|
-
<span>{{ t('sort') }}</span>
|
|
273
|
-
<select v-model="store.sortKey">
|
|
274
|
-
<option value="default">{{ t('sortDefault') }}</option>
|
|
275
|
-
<option value="name">{{ t('sortName') }}</option>
|
|
276
|
-
<option value="updated">{{ t('sortUpdated') }}</option>
|
|
277
|
-
<option value="kind">{{ t('sortKind') }}</option>
|
|
278
|
-
<option value="source">{{ t('sortSource') }}</option>
|
|
279
|
-
<option value="product">{{ t('sortProduct') }}</option>
|
|
280
|
-
</select>
|
|
281
|
-
</label>
|
|
280
|
+
<div class="topbar-left">
|
|
281
|
+
<div class="search-box">
|
|
282
|
+
<span class="search-icon">⌕</span>
|
|
283
|
+
<input
|
|
284
|
+
v-model="store.query"
|
|
285
|
+
type="search"
|
|
286
|
+
:placeholder="t('searchPlaceholder')"
|
|
287
|
+
/>
|
|
282
288
|
</div>
|
|
289
|
+
</div>
|
|
283
290
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
<button
|
|
290
|
-
v-for="chip in activeFilterChips"
|
|
291
|
-
:key="`${chip.key}:${chip.value}`"
|
|
292
|
-
class="active-chip"
|
|
293
|
-
@click="clearFilterChip(chip.key)"
|
|
294
|
-
>
|
|
295
|
-
<span>{{ chip.label }}</span>
|
|
296
|
-
<strong>{{ chip.value }}</strong>
|
|
297
|
-
<b>×</b>
|
|
298
|
-
</button>
|
|
299
|
-
</div>
|
|
300
|
-
<span v-else class="active-filter">{{ t('searchHint') }}</span>
|
|
301
|
-
<span v-if="store.reloadState?.lastReloadAt" class="reload-state">
|
|
302
|
-
{{ t('lastReload') }} {{ new Date(store.reloadState.lastReloadAt).toLocaleTimeString() }}
|
|
303
|
-
</span>
|
|
304
|
-
</div>
|
|
291
|
+
<div class="topbar-right">
|
|
292
|
+
<button class="btn soft" @click="store.clearFilters">{{ t('clear') }}</button>
|
|
293
|
+
<button class="btn primary" :disabled="store.reloadState?.scanning" @click="store.reload({ done: t('reloadDone') })">
|
|
294
|
+
{{ store.reloadState?.scanning ? t('reloading') : t('reload') }}
|
|
295
|
+
</button>
|
|
305
296
|
</div>
|
|
306
297
|
</header>
|
|
307
298
|
|
|
308
|
-
|
|
309
|
-
<div
|
|
299
|
+
<!-- CONTENT AREA -->
|
|
300
|
+
<div class="content-wrapper">
|
|
301
|
+
<div v-if="store.error" class="error">{{ store.error }}</div>
|
|
302
|
+
<div v-if="store.loading" class="loading">{{ t('loading') }}</div>
|
|
310
303
|
|
|
311
|
-
|
|
304
|
+
<!-- LIST PANEL -->
|
|
312
305
|
<section class="list-panel">
|
|
313
306
|
<div class="list-head">
|
|
314
307
|
<div class="list-view-label">{{ viewModeLabel }}</div>
|
|
315
|
-
<div class="list-view-controls">
|
|
316
|
-
<button
|
|
317
|
-
:class="{ active: viewMode === 'list' }"
|
|
318
|
-
class="view-btn"
|
|
319
|
-
@click="viewMode = 'list'"
|
|
320
|
-
:title="t('list')"
|
|
321
|
-
>{{ t('list') }}</button>
|
|
322
|
-
<button
|
|
323
|
-
:class="{ active: viewMode === 'tree' }"
|
|
324
|
-
class="view-btn"
|
|
325
|
-
@click="viewMode = 'tree'"
|
|
326
|
-
:title="t('category')"
|
|
327
|
-
>{{ t('category') }}</button>
|
|
328
|
-
<button
|
|
329
|
-
:class="{ active: viewMode === 'path-tree' }"
|
|
330
|
-
class="view-btn"
|
|
331
|
-
@click="viewMode = 'path-tree'"
|
|
332
|
-
title="按目录结构"
|
|
333
|
-
>目录结构</button>
|
|
334
|
-
<button
|
|
335
|
-
:class="{ active: viewMode === 'app-tree' }"
|
|
336
|
-
class="view-btn"
|
|
337
|
-
@click="viewMode = 'app-tree'"
|
|
338
|
-
title="按应用分组"
|
|
339
|
-
>应用分组</button>
|
|
340
|
-
</div>
|
|
341
308
|
<div>
|
|
342
309
|
<strong>{{ store.filtered.length }}</strong>
|
|
343
310
|
<span>{{ t('items') }}</span>
|
|
@@ -346,28 +313,28 @@ function formatBytes(n) {
|
|
|
346
313
|
|
|
347
314
|
<!-- List View -->
|
|
348
315
|
<template v-if="viewMode === 'list'">
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
316
|
+
<button
|
|
317
|
+
v-for="item in store.filtered"
|
|
318
|
+
:key="item.id"
|
|
319
|
+
class="skill-card"
|
|
320
|
+
:class="{ selected: item.id === store.selectedId }"
|
|
321
|
+
@click="store.loadDetail(item.id)"
|
|
322
|
+
>
|
|
323
|
+
<div class="skill-title">
|
|
324
|
+
<span class="src" :class="`src-${item.source}`">{{ item.source }}</span>
|
|
325
|
+
<strong v-html="highlighted(item.name)"></strong>
|
|
326
|
+
</div>
|
|
327
|
+
<div class="skill-chipline">
|
|
328
|
+
<span>{{ item.editor || item.source }}</span>
|
|
329
|
+
<span>{{ item.kind || '-' }}</span>
|
|
330
|
+
<span v-if="item.product">{{ item.product }}</span>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="skill-meta">
|
|
333
|
+
<span>{{ item.category || '-' }}</span>
|
|
334
|
+
<span v-if="item.brand">· {{ item.brand }}</span>
|
|
335
|
+
</div>
|
|
336
|
+
<p v-html="highlighted(item.description)"></p>
|
|
337
|
+
</button>
|
|
371
338
|
</template>
|
|
372
339
|
|
|
373
340
|
<!-- Tree View (by Source) -->
|
|
@@ -379,105 +346,112 @@ function formatBytes(n) {
|
|
|
379
346
|
<!-- App Tree View -->
|
|
380
347
|
<AppTree v-else-if="viewMode === 'app-tree'" />
|
|
381
348
|
</section>
|
|
349
|
+
</div>
|
|
350
|
+
</main>
|
|
382
351
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
<span class="src" :class="`src-${store.selected.source}`">{{ store.selected.source }}</span>
|
|
388
|
-
<span>{{ store.selected.editor || store.selected.source }}</span>
|
|
389
|
-
<span>{{ store.selected.category || t('uncategorized') }}</span>
|
|
390
|
-
</div>
|
|
391
|
-
<h2>{{ i18n.skillText(store.selected, 'name') }}</h2>
|
|
392
|
-
<div class="badge-row">
|
|
393
|
-
<span
|
|
394
|
-
v-for="badge in detailBadges"
|
|
395
|
-
:key="badge"
|
|
396
|
-
class="badge"
|
|
397
|
-
:class="{ danger: badge === t('parseError'), warn: badge === t('redacted') }"
|
|
398
|
-
>{{ badge }}</span>
|
|
399
|
-
</div>
|
|
400
|
-
<p>{{ i18n.skillText(store.selected, 'description') }}</p>
|
|
401
|
-
</div>
|
|
402
|
-
</div>
|
|
352
|
+
<!-- RIGHT DETAIL PANEL - 固定第三栏(不是浮层) -->
|
|
353
|
+
<aside class="detail-panel-fixed" v-if="store.selected" role="complementary">
|
|
354
|
+
<!-- CLOSE BUTTON -->
|
|
355
|
+
<button class="detail-close" @click="store.selectedId = null" title="Close (Esc)">✕</button>
|
|
403
356
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
<
|
|
409
|
-
<
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
357
|
+
<!-- HEADER -->
|
|
358
|
+
<div class="detail-head">
|
|
359
|
+
<div>
|
|
360
|
+
<div class="detail-kicker">
|
|
361
|
+
<span class="src" :class="`src-${store.selected.source}`">{{ store.selected.source }}</span>
|
|
362
|
+
<span>{{ store.selected.editor || store.selected.source }}</span>
|
|
363
|
+
<span>{{ store.selected.category || t('uncategorized') }}</span>
|
|
364
|
+
</div>
|
|
365
|
+
<h2>{{ i18n.skillText(store.selected, 'name') }}</h2>
|
|
366
|
+
<div class="badge-row">
|
|
367
|
+
<span
|
|
368
|
+
v-for="badge in detailBadges"
|
|
369
|
+
:key="badge"
|
|
370
|
+
class="badge"
|
|
371
|
+
:class="{ danger: badge === t('parseError'), warn: badge === t('redacted') }"
|
|
372
|
+
>{{ badge }}</span>
|
|
413
373
|
</div>
|
|
374
|
+
<p>{{ i18n.skillText(store.selected, 'description') }}</p>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
414
377
|
|
|
415
|
-
|
|
378
|
+
<!-- ACTIONS -->
|
|
379
|
+
<div class="actions">
|
|
380
|
+
<button @click="store.copySelected('path', { copied: t('copied'), bytes: t('bytes') })" class="action-btn">💬 {{ t('copyPath') }}</button>
|
|
381
|
+
<button @click="store.copySelected('dir', { copied: t('copied'), bytes: t('bytes') })" class="action-btn">📂 {{ t('copyDir') }}</button>
|
|
382
|
+
<button @click="store.copySelected('rel', { copied: t('copied'), bytes: t('bytes') })" class="action-btn">🔗 {{ t('copyRel') }}</button>
|
|
383
|
+
<button @click="store.copySelected('prompt', { copied: t('copied'), bytes: t('bytes') })" class="action-btn">📋 {{ t('copyPrompt') }}</button>
|
|
384
|
+
<button @click="store.openSelected('cursor', { opened: t('opened') })" class="action-btn">🎯 {{ t('openCursor') }}</button>
|
|
385
|
+
<button @click="store.openSelected('finder', { opened: t('opened') })" class="action-btn">🔍 {{ t('reveal') }}</button>
|
|
386
|
+
</div>
|
|
416
387
|
|
|
417
|
-
|
|
418
|
-
<div class="usage-head">
|
|
419
|
-
<div>
|
|
420
|
-
<span>{{ t('usage') }}</span>
|
|
421
|
-
<h3>{{ t('howToUse') }}</h3>
|
|
422
|
-
</div>
|
|
423
|
-
<button @click="store.copySelected('prompt', { copied: t('copied'), bytes: t('bytes') })">{{ t('copyPrompt') }}</button>
|
|
424
|
-
</div>
|
|
425
|
-
<div class="usage-prompt"><code>{{ usagePrompt }}</code></div>
|
|
426
|
-
<div class="usage-grid">
|
|
427
|
-
<div>
|
|
428
|
-
<strong>{{ t('appliesTo') }}</strong>
|
|
429
|
-
<p>{{ store.selected.description || '-' }}</p>
|
|
430
|
-
</div>
|
|
431
|
-
<div>
|
|
432
|
-
<strong>{{ t('quickFacts') }}</strong>
|
|
433
|
-
<p>{{ store.selected.editor || store.selected.source }} · {{ store.selected.kind }}<span v-if="store.selected.product"> · {{ store.selected.product }}</span></p>
|
|
434
|
-
</div>
|
|
435
|
-
</div>
|
|
436
|
-
<div class="usage-block" v-if="store.selected.params?.length">
|
|
437
|
-
<strong>{{ t('params') }}</strong>
|
|
438
|
-
<div class="param-table">
|
|
439
|
-
<div v-for="p in store.selected.params" :key="p.name">
|
|
440
|
-
<code>{{ p.name }}</code>
|
|
441
|
-
<span>{{ p.type || '-' }}</span>
|
|
442
|
-
<b v-if="p.required">{{ t('required') }}</b>
|
|
443
|
-
<p>{{ p.description || p.default || '-' }}</p>
|
|
444
|
-
</div>
|
|
445
|
-
</div>
|
|
446
|
-
</div>
|
|
447
|
-
<div class="usage-block" v-if="store.selected.triggers?.length">
|
|
448
|
-
<strong>{{ t('triggers') }}</strong>
|
|
449
|
-
<span class="tag" v-for="tag in store.selected.triggers.slice(0, 12)" :key="tag">{{ tag }}</span>
|
|
450
|
-
</div>
|
|
451
|
-
<div class="usage-block" v-if="store.selected.links?.length">
|
|
452
|
-
<strong>{{ t('links') }}</strong>
|
|
453
|
-
<a class="usage-link" v-for="link in store.selected.links" :key="link.url" :href="link.url" target="_blank" rel="noreferrer">{{ link.label || link.url }}</a>
|
|
454
|
-
</div>
|
|
455
|
-
</section>
|
|
388
|
+
<div v-if="store.notice" class="notice">{{ store.notice }}</div>
|
|
456
389
|
|
|
457
|
-
|
|
390
|
+
<!-- SCROLLABLE CONTENT -->
|
|
391
|
+
<div class="detail-scroll">
|
|
392
|
+
<!-- USAGE CARD -->
|
|
393
|
+
<section class="usage-card">
|
|
394
|
+
<div class="usage-head">
|
|
395
|
+
<span>{{ t('usage') }}</span>
|
|
396
|
+
<h3>{{ t('howToUse') }}</h3>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="usage-prompt"><code>{{ usagePrompt }}</code></div>
|
|
399
|
+
<div class="usage-grid">
|
|
458
400
|
<div>
|
|
459
|
-
<
|
|
460
|
-
<
|
|
401
|
+
<strong>{{ t('appliesTo') }}</strong>
|
|
402
|
+
<p>{{ store.selected.description || '-' }}</p>
|
|
461
403
|
</div>
|
|
462
|
-
<div
|
|
463
|
-
<
|
|
464
|
-
<
|
|
465
|
-
</div>
|
|
466
|
-
<div v-if="store.selected.parseError" class="kv-error">
|
|
467
|
-
<dt>{{ t('parseError') }}</dt>
|
|
468
|
-
<dd>{{ store.selected.parseError }}</dd>
|
|
404
|
+
<div>
|
|
405
|
+
<strong>{{ t('quickFacts') }}</strong>
|
|
406
|
+
<p>{{ store.selected.editor || store.selected.source }} · {{ store.selected.kind }}<span v-if="store.selected.product"> · {{ store.selected.product }}</span></p>
|
|
469
407
|
</div>
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
408
|
+
</div>
|
|
409
|
+
<div class="usage-block" v-if="store.selected.params?.length">
|
|
410
|
+
<strong>{{ t('params') }}</strong>
|
|
411
|
+
<div class="param-table">
|
|
412
|
+
<div v-for="p in store.selected.params" :key="p.name">
|
|
413
|
+
<code>{{ p.name }}</code>
|
|
414
|
+
<span>{{ p.type || '-' }}</span>
|
|
415
|
+
<b v-if="p.required">{{ t('required') }}</b>
|
|
416
|
+
<p>{{ p.description || p.default || '-' }}</p>
|
|
417
|
+
</div>
|
|
475
418
|
</div>
|
|
476
|
-
</
|
|
477
|
-
|
|
478
|
-
|
|
419
|
+
</div>
|
|
420
|
+
<div class="usage-block" v-if="store.selected.triggers?.length">
|
|
421
|
+
<strong>{{ t('triggers') }}</strong>
|
|
422
|
+
<span class="tag" v-for="tag in store.selected.triggers.slice(0, 12)" :key="tag">{{ tag }}</span>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="usage-block" v-if="store.selected.links?.length">
|
|
425
|
+
<strong>{{ t('links') }}</strong>
|
|
426
|
+
<a class="usage-link" v-for="link in store.selected.links" :key="link.url" :href="link.url" target="_blank" rel="noreferrer">{{ link.label || link.url }}</a>
|
|
427
|
+
</div>
|
|
479
428
|
</section>
|
|
429
|
+
|
|
430
|
+
<!-- METADATA -->
|
|
431
|
+
<dl class="kv">
|
|
432
|
+
<div>
|
|
433
|
+
<dt>{{ t('path') }}</dt>
|
|
434
|
+
<dd><code>{{ store.selected.paths?.abs }}</code></dd>
|
|
435
|
+
</div>
|
|
436
|
+
<div v-for="([label, value]) in detailMeta" :key="label">
|
|
437
|
+
<dt>{{ label }}</dt>
|
|
438
|
+
<dd>{{ value }}</dd>
|
|
439
|
+
</div>
|
|
440
|
+
<div v-if="store.selected.parseError" class="kv-error">
|
|
441
|
+
<dt>{{ t('parseError') }}</dt>
|
|
442
|
+
<dd>{{ store.selected.parseError }}</dd>
|
|
443
|
+
</div>
|
|
444
|
+
<div v-if="store.selected.triggers?.length">
|
|
445
|
+
<dt>{{ t('triggers') }}</dt>
|
|
446
|
+
<dd>
|
|
447
|
+
<span class="tag" v-for="trig in store.selected.triggers.slice(0, 8)" :key="trig">{{ trig }}</span>
|
|
448
|
+
</dd>
|
|
449
|
+
</div>
|
|
450
|
+
</dl>
|
|
451
|
+
|
|
452
|
+
<!-- MARKDOWN CONTENT -->
|
|
453
|
+
<article class="markdown" v-html="detailHtml"></article>
|
|
480
454
|
</div>
|
|
481
|
-
</
|
|
455
|
+
</aside>
|
|
482
456
|
</div>
|
|
483
457
|
</template>
|