@symbo.ls/connect 3.2.7
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/build.js +205 -0
- package/dist/assets/1024x1024.png +0 -0
- package/dist/assets/128x128.png +0 -0
- package/dist/assets/144x144.png +0 -0
- package/dist/assets/192x192.png +0 -0
- package/dist/assets/48x48.png +0 -0
- package/dist/assets/512x512.png +0 -0
- package/dist/assets/72x72.png +0 -0
- package/dist/assets/96x96.png +0 -0
- package/dist/assets/active_cursor.png +0 -0
- package/dist/assets/favicon.svg +6 -0
- package/dist/assets/old/144x144.png +0 -0
- package/dist/assets/old/192x192.png +0 -0
- package/dist/assets/old/48x48.png +0 -0
- package/dist/assets/old/48x48_faint.png +0 -0
- package/dist/assets/old/512x512.png +0 -0
- package/dist/assets/old/72x72.png +0 -0
- package/dist/assets/old/96x96.png +0 -0
- package/dist/auth.js +373 -0
- package/dist/content.css +46 -0
- package/dist/content.js +1171 -0
- package/dist/content.js.map +7 -0
- package/dist/devtools.html +7 -0
- package/dist/devtools.js +5 -0
- package/dist/manifest.json +87 -0
- package/dist/page-agent.js +727 -0
- package/dist/panel.css +2239 -0
- package/dist/panel.html +235 -0
- package/dist/panel.js +4973 -0
- package/dist/picker.html +111 -0
- package/dist/picker.js +300 -0
- package/dist/service_worker.js +219 -0
- package/dist/service_worker.js.map +7 -0
- package/dist/settings.css +128 -0
- package/dist/settings.html +26 -0
- package/dist/settings_ui.js +57 -0
- package/dist/settings_ui.js.map +7 -0
- package/package.json +20 -0
- package/src/content.js +104 -0
- package/src/grabber/clean.js +605 -0
- package/src/grabber/computed.js +78 -0
- package/src/grabber/parse.js +268 -0
- package/src/grabber/stylesheets.js +117 -0
- package/src/grabber/utils.js +238 -0
- package/src/service_worker.js +246 -0
- package/src/settings/settings_ui.js +52 -0
- package/src/settings/settings_utils.js +70 -0
- package/static/assets/1024x1024.png +0 -0
- package/static/assets/128x128.png +0 -0
- package/static/assets/144x144.png +0 -0
- package/static/assets/192x192.png +0 -0
- package/static/assets/48x48.png +0 -0
- package/static/assets/512x512.png +0 -0
- package/static/assets/72x72.png +0 -0
- package/static/assets/96x96.png +0 -0
- package/static/assets/active_cursor.png +0 -0
- package/static/assets/favicon.svg +6 -0
- package/static/assets/old/144x144.png +0 -0
- package/static/assets/old/192x192.png +0 -0
- package/static/assets/old/48x48.png +0 -0
- package/static/assets/old/48x48_faint.png +0 -0
- package/static/assets/old/512x512.png +0 -0
- package/static/assets/old/72x72.png +0 -0
- package/static/assets/old/96x96.png +0 -0
- package/static/auth.js +373 -0
- package/static/content.css +46 -0
- package/static/devtools.html +7 -0
- package/static/devtools.js +5 -0
- package/static/manifest.json +56 -0
- package/static/page-agent.js +727 -0
- package/static/panel.css +2239 -0
- package/static/panel.html +235 -0
- package/static/panel.js +4973 -0
- package/static/picker.html +111 -0
- package/static/picker.js +300 -0
- package/static/settings.css +128 -0
- package/static/settings.html +26 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Symbols Connect — Select Folder</title>
|
|
6
|
+
<link rel="icon" href="assets/favicon.svg" />
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #141416;
|
|
11
|
+
--bg-alt: #242428;
|
|
12
|
+
--bg-hover: #34343a;
|
|
13
|
+
--border: #34343a;
|
|
14
|
+
--text: #bcbcc2;
|
|
15
|
+
--text-dim: #65656f;
|
|
16
|
+
--text-bright: #e0e0e2;
|
|
17
|
+
--accent: #0085FF;
|
|
18
|
+
}
|
|
19
|
+
body {
|
|
20
|
+
background: var(--bg);
|
|
21
|
+
color: var(--text);
|
|
22
|
+
font-family: 'DmSansVariable', 'Helvetica Neue', 'Helvetica', system-ui, sans-serif;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
height: 100vh;
|
|
27
|
+
}
|
|
28
|
+
.picker-inner {
|
|
29
|
+
text-align: center;
|
|
30
|
+
max-width: 400px;
|
|
31
|
+
padding: 40px;
|
|
32
|
+
}
|
|
33
|
+
.picker-logo {
|
|
34
|
+
width: 48px;
|
|
35
|
+
height: 48px;
|
|
36
|
+
margin: 0 auto 16px;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
}
|
|
41
|
+
.picker-logo svg { width: 48px; height: 48px; fill: var(--accent); }
|
|
42
|
+
h1 {
|
|
43
|
+
font-size: 18px;
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
color: var(--text-bright);
|
|
46
|
+
margin-bottom: 8px;
|
|
47
|
+
}
|
|
48
|
+
p {
|
|
49
|
+
font-size: 13px;
|
|
50
|
+
color: var(--text-dim);
|
|
51
|
+
margin-bottom: 24px;
|
|
52
|
+
line-height: 1.4;
|
|
53
|
+
}
|
|
54
|
+
button {
|
|
55
|
+
background: var(--accent);
|
|
56
|
+
color: #fff;
|
|
57
|
+
border: none;
|
|
58
|
+
padding: 12px 32px;
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
font-size: 14px;
|
|
61
|
+
font-family: inherit;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
}
|
|
64
|
+
button:hover { opacity: 0.9; }
|
|
65
|
+
.picker-btn-secondary {
|
|
66
|
+
background: transparent;
|
|
67
|
+
color: var(--text-dim);
|
|
68
|
+
border: 1px solid var(--border);
|
|
69
|
+
padding: 10px 24px;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
font-family: inherit;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
.picker-btn-secondary:hover {
|
|
76
|
+
color: var(--text-bright);
|
|
77
|
+
border-color: var(--accent);
|
|
78
|
+
}
|
|
79
|
+
.picker-success-icon {
|
|
80
|
+
width: 64px;
|
|
81
|
+
height: 64px;
|
|
82
|
+
margin: 0 auto 16px;
|
|
83
|
+
background: #59AC56;
|
|
84
|
+
color: #fff;
|
|
85
|
+
font-size: 36px;
|
|
86
|
+
line-height: 64px;
|
|
87
|
+
text-align: center;
|
|
88
|
+
border-radius: 50%;
|
|
89
|
+
}
|
|
90
|
+
.status {
|
|
91
|
+
margin-top: 16px;
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
color: var(--text-dim);
|
|
94
|
+
}
|
|
95
|
+
.status.error { color: #EB6650; }
|
|
96
|
+
.status.success { color: #59AC56; }
|
|
97
|
+
</style>
|
|
98
|
+
</head>
|
|
99
|
+
<body>
|
|
100
|
+
<div class="picker-inner">
|
|
101
|
+
<div class="picker-logo">
|
|
102
|
+
<svg viewBox="0 0 24 24"><path d="M13.843 2.7C19.063 2.7 23 6.366 23 11.228c0 3.754-2.862 6.584-6.658 6.584-3.287 0-5.007-2.318-5.007-4.609 0-2.395 1.923-4.344 4.287-4.344.566 0 1.023.12 1.309.223a.212.212 0 01.137.229l-.016.058-.514 1.18a.223.223 0 01-.245.13 2.965 2.965 0 00-.506-.046c-1.245 0-2.258 1.027-2.258 2.288 0 1.33 1.165 2.373 2.651 2.373 2.195 0 3.913-1.777 3.913-4.046 0-3.024-2.294-5.135-5.58-5.135-4.076 0-7.393 3.36-7.393 7.491a7.519 7.519 0 002.871 5.924l-4.96 3.18A12.042 12.042 0 012 14.7c0-6.617 5.313-12 11.843-12z" fill-rule="evenodd"/></svg>
|
|
103
|
+
</div>
|
|
104
|
+
<h1>Select Project Folder</h1>
|
|
105
|
+
<p>Choose a local folder to connect. The extension will be able to read and write files in the selected directory.</p>
|
|
106
|
+
<button id="btn-pick">Choose Folder</button>
|
|
107
|
+
<div id="status" class="status"></div>
|
|
108
|
+
</div>
|
|
109
|
+
<script src="picker.js"></script>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
package/static/picker.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
const DB_NAME = 'symbols-connect'
|
|
2
|
+
const DB_STORE = 'handles'
|
|
3
|
+
const FILES_STORE = 'files'
|
|
4
|
+
|
|
5
|
+
function openDB () {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const req = indexedDB.open(DB_NAME, 3)
|
|
8
|
+
req.onupgradeneeded = (e) => {
|
|
9
|
+
const db = e.target.result
|
|
10
|
+
if (!db.objectStoreNames.contains(DB_STORE)) db.createObjectStore(DB_STORE)
|
|
11
|
+
if (!db.objectStoreNames.contains(FILES_STORE)) db.createObjectStore(FILES_STORE)
|
|
12
|
+
if (!db.objectStoreNames.contains('threads')) {
|
|
13
|
+
const store = db.createObjectStore('threads', { keyPath: 'id' })
|
|
14
|
+
store.createIndex('project', 'project', { unique: false })
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
req.onsuccess = () => resolve(req.result)
|
|
18
|
+
req.onerror = () => reject(req.error)
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function saveHandle (name, handle) {
|
|
23
|
+
const db = await openDB()
|
|
24
|
+
const tx = db.transaction(DB_STORE, 'readwrite')
|
|
25
|
+
tx.objectStore(DB_STORE).put({ handle, name, time: Date.now() }, name)
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
tx.oncomplete = resolve
|
|
28
|
+
tx.onerror = () => reject(tx.error)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function saveFileCache (projectName, cache) {
|
|
33
|
+
const db = await openDB()
|
|
34
|
+
const tx = db.transaction(FILES_STORE, 'readwrite')
|
|
35
|
+
tx.objectStore(FILES_STORE).put(cache, projectName)
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
tx.oncomplete = resolve
|
|
38
|
+
tx.onerror = () => reject(tx.error)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Scan a directory and read all file contents
|
|
43
|
+
async function scanAndCache (dirHandle, basePath, depth) {
|
|
44
|
+
if (depth > 6) return { tree: [], files: {} }
|
|
45
|
+
const tree = []
|
|
46
|
+
const files = {}
|
|
47
|
+
|
|
48
|
+
for await (const entry of dirHandle.values()) {
|
|
49
|
+
const entryPath = basePath ? basePath + '/' + entry.name : entry.name
|
|
50
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') continue
|
|
51
|
+
|
|
52
|
+
if (entry.kind === 'directory') {
|
|
53
|
+
const sub = await scanAndCache(entry, entryPath, depth + 1)
|
|
54
|
+
tree.push({ name: entry.name, path: entryPath, kind: 'dir', children: sub.tree })
|
|
55
|
+
Object.assign(files, sub.files)
|
|
56
|
+
} else {
|
|
57
|
+
if (/\.(js|jsx|ts|tsx|json|css|html)$/i.test(entry.name)) {
|
|
58
|
+
tree.push({ name: entry.name, path: entryPath, kind: 'file' })
|
|
59
|
+
try {
|
|
60
|
+
const file = await entry.getFile()
|
|
61
|
+
files[entryPath] = await file.text()
|
|
62
|
+
} catch (e) {
|
|
63
|
+
files[entryPath] = null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
tree.sort((a, b) => {
|
|
70
|
+
if (a.kind !== b.kind) return a.kind === 'dir' ? -1 : 1
|
|
71
|
+
return a.name.localeCompare(b.name)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return { tree, files }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const statusEl = document.getElementById('status')
|
|
78
|
+
const innerEl = document.querySelector('.picker-inner')
|
|
79
|
+
|
|
80
|
+
function showSuccess (name) {
|
|
81
|
+
innerEl.innerHTML = ''
|
|
82
|
+
|
|
83
|
+
const checkmark = document.createElement('div')
|
|
84
|
+
checkmark.className = 'picker-success-icon'
|
|
85
|
+
checkmark.textContent = '\u2713'
|
|
86
|
+
|
|
87
|
+
const title = document.createElement('h1')
|
|
88
|
+
title.textContent = 'Connected'
|
|
89
|
+
title.style.marginBottom = '8px'
|
|
90
|
+
|
|
91
|
+
const nameEl = document.createElement('p')
|
|
92
|
+
nameEl.style.cssText = 'color:#59AC56;font-size:16px;font-weight:500;margin-bottom:8px'
|
|
93
|
+
nameEl.textContent = name
|
|
94
|
+
|
|
95
|
+
const hint = document.createElement('p')
|
|
96
|
+
hint.textContent = 'You can close this tab and return to DevTools.'
|
|
97
|
+
hint.style.marginBottom = '24px'
|
|
98
|
+
|
|
99
|
+
const anotherBtn = document.createElement('button')
|
|
100
|
+
anotherBtn.textContent = 'Choose Another Folder'
|
|
101
|
+
anotherBtn.className = 'picker-btn-secondary'
|
|
102
|
+
anotherBtn.addEventListener('click', () => location.reload())
|
|
103
|
+
|
|
104
|
+
innerEl.appendChild(checkmark)
|
|
105
|
+
innerEl.appendChild(title)
|
|
106
|
+
innerEl.appendChild(nameEl)
|
|
107
|
+
innerEl.appendChild(hint)
|
|
108
|
+
innerEl.appendChild(anotherBtn)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function pickFolder () {
|
|
112
|
+
try {
|
|
113
|
+
const handle = await window.showDirectoryPicker({ mode: 'readwrite' })
|
|
114
|
+
|
|
115
|
+
// Validate symbols.json
|
|
116
|
+
let configHandle
|
|
117
|
+
try {
|
|
118
|
+
configHandle = await handle.getFileHandle('symbols.json')
|
|
119
|
+
} catch (e) {
|
|
120
|
+
statusEl.textContent = 'No symbols.json found. Please select a Symbols project folder.'
|
|
121
|
+
statusEl.className = 'status error'
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
statusEl.textContent = 'Reading symbols.json...'
|
|
126
|
+
statusEl.className = 'status'
|
|
127
|
+
|
|
128
|
+
const configFile = await configHandle.getFile()
|
|
129
|
+
const configText = await configFile.text()
|
|
130
|
+
let config
|
|
131
|
+
try {
|
|
132
|
+
config = JSON.parse(configText)
|
|
133
|
+
} catch (e) {
|
|
134
|
+
statusEl.textContent = 'Invalid symbols.json: ' + e.message
|
|
135
|
+
statusEl.className = 'status error'
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Resolve symbols directory
|
|
140
|
+
let symbolsDir = (config.dir || './symbols').replace(/^\.\//, '')
|
|
141
|
+
let symbolsDirHandle
|
|
142
|
+
try {
|
|
143
|
+
symbolsDirHandle = await handle.getDirectoryHandle(symbolsDir)
|
|
144
|
+
} catch (e) {
|
|
145
|
+
statusEl.textContent = 'Symbols directory "' + symbolsDir + '" not found.'
|
|
146
|
+
statusEl.className = 'status error'
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
statusEl.textContent = 'Scanning symbols folder...'
|
|
151
|
+
|
|
152
|
+
// Scan and cache all file contents
|
|
153
|
+
const { tree, files } = await scanAndCache(symbolsDirHandle, '', 0)
|
|
154
|
+
|
|
155
|
+
statusEl.textContent = 'Saving...'
|
|
156
|
+
|
|
157
|
+
// Save handle and file cache
|
|
158
|
+
await saveHandle(handle.name, handle)
|
|
159
|
+
await saveFileCache(handle.name, {
|
|
160
|
+
config,
|
|
161
|
+
symbolsDir,
|
|
162
|
+
tree,
|
|
163
|
+
files,
|
|
164
|
+
time: Date.now()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
showSuccess(handle.name)
|
|
168
|
+
|
|
169
|
+
// Notify the devtools panel
|
|
170
|
+
chrome.runtime.sendMessage({
|
|
171
|
+
type: 'folder-picked',
|
|
172
|
+
name: handle.name
|
|
173
|
+
})
|
|
174
|
+
} catch (e) {
|
|
175
|
+
if (e.name !== 'AbortError') {
|
|
176
|
+
statusEl.textContent = 'Error: ' + e.message
|
|
177
|
+
statusEl.className = 'status error'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Listen for file operation requests from the panel
|
|
183
|
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
184
|
+
if (msg.type === 'read-file') {
|
|
185
|
+
(async () => {
|
|
186
|
+
try {
|
|
187
|
+
const db = await openDB()
|
|
188
|
+
const tx = db.transaction(FILES_STORE, 'readonly')
|
|
189
|
+
const req = tx.objectStore(FILES_STORE).get(msg.project)
|
|
190
|
+
req.onsuccess = () => {
|
|
191
|
+
const cache = req.result
|
|
192
|
+
if (cache && cache.files && msg.path in cache.files) {
|
|
193
|
+
sendResponse({ content: cache.files[msg.path] })
|
|
194
|
+
} else {
|
|
195
|
+
sendResponse({ error: 'File not in cache' })
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
req.onerror = () => sendResponse({ error: 'DB error' })
|
|
199
|
+
} catch (e) {
|
|
200
|
+
sendResponse({ error: e.message })
|
|
201
|
+
}
|
|
202
|
+
})()
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (msg.type === 'write-file') {
|
|
207
|
+
(async () => {
|
|
208
|
+
try {
|
|
209
|
+
const db = await openDB()
|
|
210
|
+
const tx = db.transaction(DB_STORE, 'readonly')
|
|
211
|
+
const req = tx.objectStore(DB_STORE).get(msg.project)
|
|
212
|
+
req.onsuccess = async () => {
|
|
213
|
+
try {
|
|
214
|
+
const item = req.result
|
|
215
|
+
if (!item || !item.handle) {
|
|
216
|
+
sendResponse({ error: 'Project handle not found' })
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let symbolsDir = msg.symbolsDir || 'symbols'
|
|
221
|
+
let dirHandle = await item.handle.getDirectoryHandle(symbolsDir)
|
|
222
|
+
|
|
223
|
+
// Navigate to file
|
|
224
|
+
const parts = msg.path.split('/')
|
|
225
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
226
|
+
dirHandle = await dirHandle.getDirectoryHandle(parts[i])
|
|
227
|
+
}
|
|
228
|
+
const fileHandle = await dirHandle.getFileHandle(parts[parts.length - 1])
|
|
229
|
+
const writable = await fileHandle.createWritable()
|
|
230
|
+
await writable.write(msg.content)
|
|
231
|
+
await writable.close()
|
|
232
|
+
|
|
233
|
+
// Update cache
|
|
234
|
+
const db2 = await openDB()
|
|
235
|
+
const tx2 = db2.transaction(FILES_STORE, 'readwrite')
|
|
236
|
+
const req2 = tx2.objectStore(FILES_STORE).get(msg.project)
|
|
237
|
+
req2.onsuccess = () => {
|
|
238
|
+
const cache = req2.result
|
|
239
|
+
if (cache) {
|
|
240
|
+
cache.files[msg.path] = msg.content
|
|
241
|
+
tx2.objectStore(FILES_STORE).put(cache, msg.project)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
sendResponse({ success: true })
|
|
246
|
+
} catch (e) {
|
|
247
|
+
sendResponse({ error: e.message })
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
req.onerror = () => sendResponse({ error: 'DB error' })
|
|
251
|
+
} catch (e) {
|
|
252
|
+
sendResponse({ error: e.message })
|
|
253
|
+
}
|
|
254
|
+
})()
|
|
255
|
+
return true
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (msg.type === 'rescan-project') {
|
|
259
|
+
(async () => {
|
|
260
|
+
try {
|
|
261
|
+
const db = await openDB()
|
|
262
|
+
const tx = db.transaction(DB_STORE, 'readonly')
|
|
263
|
+
const req = tx.objectStore(DB_STORE).get(msg.project)
|
|
264
|
+
req.onsuccess = async () => {
|
|
265
|
+
try {
|
|
266
|
+
const item = req.result
|
|
267
|
+
if (!item || !item.handle) {
|
|
268
|
+
sendResponse({ error: 'No handle' })
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const perm = await item.handle.queryPermission({ mode: 'readwrite' })
|
|
273
|
+
if (perm !== 'granted') {
|
|
274
|
+
sendResponse({ error: 'Permission denied. Re-open folder.' })
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let configHandle = await item.handle.getFileHandle('symbols.json')
|
|
279
|
+
const configFile = await configHandle.getFile()
|
|
280
|
+
const config = JSON.parse(await configFile.text())
|
|
281
|
+
|
|
282
|
+
let symbolsDir = (config.dir || './symbols').replace(/^\.\//, '')
|
|
283
|
+
let symbolsDirHandle = await item.handle.getDirectoryHandle(symbolsDir)
|
|
284
|
+
const { tree, files } = await scanAndCache(symbolsDirHandle, '', 0)
|
|
285
|
+
|
|
286
|
+
await saveFileCache(msg.project, { config, symbolsDir, tree, files, time: Date.now() })
|
|
287
|
+
sendResponse({ success: true, config, symbolsDir, tree })
|
|
288
|
+
} catch (e) {
|
|
289
|
+
sendResponse({ error: e.message })
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
sendResponse({ error: e.message })
|
|
294
|
+
}
|
|
295
|
+
})()
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
document.getElementById('btn-pick').addEventListener('click', pickFolder)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
--bg: #141416;
|
|
9
|
+
--bg-alt: #242428;
|
|
10
|
+
--bg-hover: #34343a;
|
|
11
|
+
--border: #34343a;
|
|
12
|
+
--text: #bcbcc2;
|
|
13
|
+
--text-dim: #65656f;
|
|
14
|
+
--text-bright: #e0e0e2;
|
|
15
|
+
--accent: #0085FF;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
background: var(--bg);
|
|
20
|
+
color: var(--text);
|
|
21
|
+
font-family: 'DmSansVariable', 'Helvetica Neue', 'Helvetica', system-ui, sans-serif;
|
|
22
|
+
font-size: 14px;
|
|
23
|
+
line-height: 1.5;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.main-container {
|
|
27
|
+
margin: 0 auto;
|
|
28
|
+
max-width: 700px;
|
|
29
|
+
padding: 40px 24px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
header {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: 12px;
|
|
36
|
+
margin-bottom: 8px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.logo {
|
|
40
|
+
width: 32px;
|
|
41
|
+
height: 32px;
|
|
42
|
+
fill: var(--accent);
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
h1 {
|
|
47
|
+
font-size: 20px;
|
|
48
|
+
font-weight: 600;
|
|
49
|
+
color: var(--text-bright);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
section p {
|
|
53
|
+
color: var(--text-dim);
|
|
54
|
+
font-size: 13px;
|
|
55
|
+
margin-bottom: 24px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.separator {
|
|
59
|
+
margin: 0 0 24px;
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 1px;
|
|
62
|
+
background: var(--border);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#settings-container {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
gap: 12px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.setting {
|
|
72
|
+
position: relative;
|
|
73
|
+
background: var(--bg-alt);
|
|
74
|
+
border: 1px solid var(--border);
|
|
75
|
+
border-radius: 10px;
|
|
76
|
+
transition: border-color 0.15s;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.setting:hover {
|
|
80
|
+
border-color: var(--accent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.setting label {
|
|
84
|
+
color: var(--text-bright);
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: space-between;
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
line-height: 1;
|
|
91
|
+
padding: 16px 20px;
|
|
92
|
+
border-radius: 10px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.setting input[type='checkbox'] {
|
|
96
|
+
appearance: none;
|
|
97
|
+
-webkit-appearance: none;
|
|
98
|
+
width: 40px;
|
|
99
|
+
height: 22px;
|
|
100
|
+
background: var(--bg-hover);
|
|
101
|
+
border-radius: 11px;
|
|
102
|
+
position: relative;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
flex-shrink: 0;
|
|
105
|
+
margin: 0;
|
|
106
|
+
transition: background 0.2s;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.setting input[type='checkbox']::after {
|
|
110
|
+
content: '';
|
|
111
|
+
position: absolute;
|
|
112
|
+
top: 3px;
|
|
113
|
+
left: 3px;
|
|
114
|
+
width: 16px;
|
|
115
|
+
height: 16px;
|
|
116
|
+
background: var(--text-dim);
|
|
117
|
+
border-radius: 50%;
|
|
118
|
+
transition: transform 0.2s, background 0.2s;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.setting input[type='checkbox']:checked {
|
|
122
|
+
background: var(--accent);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.setting input[type='checkbox']:checked::after {
|
|
126
|
+
transform: translateX(18px);
|
|
127
|
+
background: #fff;
|
|
128
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Symbols Connect — Settings</title>
|
|
7
|
+
<link rel="icon" href="assets/favicon.svg" />
|
|
8
|
+
<link rel="stylesheet" href="settings.css" />
|
|
9
|
+
<script type="module" src="settings_ui.js" defer></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div class="main-container">
|
|
13
|
+
<header>
|
|
14
|
+
<svg class="logo" viewBox="0 0 24 24"><path d="M13.843 2.7C19.063 2.7 23 6.366 23 11.228c0 3.754-2.862 6.584-6.658 6.584-3.287 0-5.007-2.318-5.007-4.609 0-2.395 1.923-4.344 4.287-4.344.566 0 1.023.12 1.309.223a.212.212 0 01.137.229l-.016.058-.514 1.18a.223.223 0 01-.245.13 2.965 2.965 0 00-.506-.046c-1.245 0-2.258 1.027-2.258 2.288 0 1.33 1.165 2.373 2.651 2.373 2.195 0 3.913-1.777 3.913-4.046 0-3.024-2.294-5.135-5.58-5.135-4.076 0-7.393 3.36-7.393 7.491a7.519 7.519 0 002.871 5.924l-4.96 3.18A12.042 12.042 0 012 14.7c0-6.617 5.313-12 11.843-12z" fill-rule="evenodd"/></svg>
|
|
15
|
+
<h1>Symbols Connect — Settings</h1>
|
|
16
|
+
</header>
|
|
17
|
+
<main>
|
|
18
|
+
<section>
|
|
19
|
+
<p>Adjust preferences for the Symbols extension. Changes take effect immediately.</p>
|
|
20
|
+
<div class="separator"></div>
|
|
21
|
+
<div id="settings-container"></div>
|
|
22
|
+
</section>
|
|
23
|
+
</main>
|
|
24
|
+
</div>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|