@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.
Files changed (77) hide show
  1. package/build.js +205 -0
  2. package/dist/assets/1024x1024.png +0 -0
  3. package/dist/assets/128x128.png +0 -0
  4. package/dist/assets/144x144.png +0 -0
  5. package/dist/assets/192x192.png +0 -0
  6. package/dist/assets/48x48.png +0 -0
  7. package/dist/assets/512x512.png +0 -0
  8. package/dist/assets/72x72.png +0 -0
  9. package/dist/assets/96x96.png +0 -0
  10. package/dist/assets/active_cursor.png +0 -0
  11. package/dist/assets/favicon.svg +6 -0
  12. package/dist/assets/old/144x144.png +0 -0
  13. package/dist/assets/old/192x192.png +0 -0
  14. package/dist/assets/old/48x48.png +0 -0
  15. package/dist/assets/old/48x48_faint.png +0 -0
  16. package/dist/assets/old/512x512.png +0 -0
  17. package/dist/assets/old/72x72.png +0 -0
  18. package/dist/assets/old/96x96.png +0 -0
  19. package/dist/auth.js +373 -0
  20. package/dist/content.css +46 -0
  21. package/dist/content.js +1171 -0
  22. package/dist/content.js.map +7 -0
  23. package/dist/devtools.html +7 -0
  24. package/dist/devtools.js +5 -0
  25. package/dist/manifest.json +87 -0
  26. package/dist/page-agent.js +727 -0
  27. package/dist/panel.css +2239 -0
  28. package/dist/panel.html +235 -0
  29. package/dist/panel.js +4973 -0
  30. package/dist/picker.html +111 -0
  31. package/dist/picker.js +300 -0
  32. package/dist/service_worker.js +219 -0
  33. package/dist/service_worker.js.map +7 -0
  34. package/dist/settings.css +128 -0
  35. package/dist/settings.html +26 -0
  36. package/dist/settings_ui.js +57 -0
  37. package/dist/settings_ui.js.map +7 -0
  38. package/package.json +20 -0
  39. package/src/content.js +104 -0
  40. package/src/grabber/clean.js +605 -0
  41. package/src/grabber/computed.js +78 -0
  42. package/src/grabber/parse.js +268 -0
  43. package/src/grabber/stylesheets.js +117 -0
  44. package/src/grabber/utils.js +238 -0
  45. package/src/service_worker.js +246 -0
  46. package/src/settings/settings_ui.js +52 -0
  47. package/src/settings/settings_utils.js +70 -0
  48. package/static/assets/1024x1024.png +0 -0
  49. package/static/assets/128x128.png +0 -0
  50. package/static/assets/144x144.png +0 -0
  51. package/static/assets/192x192.png +0 -0
  52. package/static/assets/48x48.png +0 -0
  53. package/static/assets/512x512.png +0 -0
  54. package/static/assets/72x72.png +0 -0
  55. package/static/assets/96x96.png +0 -0
  56. package/static/assets/active_cursor.png +0 -0
  57. package/static/assets/favicon.svg +6 -0
  58. package/static/assets/old/144x144.png +0 -0
  59. package/static/assets/old/192x192.png +0 -0
  60. package/static/assets/old/48x48.png +0 -0
  61. package/static/assets/old/48x48_faint.png +0 -0
  62. package/static/assets/old/512x512.png +0 -0
  63. package/static/assets/old/72x72.png +0 -0
  64. package/static/assets/old/96x96.png +0 -0
  65. package/static/auth.js +373 -0
  66. package/static/content.css +46 -0
  67. package/static/devtools.html +7 -0
  68. package/static/devtools.js +5 -0
  69. package/static/manifest.json +56 -0
  70. package/static/page-agent.js +727 -0
  71. package/static/panel.css +2239 -0
  72. package/static/panel.html +235 -0
  73. package/static/panel.js +4973 -0
  74. package/static/picker.html +111 -0
  75. package/static/picker.js +300 -0
  76. package/static/settings.css +128 -0
  77. 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>
@@ -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>