momo-ai 1.0.69 → 1.0.70
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/package.json +1 -1
- package/skills/mo-dev-vue2css/SKILL.md +297 -0
- package/skills/mo-dev-vue2css/scripts/detect-class-usage.sh +33 -0
- package/skills/mo-dev-vue2css/scripts/extract-css.sh +72 -0
- package/skills/mo-dev-vue2css/scripts/update-import.sh +55 -0
- package/src/executor/executeDocs.js +68 -32
package/package.json
CHANGED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mo-dev-vue2css
|
|
3
|
+
description: Use when Vue SFC has large style block (>100 lines), styles unstyled after split, or "Cannot find module" errors on CSS imports
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Vue CSS Extraction
|
|
7
|
+
|
|
8
|
+
Extract CSS from Vue SFC `<style>` blocks to separate files. Core principle: **file extension must match template class usage**.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Large `<style>` block (>100 lines)
|
|
13
|
+
- Styles appear unstyled after extraction
|
|
14
|
+
- Build error: "Cannot find module"
|
|
15
|
+
- Need to share styles across components
|
|
16
|
+
|
|
17
|
+
## Quick Workflow (Automated)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Run from skill directory or provide full path
|
|
21
|
+
cd .claude/skills/mo-dev-vue2css
|
|
22
|
+
|
|
23
|
+
# Extract CSS automatically
|
|
24
|
+
./scripts/extract-css.sh /path/to/Component.vue
|
|
25
|
+
|
|
26
|
+
# Verify
|
|
27
|
+
cd /path/to/web && npm run build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Script handles:**
|
|
31
|
+
- Detects `class` vs `:class="$style"` usage
|
|
32
|
+
- Chooses correct extension (`.css` or `.module.css`)
|
|
33
|
+
- Extracts CSS content
|
|
34
|
+
- Updates Vue file with import
|
|
35
|
+
- Preserves `scoped`/`module` attributes
|
|
36
|
+
|
|
37
|
+
## Manual Workflow (Fallback)
|
|
38
|
+
|
|
39
|
+
Use when script fails (multiple `<style>` blocks, mixed usage):
|
|
40
|
+
|
|
41
|
+
1. **Detect usage**: `./scripts/detect-class-usage.sh Component.vue`
|
|
42
|
+
- Output: `plain-class`, `style-module`, `mixed`, or `no-class`
|
|
43
|
+
2. **Extract manually**: Copy CSS, create file with correct extension
|
|
44
|
+
3. **Update import**: `<style scoped src="./Component.css"></style>`
|
|
45
|
+
4. **Verify**: `npm run build` + browser test
|
|
46
|
+
|
|
47
|
+
## Quick Reference
|
|
48
|
+
|
|
49
|
+
| Template Uses | File Extension | Import |
|
|
50
|
+
|---------------|----------------|--------|
|
|
51
|
+
| `class="..."` | `.css` | `<style scoped src="./X.css">` |
|
|
52
|
+
| `:class="$style.x"` | `.module.css` | `<style module src="./X.module.css">` |
|
|
53
|
+
| Multiple blocks | `.scoped.css` + `.global.css` | Two imports (manual) |
|
|
54
|
+
|
|
55
|
+
## Common Mistakes
|
|
56
|
+
|
|
57
|
+
### Mistake 1: Wrong File Extension
|
|
58
|
+
|
|
59
|
+
**Problem**: Used `.module.css` when template uses plain classes
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<!-- Template uses plain class -->
|
|
63
|
+
<div class="overview-page">...</div>
|
|
64
|
+
|
|
65
|
+
<!-- WRONG: .module.css triggers CSS Modules -->
|
|
66
|
+
<style scoped src="./Overview.module.css"></style>
|
|
67
|
+
|
|
68
|
+
<!-- CORRECT: Use .css -->
|
|
69
|
+
<style scoped src="./Overview.css"></style>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Symptom**: Build passes but page looks unstyled
|
|
73
|
+
|
|
74
|
+
### Mistake 2: Missing Scoping Attribute
|
|
75
|
+
|
|
76
|
+
**Problem**: Forgot to preserve `scoped` or `module` attribute
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<!-- WRONG: Lost scoped attribute -->
|
|
80
|
+
<style src="./Component.css"></style>
|
|
81
|
+
|
|
82
|
+
<!-- CORRECT: Preserve scoped -->
|
|
83
|
+
<style scoped src="./Component.css"></style>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Symptom**: Styles leak to other components
|
|
87
|
+
|
|
88
|
+
### Mistake 3: Incorrect Import Path
|
|
89
|
+
|
|
90
|
+
**Problem**: Wrong relative path to CSS file
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<!-- WRONG: Missing ./ prefix -->
|
|
94
|
+
<style scoped src="Component.css"></style>
|
|
95
|
+
|
|
96
|
+
<!-- CORRECT: Relative path -->
|
|
97
|
+
<style scoped src="./Component.css"></style>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Symptom**: Build fails with "Cannot find module"
|
|
101
|
+
|
|
102
|
+
### Mistake 4: Skipping Verification
|
|
103
|
+
|
|
104
|
+
**Problem**: Assumed extraction worked without testing
|
|
105
|
+
|
|
106
|
+
**Fix**: ALWAYS run build and browser test after extraction
|
|
107
|
+
|
|
108
|
+
## Debugging Checklist
|
|
109
|
+
|
|
110
|
+
If page looks unstyled after extraction:
|
|
111
|
+
|
|
112
|
+
1. ✅ Check file extension: `.css` or `.module.css`?
|
|
113
|
+
2. ✅ Check template: plain `class` or `:class="$style"`?
|
|
114
|
+
3. ✅ Check import: `<style scoped src="./xxx.css"></style>`?
|
|
115
|
+
4. ✅ Check scoping: `scoped` or `module` attribute present?
|
|
116
|
+
5. ✅ Check build: `npm run build` passes?
|
|
117
|
+
6. ✅ Check browser: styles visible in DevTools?
|
|
118
|
+
|
|
119
|
+
## Examples
|
|
120
|
+
|
|
121
|
+
### Example 1: Simple Scoped Extraction
|
|
122
|
+
|
|
123
|
+
**Before**:
|
|
124
|
+
```vue
|
|
125
|
+
<template>
|
|
126
|
+
<div class="card">
|
|
127
|
+
<h2 class="title">Title</h2>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
130
|
+
|
|
131
|
+
<style scoped>
|
|
132
|
+
.card {
|
|
133
|
+
border: 1px solid #ddd;
|
|
134
|
+
padding: 16px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.title {
|
|
138
|
+
font-size: 18px;
|
|
139
|
+
font-weight: bold;
|
|
140
|
+
}
|
|
141
|
+
</style>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**After**:
|
|
145
|
+
|
|
146
|
+
`Component.vue`:
|
|
147
|
+
```vue
|
|
148
|
+
<template>
|
|
149
|
+
<div class="card">
|
|
150
|
+
<h2 class="title">Title</h2>
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<style scoped src="./Component.css"></style>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`Component.css`:
|
|
158
|
+
```css
|
|
159
|
+
.card {
|
|
160
|
+
border: 1px solid #ddd;
|
|
161
|
+
padding: 16px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.title {
|
|
165
|
+
font-size: 18px;
|
|
166
|
+
font-weight: bold;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Example 2: CSS Modules Extraction
|
|
171
|
+
|
|
172
|
+
**Before**:
|
|
173
|
+
```vue
|
|
174
|
+
<template>
|
|
175
|
+
<div :class="$style.container">
|
|
176
|
+
<span :class="$style.label">Label</span>
|
|
177
|
+
</div>
|
|
178
|
+
</template>
|
|
179
|
+
|
|
180
|
+
<style module>
|
|
181
|
+
.container {
|
|
182
|
+
display: flex;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.label {
|
|
186
|
+
color: blue;
|
|
187
|
+
}
|
|
188
|
+
</style>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**After**:
|
|
192
|
+
|
|
193
|
+
`Component.vue`:
|
|
194
|
+
```vue
|
|
195
|
+
<template>
|
|
196
|
+
<div :class="$style.container">
|
|
197
|
+
<span :class="$style.label">Label</span>
|
|
198
|
+
</div>
|
|
199
|
+
</template>
|
|
200
|
+
|
|
201
|
+
<style module src="./Component.module.css"></style>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`Component.module.css`:
|
|
205
|
+
```css
|
|
206
|
+
.container {
|
|
207
|
+
display: flex;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.label {
|
|
211
|
+
color: blue;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Example 3: Multiple Style Blocks
|
|
216
|
+
|
|
217
|
+
**Before**:
|
|
218
|
+
```vue
|
|
219
|
+
<template>
|
|
220
|
+
<div class="local">
|
|
221
|
+
<div class="global">...</div>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|
|
224
|
+
|
|
225
|
+
<style scoped>
|
|
226
|
+
.local {
|
|
227
|
+
padding: 10px;
|
|
228
|
+
}
|
|
229
|
+
</style>
|
|
230
|
+
|
|
231
|
+
<style>
|
|
232
|
+
.global {
|
|
233
|
+
margin: 0;
|
|
234
|
+
}
|
|
235
|
+
</style>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**After**:
|
|
239
|
+
|
|
240
|
+
`Component.vue`:
|
|
241
|
+
```vue
|
|
242
|
+
<template>
|
|
243
|
+
<div class="local">
|
|
244
|
+
<div class="global">...</div>
|
|
245
|
+
</div>
|
|
246
|
+
</template>
|
|
247
|
+
|
|
248
|
+
<style scoped src="./Component.scoped.css"></style>
|
|
249
|
+
<style src="./Component.global.css"></style>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`Component.scoped.css`:
|
|
253
|
+
```css
|
|
254
|
+
.local {
|
|
255
|
+
padding: 10px;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`Component.global.css`:
|
|
260
|
+
```css
|
|
261
|
+
.global {
|
|
262
|
+
margin: 0;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Integration with ft-webos
|
|
267
|
+
|
|
268
|
+
When working in ft-webos project:
|
|
269
|
+
|
|
270
|
+
1. **Check CLAUDE.md first**: Review project-specific CSS conventions
|
|
271
|
+
2. **Follow existing patterns**: Check how other components handle CSS
|
|
272
|
+
3. **Verify theme compatibility**: Ensure extracted CSS works with all themes
|
|
273
|
+
4. **Test in all themes**: macOS, Windows, Linux themes
|
|
274
|
+
5. **Update memory**: Document any project-specific patterns learned
|
|
275
|
+
|
|
276
|
+
## Workflow Summary
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
1. Read Vue SFC
|
|
280
|
+
2. Analyze template class usage (plain vs $style)
|
|
281
|
+
3. Determine file extension (.css vs .module.css)
|
|
282
|
+
4. Extract CSS content to new file
|
|
283
|
+
5. Update SFC with external import
|
|
284
|
+
6. Preserve scoping attributes (scoped/module)
|
|
285
|
+
7. Run npm run build
|
|
286
|
+
8. Test in browser
|
|
287
|
+
9. Verify styles render correctly
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Success Criteria
|
|
291
|
+
|
|
292
|
+
- ✅ Build passes with no errors
|
|
293
|
+
- ✅ Styles render correctly in browser
|
|
294
|
+
- ✅ No visual regressions
|
|
295
|
+
- ✅ File extension matches template usage
|
|
296
|
+
- ✅ Scoping preserved (no style leaks)
|
|
297
|
+
- ✅ Import path correct
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [ $# -ne 1 ]; then
|
|
5
|
+
echo "Usage: $0 <component.vue>" >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
FILE="$1"
|
|
10
|
+
if [ ! -f "$FILE" ]; then
|
|
11
|
+
echo "Error: file not found: $FILE" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
PLAIN_COUNT=$(grep -Eo '(^|[^:])class="[^"]*"' "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
16
|
+
STYLE_COUNT=$(grep -Eo ':class="\$style\.[^"]*"' "$FILE" 2>/dev/null | wc -l | tr -d ' ' || true)
|
|
17
|
+
|
|
18
|
+
if [ "$PLAIN_COUNT" -gt 0 ] && [ "$STYLE_COUNT" -eq 0 ]; then
|
|
19
|
+
echo "plain-class"
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [ "$PLAIN_COUNT" -eq 0 ] && [ "$STYLE_COUNT" -gt 0 ]; then
|
|
24
|
+
echo "style-module"
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if [ "$PLAIN_COUNT" -gt 0 ] && [ "$STYLE_COUNT" -gt 0 ]; then
|
|
29
|
+
echo "mixed"
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
echo "no-class"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [ $# -ne 1 ]; then
|
|
5
|
+
echo "Usage: $0 <component.vue>" >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
FILE="$1"
|
|
10
|
+
if [ ! -f "$FILE" ]; then
|
|
11
|
+
echo "Error: file not found: $FILE" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
DIR="$(dirname "$FILE")"
|
|
16
|
+
BASE="$(basename "$FILE" .vue)"
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
DETECT_SCRIPT="$SCRIPT_DIR/detect-class-usage.sh"
|
|
19
|
+
UPDATE_SCRIPT="$SCRIPT_DIR/update-import.sh"
|
|
20
|
+
|
|
21
|
+
USAGE="$($DETECT_SCRIPT "$FILE")"
|
|
22
|
+
case "$USAGE" in
|
|
23
|
+
plain-class) CSS_EXT=".css" ;;
|
|
24
|
+
style-module) CSS_EXT=".module.css" ;;
|
|
25
|
+
mixed)
|
|
26
|
+
echo "Error: mixed class and \$style usage. Handle manually." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
;;
|
|
29
|
+
no-class) CSS_EXT=".css" ;;
|
|
30
|
+
*)
|
|
31
|
+
echo "Error: unknown class usage result: $USAGE" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
STYLE_COUNT=$(grep -c '<style' "$FILE" || true)
|
|
37
|
+
if [ "$STYLE_COUNT" -eq 0 ]; then
|
|
38
|
+
echo "No <style> block found in $FILE"
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [ "$STYLE_COUNT" -gt 1 ]; then
|
|
43
|
+
echo "Error: multiple <style> blocks detected. Handle manually." >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
STYLE_OPEN_LINE=$(grep -n '<style[^>]*>' "$FILE" | head -n1 | cut -d: -f1)
|
|
48
|
+
STYLE_CLOSE_LINE=$(grep -n '</style>' "$FILE" | head -n1 | cut -d: -f1)
|
|
49
|
+
|
|
50
|
+
if [ -z "$STYLE_OPEN_LINE" ] || [ -z "$STYLE_CLOSE_LINE" ] || [ "$STYLE_CLOSE_LINE" -le "$STYLE_OPEN_LINE" ]; then
|
|
51
|
+
echo "Error: invalid <style> block in $FILE" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
STYLE_TAG=$(sed -n "${STYLE_OPEN_LINE}p" "$FILE")
|
|
56
|
+
CSS_FILE="$DIR/$BASE$CSS_EXT"
|
|
57
|
+
CSS_FILE_NAME="$BASE$CSS_EXT"
|
|
58
|
+
|
|
59
|
+
sed -n "$((STYLE_OPEN_LINE + 1)),$((STYLE_CLOSE_LINE - 1))p" "$FILE" > "$CSS_FILE"
|
|
60
|
+
|
|
61
|
+
if [[ "$STYLE_TAG" == *"scoped"* ]]; then
|
|
62
|
+
MODE="scoped"
|
|
63
|
+
elif [[ "$STYLE_TAG" == *"module"* ]]; then
|
|
64
|
+
MODE="module"
|
|
65
|
+
else
|
|
66
|
+
MODE="plain"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
"$UPDATE_SCRIPT" "$FILE" "$CSS_FILE_NAME" "$MODE"
|
|
70
|
+
|
|
71
|
+
echo "Extracted: $CSS_FILE"
|
|
72
|
+
echo "Updated: $FILE"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
|
5
|
+
echo "Usage: $0 <component.vue> <css-file-name> [scoped|module|plain]" >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
FILE="$1"
|
|
10
|
+
CSS_FILE_NAME="$2"
|
|
11
|
+
MODE="${3:-scoped}"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$FILE" ]; then
|
|
14
|
+
echo "Error: file not found: $FILE" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
STYLE_COUNT=$(grep -c '<style' "$FILE" || true)
|
|
19
|
+
if [ "$STYLE_COUNT" -eq 0 ]; then
|
|
20
|
+
echo "Error: no <style> block found in $FILE" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [ "$STYLE_COUNT" -gt 1 ]; then
|
|
25
|
+
echo "Error: multiple <style> blocks detected. Handle manually." >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
STYLE_OPEN_LINE=$(grep -n '<style[^>]*>' "$FILE" | head -n1 | cut -d: -f1)
|
|
30
|
+
STYLE_CLOSE_LINE=$(grep -n '</style>' "$FILE" | head -n1 | cut -d: -f1)
|
|
31
|
+
|
|
32
|
+
if [ -z "$STYLE_OPEN_LINE" ] || [ -z "$STYLE_CLOSE_LINE" ] || [ "$STYLE_CLOSE_LINE" -le "$STYLE_OPEN_LINE" ]; then
|
|
33
|
+
echo "Error: invalid <style> block in $FILE" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
case "$MODE" in
|
|
38
|
+
scoped) NEW_STYLE="<style scoped src=\"./$CSS_FILE_NAME\"></style>" ;;
|
|
39
|
+
module) NEW_STYLE="<style module src=\"./$CSS_FILE_NAME\"></style>" ;;
|
|
40
|
+
plain) NEW_STYLE="<style src=\"./$CSS_FILE_NAME\"></style>" ;;
|
|
41
|
+
*)
|
|
42
|
+
echo "Error: invalid mode '$MODE' (use scoped|module|plain)" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
awk -v start="$STYLE_OPEN_LINE" -v end="$STYLE_CLOSE_LINE" -v repl="$NEW_STYLE" '
|
|
48
|
+
NR < start { print; next }
|
|
49
|
+
NR == start { print repl; next }
|
|
50
|
+
NR > start && NR <= end { next }
|
|
51
|
+
{ print }
|
|
52
|
+
' "$FILE" > "$FILE.tmp"
|
|
53
|
+
mv "$FILE.tmp" "$FILE"
|
|
54
|
+
|
|
55
|
+
echo "Updated import in: $FILE"
|
|
@@ -49,49 +49,85 @@ const _isObsidianInstalled = async () => {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* 检查指定 vault 是否正在运行
|
|
53
|
+
* @param {string} vaultPath vault 路径
|
|
53
54
|
* @returns {Promise<boolean>}
|
|
54
55
|
*/
|
|
55
|
-
const
|
|
56
|
+
const _isVaultRunning = async (vaultPath) => {
|
|
56
57
|
const platform = os.platform();
|
|
57
|
-
|
|
58
|
+
let configPath;
|
|
59
|
+
|
|
60
|
+
if (platform === 'darwin') {
|
|
61
|
+
configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
|
|
62
|
+
} else if (platform === 'win32') {
|
|
63
|
+
configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
|
|
64
|
+
} else {
|
|
65
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
66
|
+
configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
try {
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
return result.toString().trim().length > 0;
|
|
62
|
-
} else if (platform === 'win32') {
|
|
63
|
-
const result = execSync('tasklist /FI "IMAGENAME eq Obsidian.exe"', { stdio: 'pipe' });
|
|
64
|
-
return result.toString().includes('Obsidian.exe');
|
|
65
|
-
} else {
|
|
66
|
-
const result = execSync('pgrep -f obsidian', { stdio: 'pipe' });
|
|
67
|
-
return result.toString().trim().length > 0;
|
|
70
|
+
if (!fs.existsSync(configPath)) {
|
|
71
|
+
return false;
|
|
68
72
|
}
|
|
73
|
+
|
|
74
|
+
const content = await fsAsync.readFile(configPath, 'utf8');
|
|
75
|
+
const config = JSON.parse(content);
|
|
76
|
+
|
|
77
|
+
// 检查是否有 vault 的 open 状态为 true 且路径匹配
|
|
78
|
+
const openVault = Object.values(config.vaults || {}).find(
|
|
79
|
+
vault => vault.path === vaultPath && vault.open === true
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return !!openVault;
|
|
69
83
|
} catch {
|
|
70
84
|
return false;
|
|
71
85
|
}
|
|
72
86
|
};
|
|
73
87
|
|
|
74
88
|
/**
|
|
75
|
-
*
|
|
89
|
+
* 关闭指定 vault 的 Obsidian 窗口
|
|
90
|
+
* 通过更新配置文件将 vault 的 open 状态设为 false
|
|
91
|
+
* @param {string} vaultPath vault 路径
|
|
76
92
|
* @returns {Promise<void>}
|
|
77
93
|
*/
|
|
78
|
-
const
|
|
94
|
+
const _closeVault = async (vaultPath) => {
|
|
79
95
|
const platform = os.platform();
|
|
80
|
-
|
|
96
|
+
let configPath;
|
|
97
|
+
|
|
98
|
+
if (platform === 'darwin') {
|
|
99
|
+
configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
|
|
100
|
+
} else if (platform === 'win32') {
|
|
101
|
+
configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
|
|
102
|
+
} else {
|
|
103
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
104
|
+
configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
try {
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
// 等待进程完全关闭
|
|
85
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
86
|
-
} else if (platform === 'win32') {
|
|
87
|
-
execSync('taskkill /F /IM Obsidian.exe', { stdio: 'ignore' });
|
|
88
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
89
|
-
} else {
|
|
90
|
-
execSync('killall obsidian', { stdio: 'ignore' });
|
|
91
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
108
|
+
if (!fs.existsSync(configPath)) {
|
|
109
|
+
return;
|
|
92
110
|
}
|
|
93
|
-
|
|
94
|
-
|
|
111
|
+
|
|
112
|
+
const content = await fsAsync.readFile(configPath, 'utf8');
|
|
113
|
+
const config = JSON.parse(content);
|
|
114
|
+
|
|
115
|
+
// 找到对应的 vault 并设置 open 为 false
|
|
116
|
+
let updated = false;
|
|
117
|
+
for (const [id, vault] of Object.entries(config.vaults || {})) {
|
|
118
|
+
if (vault.path === vaultPath && vault.open === true) {
|
|
119
|
+
config.vaults[id].open = false;
|
|
120
|
+
updated = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (updated) {
|
|
125
|
+
await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
|
|
126
|
+
// 等待配置生效
|
|
127
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
Ec.warn(`⚠ 关闭 vault 失败: ${error.message}`);
|
|
95
131
|
}
|
|
96
132
|
};
|
|
97
133
|
|
|
@@ -451,11 +487,11 @@ module.exports = async (options) => {
|
|
|
451
487
|
|
|
452
488
|
Ec.info('✓ Obsidian 已安装');
|
|
453
489
|
|
|
454
|
-
// 6.
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
457
|
-
Ec.waiting('
|
|
458
|
-
await
|
|
490
|
+
// 6. 检查当前 vault 是否正在运行
|
|
491
|
+
const isVaultRunning = await _isVaultRunning(targetDir);
|
|
492
|
+
if (isVaultRunning) {
|
|
493
|
+
Ec.waiting('检测到该 vault 正在运行,正在关闭以重新加载...');
|
|
494
|
+
await _closeVault(targetDir);
|
|
459
495
|
}
|
|
460
496
|
|
|
461
497
|
// 7. 注册 vault 到 Obsidian 配置
|