synthos 0.10.1 → 0.11.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/README.md +5 -5
- package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
- package/default-pages/elevenlabs_effects_studio/page.json +13 -11
- package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_voice_studio/page.html +782 -801
- package/default-pages/elevenlabs_voice_studio/page.json +13 -11
- package/default-pages/json_tools/chat-history.json +1 -0
- package/default-pages/json_tools/page.html +70 -90
- package/default-pages/json_tools/page.json +12 -10
- package/default-pages/my_notes/chat-history.json +1 -0
- package/default-pages/my_notes/page.html +115 -131
- package/default-pages/my_notes/page.json +14 -12
- package/default-pages/neon_asteroids/chat-history.json +1 -0
- package/default-pages/neon_asteroids/page.html +1777 -1803
- package/default-pages/neon_asteroids/page.json +14 -12
- package/default-pages/oregon_trail/chat-history.json +1 -0
- package/default-pages/oregon_trail/page.html +290 -307
- package/default-pages/oregon_trail/page.json +14 -12
- package/default-pages/solar_explorer/chat-history.json +1 -0
- package/default-pages/solar_explorer/page.html +1929 -1951
- package/default-pages/solar_explorer/page.json +14 -12
- package/default-pages/solar_tutorial/chat-history.json +1 -0
- package/default-pages/solar_tutorial/page.html +464 -478
- package/default-pages/solar_tutorial/page.json +12 -10
- package/default-pages/us_map/chat-history.json +1 -0
- package/default-pages/us_map/page.html +170 -193
- package/default-pages/us_map/page.json +14 -12
- package/default-pages/us_map/page.light.png +0 -0
- package/default-pages/us_map_1850/chat-history.json +1 -0
- package/default-pages/us_map_1850/page.html +302 -326
- package/default-pages/us_map_1850/page.json +14 -12
- package/default-pages/western_cities_1850/chat-history.json +1 -0
- package/default-pages/western_cities_1850/page.html +503 -527
- package/default-pages/western_cities_1850/page.json +14 -12
- package/default-themes/aurora-dawn.v3.css +15 -14
- package/default-themes/aurora-dusk.v3.css +26 -26
- package/default-themes/cosmos-dawn.v3.css +15 -14
- package/default-themes/cosmos-dusk.v3.css +26 -26
- package/default-themes/elemental-dawn.v3.css +200 -0
- package/default-themes/nebula-dawn.v3.css +15 -14
- package/default-themes/nebula-dusk.v3.css +24 -24
- package/default-themes/solar-flare-dawn.v3.css +15 -14
- package/default-themes/solar-flare-dusk.v3.css +26 -26
- package/dist/builders/anthropic.d.ts +26 -2
- package/dist/builders/anthropic.d.ts.map +1 -1
- package/dist/builders/anthropic.js +132 -31
- package/dist/builders/anthropic.js.map +1 -1
- package/dist/builders/claudecode.d.ts +13 -0
- package/dist/builders/claudecode.d.ts.map +1 -0
- package/dist/builders/claudecode.js +253 -0
- package/dist/builders/claudecode.js.map +1 -0
- package/dist/builders/index.d.ts +2 -1
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +8 -1
- package/dist/builders/index.js.map +1 -1
- package/dist/builders/openai.js +2 -1
- package/dist/builders/openai.js.map +1 -1
- package/dist/builders/types.d.ts +31 -7
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +60 -28
- package/dist/builders/types.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +13 -6
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +161 -14
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +1 -0
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +129 -29
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/chainOfThought.d.ts.map +1 -1
- package/dist/models/chainOfThought.js +32 -19
- package/dist/models/chainOfThought.js.map +1 -1
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/providers.d.ts +1 -0
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +12 -4
- package/dist/models/providers.js.map +1 -1
- package/dist/models/types.d.ts +15 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +57 -8
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +258 -45
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +5 -0
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/mediaCache.d.ts +36 -0
- package/dist/service/mediaCache.d.ts.map +1 -0
- package/dist/service/mediaCache.js +182 -0
- package/dist/service/mediaCache.js.map +1 -0
- package/dist/service/pageValidator.d.ts +25 -0
- package/dist/service/pageValidator.d.ts.map +1 -0
- package/dist/service/pageValidator.js +315 -0
- package/dist/service/pageValidator.js.map +1 -0
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +4 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/sharedTableSchema.d.ts +73 -0
- package/dist/service/sharedTableSchema.d.ts.map +1 -0
- package/dist/service/sharedTableSchema.js +206 -0
- package/dist/service/sharedTableSchema.js.map +1 -0
- package/dist/service/transformPage.d.ts +49 -11
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +354 -241
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +288 -34
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +170 -32
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +59 -2
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useExtractRoutes.d.ts +4 -0
- package/dist/service/useExtractRoutes.d.ts.map +1 -0
- package/dist/service/useExtractRoutes.js +304 -0
- package/dist/service/useExtractRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +17 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +1385 -483
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
- package/dist/service/useSharedDataRoutes.js +54 -2
- package/dist/service/useSharedDataRoutes.js.map +1 -1
- package/dist/settings.d.ts +27 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +40 -1
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +0 -5
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +3 -95
- package/dist/themes.js.map +1 -1
- package/migration-rules/v2-to-v3.md +277 -119
- package/package.json +5 -1
- package/{default-pages/application → required-pages/_shell}/page.html +56 -42
- package/required-pages/_shell/page.json +14 -0
- package/required-pages/_starters/page.html +534 -0
- package/required-pages/_starters/page.json +12 -0
- package/required-pages/builder/page.html +353 -43
- package/required-pages/builder/page.json +12 -10
- package/required-pages/pages/page.html +697 -924
- package/required-pages/pages/page.json +12 -10
- package/required-pages/settings/page.html +1879 -1753
- package/required-pages/settings/page.json +12 -10
- package/required-pages/synthos_apis/page.html +834 -845
- package/required-pages/synthos_apis/page.json +12 -10
- package/required-pages/synthos_scripts/page.html +74 -88
- package/required-pages/synthos_scripts/page.json +12 -10
- package/scripts/append-instructions.py +90 -0
- package/scripts/audit-instructions.py +76 -0
- package/scripts/cleanup-shell-markup.mjs +112 -0
- package/service-connectors/buffer/connector.json +46 -0
- package/service-connectors/canva/connector.json +67 -0
- package/service-connectors/elevenlabs/connector.json +1 -1
- package/src/builders/anthropic.ts +150 -25
- package/src/builders/claudecode.ts +310 -0
- package/src/builders/index.ts +7 -1
- package/src/builders/openai.ts +2 -1
- package/src/builders/types.ts +93 -32
- package/src/connectors/types.ts +8 -0
- package/src/init.ts +13 -7
- package/src/migrations.ts +187 -16
- package/src/models/anthropic.ts +140 -30
- package/src/models/chainOfThought.ts +33 -18
- package/src/models/index.ts +2 -2
- package/src/models/providers.ts +10 -1
- package/src/models/types.ts +21 -1
- package/src/pages.ts +271 -35
- package/src/service/createCompletePrompt.ts +6 -0
- package/src/service/mediaCache.ts +206 -0
- package/src/service/pageValidator.ts +337 -0
- package/src/service/server.ts +4 -0
- package/src/service/sharedTableSchema.ts +236 -0
- package/src/service/transformPage.ts +370 -260
- package/src/service/useApiRoutes.ts +282 -32
- package/src/service/useConnectorRoutes.ts +189 -34
- package/src/service/useDataRoutes.ts +198 -116
- package/src/service/useExtractRoutes.ts +331 -0
- package/src/service/usePageRoutes.ts +1411 -394
- package/src/service/useSharedDataRoutes.ts +184 -109
- package/src/settings.ts +65 -0
- package/src/themes.ts +78 -180
- package/starters/blank_starter/chat-history.json +1 -0
- package/starters/blank_starter/page.dark.png +0 -0
- package/starters/blank_starter/page.html +47 -0
- package/starters/blank_starter/page.json +13 -0
- package/starters/blank_starter/page.light.png +0 -0
- package/starters/calculator_starter/chat-history.json +1 -0
- package/starters/calculator_starter/page.dark.png +0 -0
- package/starters/calculator_starter/page.html +232 -0
- package/starters/calculator_starter/page.json +13 -0
- package/starters/calculator_starter/page.light.png +0 -0
- package/starters/calendar_starter/chat-history.json +1 -0
- package/starters/calendar_starter/page.dark.png +0 -0
- package/starters/calendar_starter/page.html +495 -0
- package/starters/calendar_starter/page.json +13 -0
- package/starters/calendar_starter/page.light.png +0 -0
- package/starters/chat_starter/chat-history.json +1 -0
- package/starters/chat_starter/page.dark.png +0 -0
- package/starters/chat_starter/page.html +351 -0
- package/starters/chat_starter/page.json +13 -0
- package/starters/chat_starter/page.light.png +0 -0
- package/starters/checklist_starter/chat-history.json +1 -0
- package/starters/checklist_starter/page.dark.png +0 -0
- package/starters/checklist_starter/page.html +437 -0
- package/starters/checklist_starter/page.json +13 -0
- package/starters/checklist_starter/page.light.png +0 -0
- package/starters/dashboard_starter/chat-history.json +1 -0
- package/starters/dashboard_starter/page.dark.png +0 -0
- package/starters/dashboard_starter/page.html +195 -0
- package/starters/dashboard_starter/page.json +13 -0
- package/starters/dashboard_starter/page.light.png +0 -0
- package/starters/form_starter/chat-history.json +1 -0
- package/starters/form_starter/page.dark.png +0 -0
- package/starters/form_starter/page.html +313 -0
- package/starters/form_starter/page.json +13 -0
- package/starters/form_starter/page.light.png +0 -0
- package/starters/gallery_starter/chat-history.json +1 -0
- package/starters/gallery_starter/page.dark.png +0 -0
- package/starters/gallery_starter/page.html +418 -0
- package/starters/gallery_starter/page.json +13 -0
- package/starters/gallery_starter/page.light.png +0 -0
- package/starters/generator_starter/chat-history.json +1 -0
- package/starters/generator_starter/page.dark.png +0 -0
- package/starters/generator_starter/page.html +261 -0
- package/starters/generator_starter/page.json +13 -0
- package/starters/generator_starter/page.light.png +0 -0
- package/starters/index.html +538 -0
- package/starters/kanban_starter/chat-history.json +1 -0
- package/starters/kanban_starter/page.dark.png +0 -0
- package/starters/kanban_starter/page.html +432 -0
- package/starters/kanban_starter/page.json +13 -0
- package/starters/kanban_starter/page.light.png +0 -0
- package/starters/presentation_builder/chat-history.json +1 -0
- package/starters/presentation_builder/page.dark.png +0 -0
- package/starters/presentation_builder/page.html +970 -0
- package/starters/presentation_builder/page.json +15 -0
- package/starters/presentation_builder/page.light.png +0 -0
- package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
- package/starters/pulse_starter/chat-history.json +1 -0
- package/starters/pulse_starter/page.dark.png +0 -0
- package/starters/pulse_starter/page.html +698 -0
- package/starters/pulse_starter/page.json +13 -0
- package/starters/pulse_starter/page.light.png +0 -0
- package/starters/quiz_starter/chat-history.json +1 -0
- package/starters/quiz_starter/page.dark.png +0 -0
- package/starters/quiz_starter/page.html +292 -0
- package/starters/quiz_starter/page.json +13 -0
- package/starters/quiz_starter/page.light.png +0 -0
- package/starters/reference_starter/chat-history.json +1 -0
- package/starters/reference_starter/page.dark.png +0 -0
- package/starters/reference_starter/page.html +250 -0
- package/starters/reference_starter/page.json +13 -0
- package/starters/reference_starter/page.light.png +0 -0
- package/starters/retro_game_starter/chat-history.json +1 -0
- package/starters/retro_game_starter/page.dark.png +0 -0
- package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
- package/starters/retro_game_starter/page.json +15 -0
- package/starters/retro_game_starter/page.light.png +0 -0
- package/starters/roster_starter/chat-history.json +1 -0
- package/starters/roster_starter/page.dark.png +0 -0
- package/starters/roster_starter/page.html +600 -0
- package/starters/roster_starter/page.json +13 -0
- package/starters/roster_starter/page.light.png +0 -0
- package/starters/server.js +182 -0
- package/starters/start.cmd +1 -0
- package/starters/timeline_starter/chat-history.json +1 -0
- package/starters/timeline_starter/page.dark.png +0 -0
- package/starters/timeline_starter/page.html +446 -0
- package/starters/timeline_starter/page.json +13 -0
- package/starters/timeline_starter/page.light.png +0 -0
- package/starters/tutorial_starter/chat-history.json +1 -0
- package/starters/tutorial_starter/page.dark.png +0 -0
- package/starters/tutorial_starter/page.html +283 -0
- package/starters/tutorial_starter/page.json +13 -0
- package/starters/tutorial_starter/page.light.png +0 -0
- package/static-files/agent.v3.js +122 -0
- package/static-files/connector.v3.js +48 -0
- package/static-files/extract.v3.js +188 -0
- package/static-files/helpers.v3.js +50 -6
- package/static-files/page-bridge.js +114 -0
- package/static-files/page.v3.js +1292 -1290
- package/static-files/script.v3.js +32 -0
- package/static-files/server.v3.js +89 -0
- package/static-files/shell-bridge.v3.js +174 -0
- package/static-files/shell-modals.v3.js +521 -0
- package/static-files/{shell.css → shell.v3.css} +271 -22
- package/static-files/shell.v3.js +1865 -0
- package/static-files/storage.v3.js +176 -0
- package/tests/anthropic.spec.ts +42 -7
- package/tests/builders.spec.ts +70 -2
- package/tests/pageValidator.spec.ts +548 -0
- package/tests/profiles.spec.ts +122 -0
- package/tests/sharedTableSchema.spec.ts +242 -0
- package/tests/transformPage.spec.ts +62 -81
- package/default-pages/application/page.json +0 -10
- package/default-pages/retro_game_starter/page.json +0 -12
- package/default-pages/sidebar_page/page.html +0 -51
- package/default-pages/sidebar_page/page.json +0 -10
- package/default-pages/two-panel_page/page.html +0 -68
- package/default-pages/two-panel_page/page.json +0 -10
|
@@ -1,924 +1,697 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head>
|
|
2
|
-
<meta charset="UTF-8">
|
|
3
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4
|
-
<title>SynthOS - Pages</title>
|
|
5
|
-
<script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
|
|
6
|
-
<link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
|
|
7
|
-
<style>
|
|
8
|
-
/* Layout-specific rules only — all component styling via FluentLM */
|
|
9
|
-
|
|
10
|
-
.page-
|
|
11
|
-
.page-btn {
|
|
12
|
-
.page-btn
|
|
13
|
-
.page-btn .btn-label {
|
|
14
|
-
.page-btn.scrolling .
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.page-btn.builder-page {
|
|
18
|
-
.page-btn.
|
|
19
|
-
.page-btn.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.
|
|
23
|
-
.toast-fixed {
|
|
24
|
-
.
|
|
25
|
-
.
|
|
26
|
-
.
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
var
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
function
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
await loadPages();
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
mode: newMode
|
|
699
|
-
})
|
|
700
|
-
});
|
|
701
|
-
if (!res.ok) throw new Error('Failed to update page');
|
|
702
|
-
closeEditModal();
|
|
703
|
-
await loadPages();
|
|
704
|
-
} catch (err) {
|
|
705
|
-
console.error('Error saving page:', err);
|
|
706
|
-
alert('Failed to save changes: ' + err.message);
|
|
707
|
-
}
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
// Edit modal mousedown-guarded close
|
|
711
|
-
var editModalMouseDown = null;
|
|
712
|
-
editPageModal.addEventListener('mousedown', function(e) { editModalMouseDown = e.target; });
|
|
713
|
-
editPageModal.addEventListener('click', function(e) {
|
|
714
|
-
if (e.target === editPageModal && editModalMouseDown === editPageModal) closeEditModal();
|
|
715
|
-
editModalMouseDown = null;
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
// ── Delete modal ──
|
|
719
|
-
deletePageBtn.addEventListener('click', function() {
|
|
720
|
-
if (!currentEditPage) return;
|
|
721
|
-
currentDeletePage = currentEditPage;
|
|
722
|
-
deletePageNameDisplay2.textContent = displayName(currentEditPage);
|
|
723
|
-
closeEditModal();
|
|
724
|
-
showDialog(deleteConfirmModal);
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
deleteCancelBtn.addEventListener('click', closeDeleteModal);
|
|
728
|
-
deleteCloseBtn.addEventListener('click', closeDeleteModal);
|
|
729
|
-
|
|
730
|
-
deleteConfirmBtn.addEventListener('click', async function() {
|
|
731
|
-
if (!currentDeletePage) return;
|
|
732
|
-
var pageName = currentDeletePage.name;
|
|
733
|
-
closeDeleteModal();
|
|
734
|
-
try {
|
|
735
|
-
var res = await fetch('/api/pages/' + encodeURIComponent(pageName), { method: 'DELETE' });
|
|
736
|
-
if (!res.ok) {
|
|
737
|
-
var data = await res.json().catch(function() { return {}; });
|
|
738
|
-
throw new Error(data.error || 'Failed to delete page');
|
|
739
|
-
}
|
|
740
|
-
await loadPages();
|
|
741
|
-
} catch (err) {
|
|
742
|
-
console.error('Error deleting page:', err);
|
|
743
|
-
alert('Failed to delete page: ' + err.message);
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
// Delete modal mousedown-guarded close
|
|
748
|
-
var deleteModalMouseDown = null;
|
|
749
|
-
deleteConfirmModal.addEventListener('mousedown', function(e) { deleteModalMouseDown = e.target; });
|
|
750
|
-
deleteConfirmModal.addEventListener('click', function(e) {
|
|
751
|
-
if (e.target === deleteConfirmModal && deleteModalMouseDown === deleteConfirmModal) closeDeleteModal();
|
|
752
|
-
deleteModalMouseDown = null;
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
// ── Copy modal ──
|
|
756
|
-
copyMenuItem.addEventListener('click', function() {
|
|
757
|
-
if (!contextTarget) return;
|
|
758
|
-
currentCopySource = contextTarget;
|
|
759
|
-
copySourcePageDisplay.textContent = displayName(contextTarget);
|
|
760
|
-
copyPageTitle.value = '';
|
|
761
|
-
copyPageCategories.value = (contextTarget.categories || []).join(', ');
|
|
762
|
-
copyNewPageName.value = '';
|
|
763
|
-
hideContextMenu();
|
|
764
|
-
showDialog(copyPageModal);
|
|
765
|
-
copyPageTitle.focus();
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
copyCancelBtn.addEventListener('click', closeCopyModal);
|
|
769
|
-
copyCloseBtn.addEventListener('click', closeCopyModal);
|
|
770
|
-
|
|
771
|
-
copyConfirmBtn.addEventListener('click', async function() {
|
|
772
|
-
if (!currentCopySource) return;
|
|
773
|
-
var newTitle = copyPageTitle.value.trim();
|
|
774
|
-
var newName = copyNewPageName.value.trim();
|
|
775
|
-
if (!newName && newTitle) newName = newTitle;
|
|
776
|
-
if (!newName) {
|
|
777
|
-
alert('Please enter a title or page name.');
|
|
778
|
-
copyPageTitle.focus();
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
newName = newName.toLowerCase().replace(/\s+/g, '_');
|
|
782
|
-
newName = newName.replace(/[^a-z0-9_-]/g, '');
|
|
783
|
-
if (!newName) {
|
|
784
|
-
alert('Page name must contain at least one valid character (letters, numbers, hyphens, or underscores).');
|
|
785
|
-
copyPageTitle.focus();
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
var newCategories = copyPageCategories.value.split(',').map(function(c) { return c.trim(); }).filter(function(c) { return c.length > 0; });
|
|
789
|
-
try {
|
|
790
|
-
var body = { name: newName, title: newTitle, categories: newCategories };
|
|
791
|
-
var res = await fetch('/api/pages/' + encodeURIComponent(currentCopySource.name) + '/copy', {
|
|
792
|
-
method: 'POST',
|
|
793
|
-
headers: { 'Content-Type': 'application/json' },
|
|
794
|
-
body: JSON.stringify(body)
|
|
795
|
-
});
|
|
796
|
-
if (!res.ok) {
|
|
797
|
-
var data = await res.json().catch(function() { return {}; });
|
|
798
|
-
throw new Error(data.error || 'Failed to copy page');
|
|
799
|
-
}
|
|
800
|
-
closeCopyModal();
|
|
801
|
-
window.location.href = '/' + newName;
|
|
802
|
-
} catch (err) {
|
|
803
|
-
console.error('Error copying page:', err);
|
|
804
|
-
alert('Failed to copy page: ' + err.message);
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// Copy modal mousedown-guarded close
|
|
809
|
-
var copyModalMouseDown = null;
|
|
810
|
-
copyPageModal.addEventListener('mousedown', function(e) { copyModalMouseDown = e.target; });
|
|
811
|
-
copyPageModal.addEventListener('click', function(e) {
|
|
812
|
-
if (e.target === copyPageModal && copyModalMouseDown === copyPageModal) closeCopyModal();
|
|
813
|
-
copyModalMouseDown = null;
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
// ── Advanced toggle ──
|
|
817
|
-
document.getElementById('advancedToggle').addEventListener('click', function() {
|
|
818
|
-
var content = document.getElementById('advancedContent');
|
|
819
|
-
var icon = this.querySelector('.toggle-icon');
|
|
820
|
-
if (content.style.display === 'none') {
|
|
821
|
-
content.style.display = 'block';
|
|
822
|
-
icon.classList.add('open');
|
|
823
|
-
} else {
|
|
824
|
-
content.style.display = 'none';
|
|
825
|
-
icon.classList.remove('open');
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
// ── Update menu item ──
|
|
830
|
-
updateMenuItem.addEventListener('click', async function() {
|
|
831
|
-
if (!contextTarget) return;
|
|
832
|
-
var pageName = contextTarget.name;
|
|
833
|
-
hideContextMenu();
|
|
834
|
-
await upgradePage(pageName);
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
// ── Share handler ──
|
|
838
|
-
shareMenuItem.addEventListener('click', async function() {
|
|
839
|
-
if (!contextTarget) return;
|
|
840
|
-
var page = contextTarget;
|
|
841
|
-
var pageName = page.name;
|
|
842
|
-
hideContextMenu();
|
|
843
|
-
try {
|
|
844
|
-
var res = await fetch('/api/pages/' + encodeURIComponent(pageName) + '/export');
|
|
845
|
-
if (!res.ok) throw new Error('Failed to export page');
|
|
846
|
-
var blob = await res.blob();
|
|
847
|
-
var fileName = pageName + '.zip';
|
|
848
|
-
var url = URL.createObjectURL(blob);
|
|
849
|
-
var a = document.createElement('a');
|
|
850
|
-
a.href = url;
|
|
851
|
-
a.download = fileName;
|
|
852
|
-
document.body.appendChild(a);
|
|
853
|
-
a.click();
|
|
854
|
-
document.body.removeChild(a);
|
|
855
|
-
URL.revokeObjectURL(url);
|
|
856
|
-
} catch (err) {
|
|
857
|
-
console.error('Error sharing page:', err);
|
|
858
|
-
alert('Failed to share page: ' + err.message);
|
|
859
|
-
}
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
// ── Import handler ──
|
|
863
|
-
importBtn.addEventListener('click', function() {
|
|
864
|
-
importFileInput.click();
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
importFileInput.addEventListener('change', async function() {
|
|
868
|
-
var file = importFileInput.files[0];
|
|
869
|
-
if (!file) return;
|
|
870
|
-
importFileInput.value = '';
|
|
871
|
-
loadingOverlay.style.display = 'flex';
|
|
872
|
-
try {
|
|
873
|
-
var buffer = await file.arrayBuffer();
|
|
874
|
-
var res = await fetch('/api/pages/import', {
|
|
875
|
-
method: 'POST',
|
|
876
|
-
headers: { 'Content-Type': 'application/zip' },
|
|
877
|
-
body: buffer
|
|
878
|
-
});
|
|
879
|
-
var data = await res.json();
|
|
880
|
-
if (!res.ok) throw new Error(data.error || 'Import failed');
|
|
881
|
-
await loadPages();
|
|
882
|
-
showUpgradeToast('Imported page: ' + (data.title || data.name));
|
|
883
|
-
} catch (err) {
|
|
884
|
-
console.error('Error importing page:', err);
|
|
885
|
-
alert('Failed to import page: ' + err.message);
|
|
886
|
-
} finally {
|
|
887
|
-
loadingOverlay.style.display = 'none';
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// ── Escape key closes all modals ──
|
|
892
|
-
document.addEventListener('keydown', function(e) {
|
|
893
|
-
if (e.key === 'Escape') {
|
|
894
|
-
closeEditModal();
|
|
895
|
-
closeCopyModal();
|
|
896
|
-
closeDeleteModal();
|
|
897
|
-
}
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
// ── Resize observer for filter bar ──
|
|
901
|
-
var resizeObserver = new ResizeObserver(function() {
|
|
902
|
-
calculateVisibleCategories();
|
|
903
|
-
renderFilterBar();
|
|
904
|
-
});
|
|
905
|
-
resizeObserver.observe(filterBar);
|
|
906
|
-
|
|
907
|
-
// ── Load pages and render ──
|
|
908
|
-
async function loadPages() {
|
|
909
|
-
try {
|
|
910
|
-
var res = await fetch('/api/pages');
|
|
911
|
-
if (!res.ok) throw new Error('Failed to fetch pages');
|
|
912
|
-
allPages = await res.json();
|
|
913
|
-
renderAll();
|
|
914
|
-
} catch (err) {
|
|
915
|
-
console.error('Error fetching pages:', err);
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
loadPages();
|
|
920
|
-
})();
|
|
921
|
-
</script>
|
|
922
|
-
<script id="page-helpers" src="/api/page-helpers.js?v=3" data-locked="true"></script>
|
|
923
|
-
<script id="page-script" src="/api/page-script.js?v=3" data-locked="true"></script>
|
|
924
|
-
</body></html>
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head>
|
|
2
|
+
<meta charset="UTF-8">
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4
|
+
<title>SynthOS - Pages</title>
|
|
5
|
+
<script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
|
|
6
|
+
<link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
|
|
7
|
+
<style>
|
|
8
|
+
/* Layout-specific rules only — all component styling via FluentLM */
|
|
9
|
+
.page-grid { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
10
|
+
.page-btn { display: flex; align-items: center; justify-content: center; min-width: 120px; height: 20px; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--neutralLight); background: var(--defaultStateBackground); color: var(--bodyText); font-size: 14px; cursor: pointer; text-decoration: none; position: relative; transition: background .2s, border-color .2s, box-shadow .2s, transform .15s; overflow: hidden; box-sizing: content-box; }
|
|
11
|
+
.page-btn:hover { background: var(--defaultHoverBackground); border-color: var(--themePrimary); box-shadow: 0 4px 12px rgba(0,0,0,.1); transform: translateY(-1px); }
|
|
12
|
+
.page-btn .btn-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
|
|
13
|
+
.page-btn.scrolling .btn-label { text-overflow: clip; overflow: visible; }
|
|
14
|
+
.page-btn.scrolling .scroll-text { animation: scrollText var(--scroll-duration, 3s) linear infinite; display: inline-block; }
|
|
15
|
+
@keyframes scrollText { 0%, 10% { transform: translateX(0) } 90%, 100% { transform: translateX(var(--scroll-distance, -50%)) } }
|
|
16
|
+
.page-btn.builder-page { background: linear-gradient(135deg, var(--themePrimary), var(--themeDarkAlt)); color: #fff; border: none; font-weight: 600; box-shadow: 0 4px 16px var(--themeLighter); }
|
|
17
|
+
.page-btn.builder-page:hover { filter: brightness(1.1); box-shadow: 0 6px 20px var(--themeLighter); }
|
|
18
|
+
.page-btn.pinned { border: 2px solid var(--themePrimary); box-shadow: 0 2px 8px var(--themeLighter); }
|
|
19
|
+
.page-btn.needs-upgrade { border: 1px dashed var(--themePrimary); animation: 3s ease-in-out infinite upgradeGlow; overflow: visible; }
|
|
20
|
+
@keyframes upgradeGlow { 0%, 100% { box-shadow: none } 50% { box-shadow: 0 0 12px var(--themeLighter) } }
|
|
21
|
+
.upgrade-badge { position: absolute; top: -6px; right: -6px; font-size: 9px; color: #fff; font-weight: 600; background: linear-gradient(135deg, var(--themePrimary), var(--themeDarkAlt)); padding: 2px 7px; border-radius: 10px; line-height: 1.3; box-shadow: 0 2px 8px var(--themeLighter); pointer-events: none; z-index: 10; }
|
|
22
|
+
.toast-fixed { position: fixed; bottom: 20px; right: 20px; z-index: 2000; max-width: 360px; opacity: 0; transform: translateY(10px); transition: opacity .3s, transform .3s; pointer-events: none; }
|
|
23
|
+
.toast-fixed.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
|
|
24
|
+
.filter-toolbar { display: flex; align-items: center; gap: var(--spacingS1); }
|
|
25
|
+
.filter-toolbar .flm-pivot { flex: 1; min-width: 0; overflow: hidden; }
|
|
26
|
+
.filter-toolbar .flm-pivot-tabs { flex-wrap: nowrap; overflow: hidden; }
|
|
27
|
+
.filter-toolbar .flm-searchbox { flex-shrink: 0; width: 200px; }
|
|
28
|
+
.more-dropdown { position: relative; flex-shrink: 0; }
|
|
29
|
+
.more-menu { position: absolute; top: 100%; left: 0; margin-top: 4px; min-width: 150px; max-height: 250px; overflow-y: auto; display: none; z-index: 100; }
|
|
30
|
+
.more-menu.show { display: block; opacity: 1; pointer-events: auto; transform: scaleY(1); }
|
|
31
|
+
</style>
|
|
32
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
|
33
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
|
|
34
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
|
|
35
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
36
|
+
<script id="page-info" src="/api/page-info.js?page=pages2"></script>
|
|
37
|
+
</head>
|
|
38
|
+
|
|
39
|
+
<body>
|
|
40
|
+
<div class="viewer-panel" id="viewerPanel" style="justify-content: flex-start; align-items: stretch;">
|
|
41
|
+
<div class="flm-stack" style="gap: var(--spacingM); height: 100%; padding: var(--spacingM);">
|
|
42
|
+
<!-- Header row -->
|
|
43
|
+
<div class="flm-stack flm-stack--horizontal flm-stack--space-between" style="align-items: center;">
|
|
44
|
+
<span class="flm-text flm-text--xLarge flm-text--bold">Pages</span>
|
|
45
|
+
<div class="flm-stack flm-stack--horizontal" style="gap: var(--spacingS1);">
|
|
46
|
+
<button class="flm-button" data-icon="Upload" id="importBtn">Import</button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<input type="file" id="importFileInput" accept=".zip" style="display:none">
|
|
50
|
+
|
|
51
|
+
<!-- Toolbar row: pivot tabs + search -->
|
|
52
|
+
<div class="filter-toolbar" id="filterBar">
|
|
53
|
+
<div class="flm-pivot flm-pivot--tabs" id="categoryPivot">
|
|
54
|
+
<div class="flm-pivot-tabs" role="tablist" id="categoryTabs">
|
|
55
|
+
<button class="flm-pivot-tab flm-pivot-tab--active" data-category="All" role="tab">All</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="more-dropdown" id="moreDropdown" style="display: none;">
|
|
59
|
+
<button class="flm-button flm-button--subtle" id="moreBtn">More <i class="flm-icon" data-icon="ChevronDown"></i></button>
|
|
60
|
+
<div class="flm-contextmenu more-menu" id="moreMenu"></div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="flm-searchbox" id="searchBox">
|
|
63
|
+
<input class="flm-searchbox-input" id="searchInput" type="text" placeholder="Search pages...">
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Favorites section (hidden when empty) -->
|
|
68
|
+
<div id="favoritesSection" class="flm-stack" style="display: none; gap: var(--spacingS1);">
|
|
69
|
+
<span class="flm-text flm-text--mediumPlus flm-text--semibold flm-text--secondary">Favorites</span>
|
|
70
|
+
<div id="favoritesGrid" class="page-grid"></div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- All Pages grid -->
|
|
74
|
+
<div class="flm-stack" style="gap: var(--spacingS1);">
|
|
75
|
+
<span class="flm-text flm-text--mediumPlus flm-text--semibold flm-text--secondary">Pages</span>
|
|
76
|
+
<div id="pagesGrid" class="page-grid"></div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Edit + Copy dialogs live in the shell (static-files/shell-modals.v3.js) -->
|
|
81
|
+
|
|
82
|
+
<!-- Delete Confirmation Dialog -->
|
|
83
|
+
<div id="deleteConfirmModal" class="flm-dialog-overlay" data-light-dismiss>
|
|
84
|
+
<div class="flm-dialog">
|
|
85
|
+
<div class="flm-dialog-header">
|
|
86
|
+
<h2 class="flm-dialog-title">Confirm Delete</h2>
|
|
87
|
+
<button class="flm-dialog-close" data-icon="Cancel" aria-label="Close" id="deleteCloseBtn"></button>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="flm-dialog-body">
|
|
90
|
+
<p class="flm-text">Are you sure you want to delete "<span id="deletePageNameDisplay2"></span>"? This cannot be undone.</p>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="flm-dialog-footer">
|
|
93
|
+
<div style="flex: 1;"></div>
|
|
94
|
+
<button class="flm-button" id="deleteCancelBtn">Cancel</button>
|
|
95
|
+
<button class="flm-button" id="deleteConfirmBtn" style="color: var(--errorText);">Delete</button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Context menu -->
|
|
103
|
+
<div id="contextMenu" class="flm-contextmenu" style="position: fixed;">
|
|
104
|
+
<button id="pinMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text"></span></button>
|
|
105
|
+
<button id="editMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Edit page definition</span></button>
|
|
106
|
+
<button id="copyMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Copy to new page</span></button>
|
|
107
|
+
<button id="updateMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Update page</span></button>
|
|
108
|
+
<div class="flm-contextmenu-divider"></div>
|
|
109
|
+
<button id="shareMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Download page</span></button>
|
|
110
|
+
<button id="deleteMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text" style="color: var(--errorText);">Delete page</span></button>
|
|
111
|
+
</div>
|
|
112
|
+
<div id="instructions" style="display: none;" data-locked="true"></div>
|
|
113
|
+
<div id="thoughts" style="display: none;" data-locked="true"></div>
|
|
114
|
+
|
|
115
|
+
<!-- Toast -->
|
|
116
|
+
<div id="upgradeToast" class="flm-messagebar flm-messagebar--success toast-fixed"></div>
|
|
117
|
+
|
|
118
|
+
<script>
|
|
119
|
+
(function() {
|
|
120
|
+
'use strict';
|
|
121
|
+
|
|
122
|
+
// ── State ──
|
|
123
|
+
var allPages = [];
|
|
124
|
+
var activeCategory = 'All';
|
|
125
|
+
var searchTerm = '';
|
|
126
|
+
var contextTarget = null;
|
|
127
|
+
var currentDeletePage = null;
|
|
128
|
+
var visibleCategories = [];
|
|
129
|
+
var overflowCategories = [];
|
|
130
|
+
var BUILDER_PAGE = 'builder';
|
|
131
|
+
|
|
132
|
+
// MRU: most-recently-used categories get priority for visible slots
|
|
133
|
+
var pagesMru = (function() {
|
|
134
|
+
try { return JSON.parse(localStorage.getItem('synthos_pagesMru')) || []; }
|
|
135
|
+
catch(e) { return []; }
|
|
136
|
+
})();
|
|
137
|
+
function pagesMruTouch(cat) {
|
|
138
|
+
if (cat === 'All') return;
|
|
139
|
+
pagesMru = pagesMru.filter(function(c) { return c !== cat; });
|
|
140
|
+
pagesMru.unshift(cat);
|
|
141
|
+
if (pagesMru.length > 20) pagesMru.length = 20;
|
|
142
|
+
try { localStorage.setItem('synthos_pagesMru', JSON.stringify(pagesMru)); } catch(e) {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── DOM refs ──
|
|
146
|
+
var favoritesSection = document.getElementById('favoritesSection');
|
|
147
|
+
var favoritesGrid = document.getElementById('favoritesGrid');
|
|
148
|
+
var filterBar = document.getElementById('filterBar');
|
|
149
|
+
var categoryTabs = document.getElementById('categoryTabs');
|
|
150
|
+
var pagesGrid = document.getElementById('pagesGrid');
|
|
151
|
+
var searchInput = document.getElementById('searchInput');
|
|
152
|
+
var moreDropdown = document.getElementById('moreDropdown');
|
|
153
|
+
var moreBtn = document.getElementById('moreBtn');
|
|
154
|
+
var moreMenu = document.getElementById('moreMenu');
|
|
155
|
+
var contextMenu = document.getElementById('contextMenu');
|
|
156
|
+
var pinMenuItem = document.getElementById('pinMenuItem');
|
|
157
|
+
var editMenuItem = document.getElementById('editMenuItem');
|
|
158
|
+
var copyMenuItem = document.getElementById('copyMenuItem');
|
|
159
|
+
var updateMenuItem = document.getElementById('updateMenuItem');
|
|
160
|
+
var deleteMenuItem = document.getElementById('deleteMenuItem');
|
|
161
|
+
var deleteConfirmModal = document.getElementById('deleteConfirmModal');
|
|
162
|
+
var deletePageNameDisplay2 = document.getElementById('deletePageNameDisplay2');
|
|
163
|
+
var deleteCancelBtn = document.getElementById('deleteCancelBtn');
|
|
164
|
+
var deleteCloseBtn = document.getElementById('deleteCloseBtn');
|
|
165
|
+
var deleteConfirmBtn = document.getElementById('deleteConfirmBtn');
|
|
166
|
+
var loadingOverlay = document.getElementById('loadingOverlay');
|
|
167
|
+
var shareMenuItem = document.getElementById('shareMenuItem');
|
|
168
|
+
var importBtn = document.getElementById('importBtn');
|
|
169
|
+
var importFileInput = document.getElementById('importFileInput');
|
|
170
|
+
|
|
171
|
+
// ── Helpers ──
|
|
172
|
+
|
|
173
|
+
function displayName(page) {
|
|
174
|
+
return page.title || page.name;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Page button creation ──
|
|
178
|
+
|
|
179
|
+
function createPageLink(page, isBuilder) {
|
|
180
|
+
var latestVersion = window.pageInfo ? window.pageInfo.latestPageVersion : 0;
|
|
181
|
+
var needsUpgrade = !isBuilder && page.pageVersion < latestVersion;
|
|
182
|
+
|
|
183
|
+
var a = document.createElement('a');
|
|
184
|
+
a.href = '/' + page.name;
|
|
185
|
+
|
|
186
|
+
var title = displayName(page);
|
|
187
|
+
|
|
188
|
+
// Label with scroll-text
|
|
189
|
+
var label = document.createElement('span');
|
|
190
|
+
label.className = 'btn-label';
|
|
191
|
+
var scrollSpan = document.createElement('span');
|
|
192
|
+
scrollSpan.className = 'scroll-text';
|
|
193
|
+
scrollSpan.textContent = title;
|
|
194
|
+
label.appendChild(scrollSpan);
|
|
195
|
+
a.appendChild(label);
|
|
196
|
+
|
|
197
|
+
a.title = title;
|
|
198
|
+
|
|
199
|
+
var classes = 'page-btn';
|
|
200
|
+
if (isBuilder) {
|
|
201
|
+
classes += ' builder-page';
|
|
202
|
+
} else if (needsUpgrade) {
|
|
203
|
+
classes += ' needs-upgrade';
|
|
204
|
+
} else if (page.pinned) {
|
|
205
|
+
classes += ' pinned';
|
|
206
|
+
}
|
|
207
|
+
a.className = classes;
|
|
208
|
+
|
|
209
|
+
if (needsUpgrade) {
|
|
210
|
+
var badge = document.createElement('span');
|
|
211
|
+
badge.className = 'upgrade-badge';
|
|
212
|
+
badge.textContent = 'Update';
|
|
213
|
+
a.appendChild(badge);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (needsUpgrade) {
|
|
217
|
+
a.addEventListener('click', function(e) {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
upgradePage(page.name);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
a.addEventListener('contextmenu', function(e) {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
showContextMenu(e, page);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
a.addEventListener('mouseenter', function() {
|
|
229
|
+
var textWidth = scrollSpan.scrollWidth;
|
|
230
|
+
var containerWidth = label.clientWidth - 8;
|
|
231
|
+
if (textWidth > containerWidth) {
|
|
232
|
+
var scrollDistance = textWidth - containerWidth + 20;
|
|
233
|
+
var duration = Math.max(2, scrollDistance / 30);
|
|
234
|
+
a.style.setProperty('--scroll-distance', '-' + scrollDistance + 'px');
|
|
235
|
+
a.style.setProperty('--scroll-duration', duration + 's');
|
|
236
|
+
a.classList.add('scrolling');
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
a.addEventListener('mouseleave', function() {
|
|
241
|
+
a.classList.remove('scrolling');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return a;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Context menu ──
|
|
248
|
+
|
|
249
|
+
function showContextMenu(e, page) {
|
|
250
|
+
contextTarget = page;
|
|
251
|
+
var isBuilder = page.name === BUILDER_PAGE;
|
|
252
|
+
|
|
253
|
+
pinMenuItem.style.display = isBuilder ? 'none' : '';
|
|
254
|
+
editMenuItem.style.display = isBuilder ? 'none' : '';
|
|
255
|
+
if (!isBuilder) {
|
|
256
|
+
pinMenuItem.querySelector('.flm-contextmenu-item-text').textContent = page.pinned ? 'Unpin from Favorites' : 'Pin to Favorites';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
var latestVersion = window.pageInfo ? window.pageInfo.latestPageVersion : 0;
|
|
260
|
+
updateMenuItem.style.display = page.pageVersion < latestVersion ? '' : 'none';
|
|
261
|
+
|
|
262
|
+
shareMenuItem.style.display = isBuilder ? 'none' : '';
|
|
263
|
+
|
|
264
|
+
contextMenu.style.left = e.clientX + 'px';
|
|
265
|
+
contextMenu.style.top = e.clientY + 'px';
|
|
266
|
+
contextMenu.classList.add('flm-contextmenu--visible');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function hideContextMenu() {
|
|
270
|
+
contextMenu.classList.remove('flm-contextmenu--visible');
|
|
271
|
+
contextTarget = null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function hideMoreMenu() {
|
|
275
|
+
moreMenu.classList.remove('show');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Rendering ──
|
|
279
|
+
|
|
280
|
+
function renderFavorites() {
|
|
281
|
+
favoritesGrid.innerHTML = '';
|
|
282
|
+
var builderPage = allPages.find(function(p) { return p.name === 'builder'; });
|
|
283
|
+
if (builderPage) favoritesGrid.appendChild(createPageLink(builderPage, true));
|
|
284
|
+
|
|
285
|
+
var pinned = allPages
|
|
286
|
+
.filter(function(p) { return p.pinned && p.name !== 'builder'; })
|
|
287
|
+
.sort(function(a, b) { return displayName(a).localeCompare(displayName(b)); });
|
|
288
|
+
pinned.forEach(function(p) { favoritesGrid.appendChild(createPageLink(p)); });
|
|
289
|
+
|
|
290
|
+
favoritesSection.style.display = (builderPage || pinned.length > 0) ? '' : 'none';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function getAllCategories() {
|
|
294
|
+
var cats = new Set();
|
|
295
|
+
allPages
|
|
296
|
+
.filter(function(p) { return p.name !== 'builder'; })
|
|
297
|
+
.forEach(function(p) {
|
|
298
|
+
p.categories.forEach(function(c) {
|
|
299
|
+
// Underscore-prefix categories are internal/system — never tabbed
|
|
300
|
+
if (c && c.charAt(0) !== '_') cats.add(c);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
return Array.from(cats).sort();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function renderFilterBar() {
|
|
307
|
+
categoryTabs.innerHTML = '';
|
|
308
|
+
moreMenu.innerHTML = '';
|
|
309
|
+
|
|
310
|
+
var allTab = document.createElement('button');
|
|
311
|
+
allTab.className = 'flm-pivot-tab' + (activeCategory === 'All' ? ' flm-pivot-tab--active' : '');
|
|
312
|
+
allTab.textContent = 'All';
|
|
313
|
+
allTab.setAttribute('role', 'tab');
|
|
314
|
+
allTab.dataset.category = 'All';
|
|
315
|
+
allTab.addEventListener('click', function() { setCategory('All'); });
|
|
316
|
+
categoryTabs.appendChild(allTab);
|
|
317
|
+
|
|
318
|
+
visibleCategories.forEach(function(cat) {
|
|
319
|
+
var tab = document.createElement('button');
|
|
320
|
+
tab.className = 'flm-pivot-tab' + (activeCategory === cat ? ' flm-pivot-tab--active' : '');
|
|
321
|
+
tab.textContent = cat;
|
|
322
|
+
tab.setAttribute('role', 'tab');
|
|
323
|
+
tab.dataset.category = cat;
|
|
324
|
+
tab.addEventListener('click', function() { setCategory(cat); });
|
|
325
|
+
categoryTabs.appendChild(tab);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (overflowCategories.length > 0) {
|
|
329
|
+
moreDropdown.style.display = '';
|
|
330
|
+
moreBtn.className = 'flm-button flm-button--subtle' + (overflowCategories.includes(activeCategory) ? ' flm-button--primary' : '');
|
|
331
|
+
overflowCategories.forEach(function(cat) {
|
|
332
|
+
var item = document.createElement('button');
|
|
333
|
+
item.className = 'flm-contextmenu-item' + (activeCategory === cat ? ' flm-contextmenu-item--checked' : '');
|
|
334
|
+
var itemText = document.createElement('span');
|
|
335
|
+
itemText.className = 'flm-contextmenu-item-text';
|
|
336
|
+
itemText.textContent = cat;
|
|
337
|
+
item.appendChild(itemText);
|
|
338
|
+
item.addEventListener('click', function() { setCategory(cat); hideMoreMenu(); });
|
|
339
|
+
moreMenu.appendChild(item);
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
moreDropdown.style.display = 'none';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function calculateVisibleCategories() {
|
|
347
|
+
var allCats = getAllCategories();
|
|
348
|
+
if (!filterBar) return;
|
|
349
|
+
|
|
350
|
+
var searchBoxEl = document.getElementById('searchBox');
|
|
351
|
+
var availableWidth = filterBar.offsetWidth - (searchBoxEl ? searchBoxEl.offsetWidth + 8 : 0) - 50 - 16;
|
|
352
|
+
visibleCategories = [];
|
|
353
|
+
overflowCategories = [];
|
|
354
|
+
|
|
355
|
+
var temp = document.createElement('button');
|
|
356
|
+
temp.className = 'flm-pivot-tab';
|
|
357
|
+
temp.style.visibility = 'hidden';
|
|
358
|
+
temp.style.position = 'absolute';
|
|
359
|
+
document.body.appendChild(temp);
|
|
360
|
+
|
|
361
|
+
// Measure "More" button width
|
|
362
|
+
temp.textContent = 'More';
|
|
363
|
+
var moreBtnWidth = temp.offsetWidth + 16;
|
|
364
|
+
|
|
365
|
+
// Measure each category tab
|
|
366
|
+
var catWidths = {};
|
|
367
|
+
allCats.forEach(function(cat) {
|
|
368
|
+
temp.textContent = cat;
|
|
369
|
+
catWidths[cat] = temp.offsetWidth + 8;
|
|
370
|
+
});
|
|
371
|
+
document.body.removeChild(temp);
|
|
372
|
+
|
|
373
|
+
// Sort categories: MRU first, then alphabetical
|
|
374
|
+
var sorted = allCats.slice().sort(function(a, b) {
|
|
375
|
+
var ai = pagesMru.indexOf(a);
|
|
376
|
+
var bi = pagesMru.indexOf(b);
|
|
377
|
+
var aInMru = ai !== -1;
|
|
378
|
+
var bInMru = bi !== -1;
|
|
379
|
+
if (aInMru && !bInMru) return -1;
|
|
380
|
+
if (!aInMru && bInMru) return 1;
|
|
381
|
+
if (aInMru && bInMru) return ai - bi;
|
|
382
|
+
return a.localeCompare(b);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
var usedWidth = 0;
|
|
386
|
+
var overflowing = false;
|
|
387
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
388
|
+
var cat = sorted[i];
|
|
389
|
+
var remaining = sorted.length - i;
|
|
390
|
+
if (!overflowing && usedWidth + catWidths[cat] <= availableWidth - (remaining > 1 ? moreBtnWidth : 0)) {
|
|
391
|
+
visibleCategories.push(cat);
|
|
392
|
+
usedWidth += catWidths[cat];
|
|
393
|
+
} else {
|
|
394
|
+
overflowing = true;
|
|
395
|
+
overflowCategories.push(cat);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (overflowCategories.length === 1) {
|
|
399
|
+
if (usedWidth + catWidths[overflowCategories[0]] <= availableWidth) {
|
|
400
|
+
visibleCategories.push(overflowCategories.pop());
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function renderPages() {
|
|
406
|
+
pagesGrid.innerHTML = '';
|
|
407
|
+
var term = searchTerm.toLowerCase();
|
|
408
|
+
var sorted = allPages.slice().sort(function(a, b) {
|
|
409
|
+
return displayName(a).localeCompare(displayName(b));
|
|
410
|
+
});
|
|
411
|
+
sorted.forEach(function(p) {
|
|
412
|
+
if (p.name === 'builder') return;
|
|
413
|
+
// Pages whose ONLY categories are underscore-prefixed (internal) are excluded everywhere
|
|
414
|
+
var hasVisibleCategory = (p.categories || []).some(function(c) { return c && c.charAt(0) !== '_'; });
|
|
415
|
+
if (!hasVisibleCategory) return;
|
|
416
|
+
var inCategory = activeCategory === 'All'
|
|
417
|
+
? (term || p.showInAll !== false)
|
|
418
|
+
: p.categories.includes(activeCategory);
|
|
419
|
+
if (!inCategory) return;
|
|
420
|
+
if (term && !p.name.toLowerCase().includes(term) && !displayName(p).toLowerCase().includes(term)) return;
|
|
421
|
+
pagesGrid.appendChild(createPageLink(p));
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function setCategory(cat) {
|
|
426
|
+
activeCategory = cat;
|
|
427
|
+
pagesMruTouch(cat);
|
|
428
|
+
renderFilterBar();
|
|
429
|
+
renderPages();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function renderAll() {
|
|
433
|
+
renderFavorites();
|
|
434
|
+
calculateVisibleCategories();
|
|
435
|
+
renderFilterBar();
|
|
436
|
+
renderPages();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── Upgrade logic ──
|
|
440
|
+
|
|
441
|
+
function showUpgradeToast(message) {
|
|
442
|
+
var toast = document.getElementById('upgradeToast');
|
|
443
|
+
toast.textContent = message;
|
|
444
|
+
toast.classList.add('show');
|
|
445
|
+
setTimeout(function() { toast.classList.remove('show'); }, 3000);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function upgradePage(pageName) {
|
|
449
|
+
loadingOverlay.style.display = 'flex';
|
|
450
|
+
try {
|
|
451
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName) + '/upgrade', {
|
|
452
|
+
method: 'POST',
|
|
453
|
+
headers: { 'Content-Type': 'application/json' }
|
|
454
|
+
});
|
|
455
|
+
var data = await res.json();
|
|
456
|
+
if (!res.ok) throw new Error(data.error || 'Upgrade failed');
|
|
457
|
+
if (!data.upgraded) return false;
|
|
458
|
+
await loadPages();
|
|
459
|
+
showUpgradeToast('Page upgraded successfully');
|
|
460
|
+
return true;
|
|
461
|
+
} catch (err) {
|
|
462
|
+
console.error('Error upgrading page:', err);
|
|
463
|
+
alert('Failed to upgrade page: ' + err.message);
|
|
464
|
+
return false;
|
|
465
|
+
} finally {
|
|
466
|
+
loadingOverlay.style.display = 'none';
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ── Dialog helpers ──
|
|
471
|
+
|
|
472
|
+
function showDialog(overlay) {
|
|
473
|
+
overlay.classList.add('flm-dialog-overlay--open');
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function hideDialog(overlay) {
|
|
477
|
+
overlay.classList.remove('flm-dialog-overlay--open');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function closeDeleteModal() {
|
|
481
|
+
hideDialog(deleteConfirmModal);
|
|
482
|
+
currentDeletePage = null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Event listeners ──
|
|
486
|
+
|
|
487
|
+
// Search
|
|
488
|
+
searchInput.addEventListener('input', function(e) {
|
|
489
|
+
searchTerm = e.target.value;
|
|
490
|
+
renderPages();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// More dropdown
|
|
494
|
+
moreBtn.addEventListener('click', function(e) {
|
|
495
|
+
e.stopPropagation();
|
|
496
|
+
moreMenu.classList.toggle('show');
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// Close menus on click
|
|
500
|
+
document.addEventListener('click', function() {
|
|
501
|
+
hideContextMenu();
|
|
502
|
+
hideMoreMenu();
|
|
503
|
+
});
|
|
504
|
+
contextMenu.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
505
|
+
|
|
506
|
+
// ── Pin handler ──
|
|
507
|
+
pinMenuItem.addEventListener('click', async function() {
|
|
508
|
+
if (!contextTarget) return;
|
|
509
|
+
var pageName = contextTarget.name;
|
|
510
|
+
var newPinned = !contextTarget.pinned;
|
|
511
|
+
var categories = contextTarget.categories || [];
|
|
512
|
+
hideContextMenu();
|
|
513
|
+
try {
|
|
514
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName), {
|
|
515
|
+
method: 'POST',
|
|
516
|
+
headers: { 'Content-Type': 'application/json' },
|
|
517
|
+
body: JSON.stringify({ categories: categories, pinned: newPinned })
|
|
518
|
+
});
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
var errorText = await res.text();
|
|
521
|
+
throw new Error('Failed to update page metadata: ' + errorText);
|
|
522
|
+
}
|
|
523
|
+
var pageIndex = allPages.findIndex(function(p) { return p.name === pageName; });
|
|
524
|
+
if (pageIndex !== -1) allPages[pageIndex].pinned = newPinned;
|
|
525
|
+
renderAll();
|
|
526
|
+
await loadPages();
|
|
527
|
+
} catch (err) {
|
|
528
|
+
console.error('Error toggling pin:', err);
|
|
529
|
+
alert('Failed to update pin status: ' + err.message);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// ── Edit modal — delegated to shell (shell-modals.v3.js) ──
|
|
534
|
+
editMenuItem.addEventListener('click', function() {
|
|
535
|
+
if (!contextTarget) return;
|
|
536
|
+
var target = contextTarget;
|
|
537
|
+
hideContextMenu();
|
|
538
|
+
if (window.synthos && window.synthos.shell && window.synthos.shell.openEditModal) {
|
|
539
|
+
window.synthos.shell.openEditModal({
|
|
540
|
+
name: target.name,
|
|
541
|
+
title: target.title || '',
|
|
542
|
+
categories: target.categories || [],
|
|
543
|
+
pinned: !!target.pinned,
|
|
544
|
+
mode: target.mode,
|
|
545
|
+
showInAll: target.showInAll
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// ── Delete page — opens confirm dialog directly from context menu ──
|
|
551
|
+
deleteMenuItem.addEventListener('click', function() {
|
|
552
|
+
if (!contextTarget) return;
|
|
553
|
+
currentDeletePage = contextTarget;
|
|
554
|
+
deletePageNameDisplay2.textContent = displayName(contextTarget);
|
|
555
|
+
hideContextMenu();
|
|
556
|
+
showDialog(deleteConfirmModal);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
deleteCancelBtn.addEventListener('click', closeDeleteModal);
|
|
560
|
+
deleteCloseBtn.addEventListener('click', closeDeleteModal);
|
|
561
|
+
|
|
562
|
+
deleteConfirmBtn.addEventListener('click', async function() {
|
|
563
|
+
if (!currentDeletePage) return;
|
|
564
|
+
var pageName = currentDeletePage.name;
|
|
565
|
+
closeDeleteModal();
|
|
566
|
+
try {
|
|
567
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName), { method: 'DELETE' });
|
|
568
|
+
if (!res.ok) {
|
|
569
|
+
var data = await res.json().catch(function() { return {}; });
|
|
570
|
+
throw new Error(data.error || 'Failed to delete page');
|
|
571
|
+
}
|
|
572
|
+
await loadPages();
|
|
573
|
+
} catch (err) {
|
|
574
|
+
console.error('Error deleting page:', err);
|
|
575
|
+
alert('Failed to delete page: ' + err.message);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Delete modal mousedown-guarded close
|
|
580
|
+
var deleteModalMouseDown = null;
|
|
581
|
+
deleteConfirmModal.addEventListener('mousedown', function(e) { deleteModalMouseDown = e.target; });
|
|
582
|
+
deleteConfirmModal.addEventListener('click', function(e) {
|
|
583
|
+
if (e.target === deleteConfirmModal && deleteModalMouseDown === deleteConfirmModal) closeDeleteModal();
|
|
584
|
+
deleteModalMouseDown = null;
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// ── Copy modal — delegated to shell (shell-modals.v3.js) ──
|
|
588
|
+
copyMenuItem.addEventListener('click', function() {
|
|
589
|
+
if (!contextTarget) return;
|
|
590
|
+
var target = contextTarget;
|
|
591
|
+
hideContextMenu();
|
|
592
|
+
if (window.synthos && window.synthos.shell && window.synthos.shell.openCopyModal) {
|
|
593
|
+
window.synthos.shell.openCopyModal({
|
|
594
|
+
name: target.name,
|
|
595
|
+
title: target.title || '',
|
|
596
|
+
categories: target.categories || []
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// Shell notifies us when an edit modal saved — refresh the grid
|
|
602
|
+
if (window.synthos && window.synthos.shell && window.synthos.shell.on) {
|
|
603
|
+
window.synthos.shell.on('pages-changed', function() { loadPages(); });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ── Update menu item ──
|
|
607
|
+
updateMenuItem.addEventListener('click', async function() {
|
|
608
|
+
if (!contextTarget) return;
|
|
609
|
+
var pageName = contextTarget.name;
|
|
610
|
+
hideContextMenu();
|
|
611
|
+
await upgradePage(pageName);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// ── Share handler ──
|
|
615
|
+
shareMenuItem.addEventListener('click', async function() {
|
|
616
|
+
if (!contextTarget) return;
|
|
617
|
+
var page = contextTarget;
|
|
618
|
+
var pageName = page.name;
|
|
619
|
+
hideContextMenu();
|
|
620
|
+
try {
|
|
621
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName) + '/export');
|
|
622
|
+
if (!res.ok) throw new Error('Failed to export page');
|
|
623
|
+
var blob = await res.blob();
|
|
624
|
+
var fileName = pageName + '.zip';
|
|
625
|
+
var url = URL.createObjectURL(blob);
|
|
626
|
+
var a = document.createElement('a');
|
|
627
|
+
a.href = url;
|
|
628
|
+
a.download = fileName;
|
|
629
|
+
document.body.appendChild(a);
|
|
630
|
+
a.click();
|
|
631
|
+
document.body.removeChild(a);
|
|
632
|
+
URL.revokeObjectURL(url);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
console.error('Error sharing page:', err);
|
|
635
|
+
alert('Failed to share page: ' + err.message);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// ── Import handler ──
|
|
640
|
+
importBtn.addEventListener('click', function() {
|
|
641
|
+
importFileInput.click();
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
importFileInput.addEventListener('change', async function() {
|
|
645
|
+
var file = importFileInput.files[0];
|
|
646
|
+
if (!file) return;
|
|
647
|
+
importFileInput.value = '';
|
|
648
|
+
loadingOverlay.style.display = 'flex';
|
|
649
|
+
try {
|
|
650
|
+
var buffer = await file.arrayBuffer();
|
|
651
|
+
var res = await fetch('/api/pages/import', {
|
|
652
|
+
method: 'POST',
|
|
653
|
+
headers: { 'Content-Type': 'application/zip' },
|
|
654
|
+
body: buffer
|
|
655
|
+
});
|
|
656
|
+
var data = await res.json();
|
|
657
|
+
if (!res.ok) throw new Error(data.error || 'Import failed');
|
|
658
|
+
await loadPages();
|
|
659
|
+
showUpgradeToast('Imported page: ' + (data.title || data.name));
|
|
660
|
+
} catch (err) {
|
|
661
|
+
console.error('Error importing page:', err);
|
|
662
|
+
alert('Failed to import page: ' + err.message);
|
|
663
|
+
} finally {
|
|
664
|
+
loadingOverlay.style.display = 'none';
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// ── Escape key closes the local delete-confirm modal ──
|
|
669
|
+
document.addEventListener('keydown', function(e) {
|
|
670
|
+
if (e.key === 'Escape') closeDeleteModal();
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// ── Resize observer for filter bar ──
|
|
674
|
+
var resizeObserver = new ResizeObserver(function() {
|
|
675
|
+
calculateVisibleCategories();
|
|
676
|
+
renderFilterBar();
|
|
677
|
+
});
|
|
678
|
+
resizeObserver.observe(filterBar);
|
|
679
|
+
|
|
680
|
+
// ── Load pages and render ──
|
|
681
|
+
async function loadPages() {
|
|
682
|
+
try {
|
|
683
|
+
var res = await fetch('/api/pages');
|
|
684
|
+
if (!res.ok) throw new Error('Failed to fetch pages');
|
|
685
|
+
allPages = await res.json();
|
|
686
|
+
renderAll();
|
|
687
|
+
} catch (err) {
|
|
688
|
+
console.error('Error fetching pages:', err);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
loadPages();
|
|
693
|
+
})();
|
|
694
|
+
</script>
|
|
695
|
+
<script id="page-helpers" src="/api/page-helpers.js?v=3" data-locked="true"></script>
|
|
696
|
+
<script id="page-script" src="/api/page-script.js?v=3" data-locked="true"></script>
|
|
697
|
+
</body></html>
|