synthos 0.7.2 → 0.9.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 +215 -65
- package/default-pages/application/page.html +42 -0
- package/default-pages/application/page.json +10 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
- package/default-pages/elevenlabs_effects_studio/page.json +11 -0
- package/default-pages/elevenlabs_voice_studio/page.html +801 -0
- package/default-pages/elevenlabs_voice_studio/page.json +11 -0
- package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
- package/default-pages/json_tools/page.json +10 -0
- package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
- package/default-pages/my_notes/page.html +132 -0
- package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
- package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
- package/default-pages/neon_asteroids/files/effects.json +74 -0
- package/default-pages/neon_asteroids/page.html +1822 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/oregon_trail/page.html +323 -0
- package/default-pages/oregon_trail/page.json +12 -0
- package/default-pages/retro_game_starter/page.html +1308 -0
- package/default-pages/retro_game_starter/page.json +12 -0
- package/default-pages/{sidebar_builder.html → sidebar_page/page.html} +12 -10
- package/default-pages/sidebar_page/page.json +10 -0
- package/default-pages/{solar_explorer.html → solar_explorer/page.html} +24 -29
- package/default-pages/{solar_explorer.json → solar_explorer/page.json} +4 -4
- package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
- package/default-pages/solar_tutorial/page.json +10 -0
- package/default-pages/{two-panel_builder.html → two-panel_page/page.html} +13 -11
- package/default-pages/two-panel_page/page.json +10 -0
- package/default-pages/us_map/page.html +193 -0
- package/default-pages/us_map/page.json +12 -0
- package/default-pages/us_map_1850/page.html +326 -0
- package/default-pages/us_map_1850/page.json +12 -0
- package/default-pages/western_cities_1850/page.html +527 -0
- package/default-pages/western_cities_1850/page.json +12 -0
- package/default-themes/aurora-dawn.json +19 -0
- package/default-themes/aurora-dawn.v3.css +198 -0
- package/default-themes/aurora-dusk.json +19 -0
- package/default-themes/aurora-dusk.v3.css +200 -0
- package/default-themes/cosmos-dawn.json +19 -0
- package/default-themes/cosmos-dawn.v3.css +198 -0
- package/default-themes/cosmos-dusk.json +19 -0
- package/default-themes/cosmos-dusk.v3.css +200 -0
- package/default-themes/high-contrast-dark.json +19 -0
- package/default-themes/high-contrast-dark.v3.css +200 -0
- package/default-themes/high-contrast-light.json +19 -0
- package/default-themes/high-contrast-light.v3.css +198 -0
- package/default-themes/{nebula-dawn.css → nebula-dawn.v2.css} +134 -0
- package/default-themes/nebula-dawn.v3.css +199 -0
- package/default-themes/{nebula-dusk.css → nebula-dusk.v2.css} +128 -0
- package/default-themes/nebula-dusk.v3.css +201 -0
- package/default-themes/solar-flare-dawn.json +19 -0
- package/default-themes/solar-flare-dawn.v3.css +198 -0
- package/default-themes/solar-flare-dusk.json +19 -0
- package/default-themes/solar-flare-dusk.v3.css +200 -0
- package/dist/agents/a2a/a2aProvider.d.ts.map +1 -0
- package/dist/agents/a2a/a2aProvider.js +126 -0
- package/dist/agents/a2a/a2aProvider.js.map +1 -0
- package/dist/agents/discovery.d.ts.map +1 -0
- package/dist/agents/discovery.js +52 -0
- package/dist/agents/discovery.js.map +1 -0
- package/dist/agents/index.d.ts +7 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +20 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/openclaw/gatewayManager.d.ts +117 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -0
- package/dist/agents/openclaw/gatewayManager.js +486 -0
- package/dist/agents/openclaw/gatewayManager.js.map +1 -0
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -0
- package/dist/agents/openclaw/openclawProvider.js +237 -0
- package/dist/agents/openclaw/openclawProvider.js.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts +25 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.js +359 -0
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +6 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/builders/anthropic.d.ts +31 -0
- package/dist/builders/anthropic.d.ts.map +1 -0
- package/dist/builders/anthropic.js +227 -0
- package/dist/builders/anthropic.js.map +1 -0
- package/dist/builders/fireworksai.d.ts +9 -0
- package/dist/builders/fireworksai.d.ts.map +1 -0
- package/dist/builders/fireworksai.js +57 -0
- package/dist/builders/fireworksai.js.map +1 -0
- package/dist/builders/index.d.ts +13 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +31 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/openai.d.ts +8 -0
- package/dist/builders/openai.d.ts.map +1 -0
- package/dist/builders/openai.js +87 -0
- package/dist/builders/openai.js.map +1 -0
- package/dist/builders/types.d.ts +54 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/builders/types.js +211 -0
- package/dist/builders/types.js.map +1 -0
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/registry.d.ts +2 -1
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +65 -96
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/customizer/Customizer.d.ts +57 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +124 -0
- package/dist/customizer/Customizer.js.map +1 -0
- package/dist/customizer/index.d.ts.map +1 -0
- package/dist/customizer/index.js +9 -0
- package/dist/customizer/index.js.map +1 -0
- package/dist/files.d.ts +17 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +75 -1
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +10 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +97 -86
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +142 -145
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +24 -0
- package/dist/models/anthropic.d.ts.map +1 -0
- package/dist/models/anthropic.js +103 -0
- package/dist/models/anthropic.js.map +1 -0
- package/dist/models/chainOfThought.d.ts.map +1 -0
- package/dist/models/chainOfThought.js +45 -0
- package/dist/models/chainOfThought.js.map +1 -0
- package/dist/models/fireworksai.d.ts.map +1 -0
- package/dist/models/fireworksai.js +141 -0
- package/dist/models/fireworksai.js.map +1 -0
- package/dist/models/index.d.ts +7 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +20 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/logCompletePrompt.d.ts.map +1 -0
- package/dist/models/logCompletePrompt.js +23 -0
- package/dist/models/logCompletePrompt.js.map +1 -0
- package/dist/models/openai.d.ts +24 -0
- package/dist/models/openai.d.ts.map +1 -0
- package/dist/models/openai.js +101 -0
- package/dist/models/openai.js.map +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 +53 -2
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +21 -0
- package/dist/models/types.js.map +1 -1
- package/dist/models/utils.d.ts.map +1 -0
- package/dist/models/utils.js +21 -0
- package/dist/models/utils.js.map +1 -0
- package/dist/pages.d.ts +30 -7
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +177 -55
- package/dist/pages.js.map +1 -1
- package/dist/scripts.d.ts.map +1 -1
- package/dist/scripts.js +4 -3
- package/dist/scripts.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +9 -6
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/generateImage.d.ts.map +1 -1
- package/dist/service/generateImage.js +3 -3
- package/dist/service/generateImage.js.map +1 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +39 -7
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +47 -18
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +559 -270
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +5 -0
- package/dist/service/useAgentRoutes.d.ts.map +1 -0
- package/dist/service/useAgentRoutes.js +392 -0
- package/dist/service/useAgentRoutes.js.map +1 -0
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +380 -138
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +20 -9
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useFileRoutes.d.ts +4 -0
- package/dist/service/useFileRoutes.d.ts.map +1 -0
- package/dist/service/useFileRoutes.js +122 -0
- package/dist/service/useFileRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +660 -68
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts +4 -0
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
- package/dist/service/useSharedDataRoutes.js +104 -0
- package/dist/service/useSharedDataRoutes.js.map +1 -0
- package/dist/service/useSharedFileRoutes.d.ts +4 -0
- package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
- package/dist/service/useSharedFileRoutes.js +121 -0
- package/dist/service/useSharedFileRoutes.js.map +1 -0
- package/dist/settings.d.ts +3 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +5 -8
- package/dist/settings.js.map +1 -1
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +4 -3
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +15 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +106 -20
- package/dist/themes.js.map +1 -1
- package/migration-rules/v1-to-v2.md +193 -0
- package/migration-rules/v2-to-v3.md +481 -0
- package/package.json +15 -11
- package/required-pages/builder/page.html +43 -0
- package/required-pages/builder/page.json +10 -0
- package/required-pages/pages/page.html +924 -0
- package/required-pages/pages/page.json +10 -0
- package/required-pages/settings/page.html +1753 -0
- package/required-pages/settings/page.json +10 -0
- package/required-pages/synthos_apis/page.html +846 -0
- package/required-pages/synthos_apis/page.json +10 -0
- package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
- package/required-pages/synthos_scripts/page.json +10 -0
- package/service-connectors/airtable/connector.json +27 -0
- package/service-connectors/alpha-vantage/connector.json +26 -0
- package/service-connectors/brave-search/connector.json +26 -0
- package/service-connectors/cloudinary/connector.json +27 -0
- package/service-connectors/deepl/connector.json +28 -0
- package/service-connectors/elevenlabs/connector.json +30 -0
- package/service-connectors/giphy/connector.json +27 -0
- package/service-connectors/github/connector.json +29 -0
- package/service-connectors/huggingface/connector.json +27 -0
- package/service-connectors/imgur/connector.json +29 -0
- package/service-connectors/instagram/connector.json +43 -0
- package/service-connectors/jira/connector.json +28 -0
- package/service-connectors/mapbox/connector.json +26 -0
- package/service-connectors/nasa/connector.json +27 -0
- package/service-connectors/newsapi/connector.json +27 -0
- package/service-connectors/notion/connector.json +28 -0
- package/service-connectors/open-exchange-rates/connector.json +27 -0
- package/service-connectors/openweathermap/connector.json +26 -0
- package/service-connectors/pexels/connector.json +27 -0
- package/service-connectors/resend/connector.json +29 -0
- package/service-connectors/rss2json/connector.json +27 -0
- package/service-connectors/sendgrid/connector.json +27 -0
- package/service-connectors/spoonacular/connector.json +28 -0
- package/service-connectors/stability-ai/connector.json +27 -0
- package/service-connectors/twilio/connector.json +28 -0
- package/service-connectors/unsplash/connector.json +27 -0
- package/service-connectors/wolfram-alpha/connector.json +26 -0
- package/service-connectors/youtube-data/connector.json +30 -0
- package/src/agents/a2a/a2aProvider.ts +110 -0
- package/src/agents/discovery.ts +74 -0
- package/src/agents/index.ts +6 -0
- package/src/agents/openclaw/gatewayManager.ts +570 -0
- package/src/agents/openclaw/openclawProvider.ts +259 -0
- package/src/agents/openclaw/sshTunnelManager.ts +393 -0
- package/src/agents/types.ts +82 -0
- package/src/builders/anthropic.ts +283 -0
- package/src/builders/fireworksai.ts +59 -0
- package/src/builders/index.ts +33 -0
- package/src/builders/openai.ts +89 -0
- package/src/builders/types.ts +261 -0
- package/src/connectors/index.ts +3 -1
- package/src/connectors/registry.ts +40 -96
- package/src/connectors/types.ts +25 -0
- package/src/customizer/Customizer.ts +151 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +71 -0
- package/src/index.ts +2 -1
- package/src/init.ts +138 -97
- package/src/migrations.ts +148 -145
- package/src/models/anthropic.ts +119 -0
- package/src/models/chainOfThought.ts +56 -0
- package/src/models/fireworksai.ts +143 -0
- package/src/models/index.ts +7 -1
- package/src/models/logCompletePrompt.ts +25 -0
- package/src/models/openai.ts +110 -0
- package/src/models/providers.ts +12 -3
- package/src/models/types.ts +97 -2
- package/src/models/utils.ts +16 -0
- package/src/pages.ts +176 -54
- package/src/scripts.ts +2 -2
- package/src/service/createCompletePrompt.ts +3 -1
- package/src/service/generateImage.ts +2 -2
- package/src/service/server.ts +39 -8
- package/src/service/transformPage.ts +605 -301
- package/src/service/useAgentRoutes.ts +428 -0
- package/src/service/useApiRoutes.ts +309 -45
- package/src/service/useConnectorRoutes.ts +21 -10
- package/src/service/useFileRoutes.ts +127 -0
- package/src/service/usePageRoutes.ts +736 -75
- package/src/service/useSharedDataRoutes.ts +106 -0
- package/src/service/useSharedFileRoutes.ts +126 -0
- package/src/settings.ts +8 -10
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +103 -20
- package/static-files/favicon.svg +12 -0
- package/static-files/fluentlm-instructions.llmd +868 -0
- package/static-files/fluentlm-instructions.md +1595 -0
- package/static-files/fluentlm.css +4844 -0
- package/static-files/fluentlm.js +3602 -0
- package/static-files/fluentlm.min.css +1 -0
- package/static-files/fluentlm.min.js +1 -0
- package/static-files/helpers.v3.js +304 -0
- package/static-files/page.v3.js +1290 -0
- package/static-files/recommended-frameworks.llmd +81 -0
- package/static-files/recommended-frameworks.md +137 -0
- package/static-files/retro-game.js +877 -0
- package/static-files/shell.css +797 -0
- package/static-files/theme-dark.css +169 -0
- package/static-files/theme-light.css +169 -0
- package/tests/anthropic.spec.ts +84 -0
- package/tests/builders.spec.ts +139 -0
- package/tests/chainOfThought.spec.ts +108 -0
- package/tests/ensureScripts.spec.ts +82 -0
- package/tests/files.spec.ts +233 -0
- package/tests/fireworksai.spec.ts +92 -0
- package/tests/logCompletePrompt.spec.ts +74 -0
- package/tests/migrations.spec.ts +79 -1
- package/tests/openai.spec.ts +71 -0
- package/tests/pages.spec.ts +226 -1
- package/tests/providers.spec.ts +144 -0
- package/tests/scripts.spec.ts +209 -0
- package/tests/transformPage.spec.ts +456 -0
- package/tests/types.spec.ts +23 -0
- package/default-pages/app_builder.html +0 -40
- package/default-pages/app_builder.json +0 -1
- package/default-pages/json_tools.json +0 -1
- package/default-pages/my_notes.html +0 -33
- package/default-pages/neon_asteroids.html +0 -77
- package/default-pages/sidebar_builder.json +0 -1
- package/default-pages/solar_tutorial.json +0 -1
- package/default-pages/two-panel_builder.json +0 -1
- package/dist/connectors/index.d.ts +0 -3
- package/dist/connectors/types.d.ts +0 -61
- package/dist/index.d.ts +0 -7
- package/dist/migrations.d.ts +0 -11
- package/dist/models/providers.d.ts +0 -7
- package/dist/scripts.d.ts +0 -14
- package/dist/service/createCompletePrompt.d.ts +0 -5
- package/dist/service/debugLog.d.ts +0 -11
- package/dist/service/generateImage.d.ts +0 -32
- package/dist/service/index.d.ts +0 -8
- package/dist/service/modelInstructions.d.ts +0 -7
- package/dist/service/requiresSettings.d.ts +0 -3
- package/dist/service/server.d.ts +0 -4
- package/dist/service/useApiRoutes.d.ts +0 -4
- package/dist/service/useConnectorRoutes.d.ts +0 -4
- package/dist/service/useDataRoutes.d.ts +0 -4
- package/dist/service/usePageRoutes.d.ts +0 -5
- package/dist/synthos-cli.d.ts +0 -2
- package/images/home.png +0 -0
- package/images/page-management.png +0 -0
- package/images/settings.png +0 -0
- package/images/synthos-square.png +0 -0
- package/page-scripts/helpers-v2.js +0 -121
- package/page-scripts/page-v2.js +0 -615
- package/required-pages/builder.html +0 -74
- package/required-pages/builder.json +0 -1
- package/required-pages/pages.html +0 -196
- package/required-pages/pages.json +0 -1
- package/required-pages/settings.html +0 -841
- package/required-pages/settings.json +0 -1
- package/required-pages/synthos_apis.html +0 -272
- package/required-pages/synthos_apis.json +0 -1
- package/required-pages/synthos_scripts.json +0 -1
|
@@ -0,0 +1,924 @@
|
|
|
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
|
+
#pagesBtn { opacity: 0.4; pointer-events: none; }
|
|
10
|
+
.page-grid { display: flex; flex-wrap: wrap; gap: 12px; }
|
|
11
|
+
.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; }
|
|
12
|
+
.page-btn:hover { background: var(--defaultHoverBackground); border-color: var(--themePrimary); box-shadow: 0 4px 12px rgba(0,0,0,.1); transform: translateY(-1px); }
|
|
13
|
+
.page-btn .btn-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }
|
|
14
|
+
.page-btn.scrolling .btn-label { text-overflow: clip; overflow: visible; }
|
|
15
|
+
.page-btn.scrolling .scroll-text { animation: scrollText var(--scroll-duration, 3s) linear infinite; display: inline-block; }
|
|
16
|
+
@keyframes scrollText { 0%, 10% { transform: translateX(0) } 90%, 100% { transform: translateX(var(--scroll-distance, -50%)) } }
|
|
17
|
+
.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); }
|
|
18
|
+
.page-btn.builder-page:hover { filter: brightness(1.1); box-shadow: 0 6px 20px var(--themeLighter); }
|
|
19
|
+
.page-btn.pinned { border: 2px solid var(--themePrimary); box-shadow: 0 2px 8px var(--themeLighter); }
|
|
20
|
+
.page-btn.needs-upgrade { border: 1px dashed var(--themePrimary); animation: 3s ease-in-out infinite upgradeGlow; overflow: visible; }
|
|
21
|
+
@keyframes upgradeGlow { 0%, 100% { box-shadow: none } 50% { box-shadow: 0 0 12px var(--themeLighter) } }
|
|
22
|
+
.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; }
|
|
23
|
+
.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; }
|
|
24
|
+
.toast-fixed.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
|
|
25
|
+
.advanced-options { margin-top: 8px; }
|
|
26
|
+
.advanced-toggle { background: none; border: none; color: var(--bodySubtext); font-size: 13px; cursor: pointer; padding: 8px 0; display: flex; align-items: center; gap: 6px; }
|
|
27
|
+
.advanced-toggle:hover { color: var(--themePrimary); }
|
|
28
|
+
.toggle-icon { font-size: 10px; transition: transform .2s; }
|
|
29
|
+
.toggle-icon.open { transform: rotate(90deg); }
|
|
30
|
+
.advanced-content { padding-left: 16px; border-left: 2px solid var(--neutralLight); margin-left: 4px; }
|
|
31
|
+
.filter-toolbar { display: flex; align-items: center; gap: var(--spacingS1); }
|
|
32
|
+
.filter-toolbar .flm-pivot { flex: 1; min-width: 0; overflow: hidden; }
|
|
33
|
+
.filter-toolbar .flm-pivot-tabs { flex-wrap: nowrap; overflow: hidden; }
|
|
34
|
+
.filter-toolbar .flm-searchbox { flex-shrink: 0; width: 200px; }
|
|
35
|
+
.more-dropdown { position: relative; flex-shrink: 0; }
|
|
36
|
+
.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; }
|
|
37
|
+
.more-menu.show { display: block; }
|
|
38
|
+
</style>
|
|
39
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
|
40
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
|
|
41
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
|
|
42
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
43
|
+
<script id="page-info" src="/api/page-info.js?page=pages2"></script>
|
|
44
|
+
</head>
|
|
45
|
+
|
|
46
|
+
<body>
|
|
47
|
+
<div class="shell-toolbar" data-locked="true">
|
|
48
|
+
<button class="shell-toolbar-btn" id="builderToggle" aria-label="Page Builder" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M7 18.5H6.2c-1.77 0-3.2-1.43-3.2-3.2V7.7C3 5.93 4.43 4.5 6.2 4.5h11.6c1.77 0 3.2 1.43 3.2 3.2v7.6c0 1.77-1.43 3.2-3.2 3.2H12l-4.2 3.2c-.5.38-1.2.02-1.2-.6V18.5Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="8.5" cy="11.5" r="1" fill="currentColor"/><circle cx="12" cy="11.5" r="1" fill="currentColor"/><circle cx="15.5" cy="11.5" r="1" fill="currentColor"/></svg></button>
|
|
49
|
+
<button class="shell-toolbar-btn" id="pagesBtn" aria-label="View All Pages" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"><rect x="3" y="3" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6 7.5h5M6 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="18" y="3" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M21 7.5h5M21 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="3" y="18" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6 22.5h5M6 25h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="18" y="18" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M21 22.5h5M21 25h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
|
50
|
+
<button class="shell-toolbar-btn" id="saveBtn" aria-label="Save Page" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M17 21v-8H7v8" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M7 3v5h8" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg></button>
|
|
51
|
+
<div class="shell-toolbar-spacer" data-locked="true"></div>
|
|
52
|
+
<button class="shell-toolbar-btn" id="settingsBtn" aria-label="Settings" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" stroke="currentColor" stroke-width="1.8"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="chat-panel" data-locked="true">
|
|
55
|
+
<div class="chat-header" data-locked="true"><span>Page Builder</span><button class="chat-header-close" id="builderClose" aria-label="Close builder" data-locked="true">×</button></div>
|
|
56
|
+
<div class="chat-messages" id="chatMessages" data-locked="true">
|
|
57
|
+
<div class="chat-message">
|
|
58
|
+
<p><strong>SynthOS:</strong> Here are all of the created pages. Right-click any page to pin it to
|
|
59
|
+
favorites, edit its definition, or copy it to a new page.</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<form action="/" method="POST" id="chatForm" data-locked="true">
|
|
63
|
+
<textarea class="chat-input" id="chatInput" name="message" rows="2" placeholder="Type a message..." data-locked="true"></textarea>
|
|
64
|
+
</form>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="viewer-panel" id="viewerPanel" style="justify-content: flex-start; align-items: stretch;">
|
|
67
|
+
<div class="flm-stack" style="gap: var(--spacingM); height: 100%; padding: var(--spacingM);">
|
|
68
|
+
<!-- Header row -->
|
|
69
|
+
<div class="flm-stack flm-stack--horizontal flm-stack--space-between" style="align-items: center;">
|
|
70
|
+
<span class="flm-text flm-text--xLarge flm-text--bold">Pages</span>
|
|
71
|
+
<div class="flm-stack flm-stack--horizontal" style="gap: var(--spacingS1);">
|
|
72
|
+
<button class="flm-button" data-icon="Upload" id="importBtn">Import</button>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<input type="file" id="importFileInput" accept=".zip" style="display:none">
|
|
76
|
+
|
|
77
|
+
<!-- Toolbar row: pivot tabs + search -->
|
|
78
|
+
<div class="filter-toolbar" id="filterBar">
|
|
79
|
+
<div class="flm-pivot flm-pivot--tabs" id="categoryPivot">
|
|
80
|
+
<div class="flm-pivot-tabs" role="tablist" id="categoryTabs">
|
|
81
|
+
<button class="flm-pivot-tab flm-pivot-tab--active" data-category="All" role="tab">All</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="more-dropdown" id="moreDropdown" style="display: none;">
|
|
85
|
+
<button class="flm-button flm-button--subtle" id="moreBtn">More <i class="flm-icon" data-icon="ChevronDown"></i></button>
|
|
86
|
+
<div class="flm-contextmenu more-menu" id="moreMenu"></div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="flm-searchbox" id="searchBox">
|
|
89
|
+
<input class="flm-searchbox-input" id="searchInput" type="text" placeholder="Search pages...">
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- Favorites section (hidden when empty) -->
|
|
94
|
+
<div id="favoritesSection" class="flm-stack" style="display: none; gap: var(--spacingS1);">
|
|
95
|
+
<span class="flm-text flm-text--mediumPlus flm-text--semibold flm-text--secondary">Favorites</span>
|
|
96
|
+
<div id="favoritesGrid" class="page-grid"></div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<!-- All Pages grid -->
|
|
100
|
+
<div class="flm-stack" style="gap: var(--spacingS1);">
|
|
101
|
+
<span class="flm-text flm-text--mediumPlus flm-text--semibold flm-text--secondary">Pages</span>
|
|
102
|
+
<div id="pagesGrid" class="page-grid"></div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Edit Page Dialog -->
|
|
107
|
+
<div id="editPageModal" class="flm-dialog-overlay" data-light-dismiss>
|
|
108
|
+
<div class="flm-dialog">
|
|
109
|
+
<div class="flm-dialog-header">
|
|
110
|
+
<h2 class="flm-dialog-title">Edit Page Definition</h2>
|
|
111
|
+
<button class="flm-dialog-close" data-icon="Cancel" aria-label="Close" id="editCloseBtn"></button>
|
|
112
|
+
</div>
|
|
113
|
+
<div class="flm-dialog-body">
|
|
114
|
+
<div class="flm-stack" style="gap: var(--spacingM);">
|
|
115
|
+
<div class="flm-textfield">
|
|
116
|
+
<label class="flm-label" for="editPageTitle">Display Title</label>
|
|
117
|
+
<input class="flm-textfield-input" id="editPageTitle" placeholder="Enter display title...">
|
|
118
|
+
<span class="flm-text flm-text--small flm-text--secondary">Page name: <span id="editPageNameDisplay"></span></span>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="flm-textfield">
|
|
121
|
+
<label class="flm-label" for="editPageCategories">Categories (comma-separated)</label>
|
|
122
|
+
<input class="flm-textfield-input" id="editPageCategories" placeholder="e.g. Tools, Games, Utilities">
|
|
123
|
+
</div>
|
|
124
|
+
<label class="flm-checkbox">
|
|
125
|
+
<input type="checkbox" class="flm-checkbox-input" id="editPagePinned">
|
|
126
|
+
<span class="flm-checkbox-mark"></span>
|
|
127
|
+
<span class="flm-checkbox-label">Pinned to Favorites</span>
|
|
128
|
+
</label>
|
|
129
|
+
<div>
|
|
130
|
+
<label class="flm-checkbox">
|
|
131
|
+
<input type="checkbox" class="flm-checkbox-input" id="editPageLocked">
|
|
132
|
+
<span class="flm-checkbox-mark"></span>
|
|
133
|
+
<span class="flm-checkbox-label">Locked</span>
|
|
134
|
+
</label>
|
|
135
|
+
<span class="flm-text flm-text--small flm-text--secondary flm-text--block" style="margin-top: 4px; padding-left: 26px;">Locked pages are read-only and cannot be modified through chat.</span>
|
|
136
|
+
</div>
|
|
137
|
+
<div>
|
|
138
|
+
<label class="flm-checkbox">
|
|
139
|
+
<input type="checkbox" class="flm-checkbox-input" id="editPageShowInAll" checked>
|
|
140
|
+
<span class="flm-checkbox-mark"></span>
|
|
141
|
+
<span class="flm-checkbox-label">Show in All</span>
|
|
142
|
+
</label>
|
|
143
|
+
<span class="flm-text flm-text--small flm-text--secondary flm-text--block" style="margin-top: 4px; padding-left: 26px;">When unchecked, this page is hidden from the "All" filter and only appears under its category.</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="flm-dialog-footer">
|
|
148
|
+
<button class="flm-button" id="deletePageBtn" style="color: var(--errorText);">Delete Page</button>
|
|
149
|
+
<div style="flex: 1;"></div>
|
|
150
|
+
<button class="flm-button" id="editCancelBtn">Cancel</button>
|
|
151
|
+
<button class="flm-button flm-button--primary" id="editSaveBtn">Save Changes</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Copy Page Dialog -->
|
|
157
|
+
<div id="copyPageModal" class="flm-dialog-overlay" data-light-dismiss>
|
|
158
|
+
<div class="flm-dialog">
|
|
159
|
+
<div class="flm-dialog-header">
|
|
160
|
+
<h2 class="flm-dialog-title">Copy to New Page</h2>
|
|
161
|
+
<button class="flm-dialog-close" data-icon="Cancel" aria-label="Close" id="copyCloseBtn"></button>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="flm-dialog-body">
|
|
164
|
+
<div class="flm-stack" style="gap: var(--spacingM);">
|
|
165
|
+
<div class="flm-textfield">
|
|
166
|
+
<label class="flm-label" for="copyPageTitle">Display Title</label>
|
|
167
|
+
<input class="flm-textfield-input" id="copyPageTitle" placeholder="Enter display title...">
|
|
168
|
+
<span class="flm-text flm-text--small flm-text--secondary">Source: <span id="copySourcePageDisplay"></span></span>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="flm-textfield">
|
|
171
|
+
<label class="flm-label" for="copyPageCategories">Categories (comma-separated)</label>
|
|
172
|
+
<input class="flm-textfield-input" id="copyPageCategories" placeholder="e.g. Tools, Games, Utilities">
|
|
173
|
+
</div>
|
|
174
|
+
<div class="advanced-options">
|
|
175
|
+
<button type="button" class="advanced-toggle" id="advancedToggle">
|
|
176
|
+
<span class="toggle-icon">▶</span> Advanced Options
|
|
177
|
+
</button>
|
|
178
|
+
<div class="advanced-content" id="advancedContent" style="display: none;">
|
|
179
|
+
<div class="flm-textfield" style="margin-top: 12px;">
|
|
180
|
+
<label class="flm-label" for="copyNewPageName">Custom Page ID</label>
|
|
181
|
+
<input class="flm-textfield-input" id="copyNewPageName" placeholder="Auto-generated from title...">
|
|
182
|
+
<span class="flm-text flm-text--small flm-text--secondary">Used in the URL. Leave blank to auto-generate from title.</span>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="flm-dialog-footer">
|
|
189
|
+
<div style="flex: 1;"></div>
|
|
190
|
+
<button class="flm-button" id="copyCancelBtn">Cancel</button>
|
|
191
|
+
<button class="flm-button flm-button--primary" id="copyConfirmBtn">Create Copy</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<!-- Delete Confirmation Dialog -->
|
|
197
|
+
<div id="deleteConfirmModal" class="flm-dialog-overlay" data-light-dismiss>
|
|
198
|
+
<div class="flm-dialog">
|
|
199
|
+
<div class="flm-dialog-header">
|
|
200
|
+
<h2 class="flm-dialog-title">Confirm Delete</h2>
|
|
201
|
+
<button class="flm-dialog-close" data-icon="Cancel" aria-label="Close" id="deleteCloseBtn"></button>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="flm-dialog-body">
|
|
204
|
+
<p class="flm-text">Are you sure you want to delete "<span id="deletePageNameDisplay2"></span>"? This cannot be undone.</p>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="flm-dialog-footer">
|
|
207
|
+
<div style="flex: 1;"></div>
|
|
208
|
+
<button class="flm-button" id="deleteCancelBtn">Cancel</button>
|
|
209
|
+
<button class="flm-button" id="deleteConfirmBtn" style="color: var(--errorText);">Delete</button>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
<div id="loadingOverlay" class="loading-overlay"><div class="spinner"></div></div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<!-- Context menu -->
|
|
217
|
+
<div id="contextMenu" class="flm-contextmenu" style="position: fixed;">
|
|
218
|
+
<button id="pinMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text"></span></button>
|
|
219
|
+
<button id="editMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Edit page definition</span></button>
|
|
220
|
+
<button id="copyMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Copy to new page</span></button>
|
|
221
|
+
<button id="updateMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Update page</span></button>
|
|
222
|
+
<div class="flm-contextmenu-divider"></div>
|
|
223
|
+
<button id="shareMenuItem" class="flm-contextmenu-item"><span class="flm-contextmenu-item-text">Download page</span></button>
|
|
224
|
+
</div>
|
|
225
|
+
<div id="instructions" style="display: none;" data-locked="true"></div>
|
|
226
|
+
<div id="thoughts" style="display: none;" data-locked="true"></div>
|
|
227
|
+
|
|
228
|
+
<!-- Toast -->
|
|
229
|
+
<div id="upgradeToast" class="flm-messagebar flm-messagebar--success toast-fixed"></div>
|
|
230
|
+
|
|
231
|
+
<script>
|
|
232
|
+
(function() {
|
|
233
|
+
'use strict';
|
|
234
|
+
|
|
235
|
+
// ── State ──
|
|
236
|
+
var allPages = [];
|
|
237
|
+
var activeCategory = 'All';
|
|
238
|
+
var searchTerm = '';
|
|
239
|
+
var contextTarget = null;
|
|
240
|
+
var currentEditPage = null;
|
|
241
|
+
var currentCopySource = null;
|
|
242
|
+
var currentDeletePage = null;
|
|
243
|
+
var visibleCategories = [];
|
|
244
|
+
var overflowCategories = [];
|
|
245
|
+
var BUILDER_PAGE = 'builder';
|
|
246
|
+
|
|
247
|
+
// MRU: most-recently-used categories get priority for visible slots
|
|
248
|
+
var pagesMru = (function() {
|
|
249
|
+
try { return JSON.parse(localStorage.getItem('synthos_pagesMru')) || []; }
|
|
250
|
+
catch(e) { return []; }
|
|
251
|
+
})();
|
|
252
|
+
function pagesMruTouch(cat) {
|
|
253
|
+
if (cat === 'All') return;
|
|
254
|
+
pagesMru = pagesMru.filter(function(c) { return c !== cat; });
|
|
255
|
+
pagesMru.unshift(cat);
|
|
256
|
+
if (pagesMru.length > 20) pagesMru.length = 20;
|
|
257
|
+
try { localStorage.setItem('synthos_pagesMru', JSON.stringify(pagesMru)); } catch(e) {}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── DOM refs ──
|
|
261
|
+
var favoritesSection = document.getElementById('favoritesSection');
|
|
262
|
+
var favoritesGrid = document.getElementById('favoritesGrid');
|
|
263
|
+
var filterBar = document.getElementById('filterBar');
|
|
264
|
+
var categoryTabs = document.getElementById('categoryTabs');
|
|
265
|
+
var pagesGrid = document.getElementById('pagesGrid');
|
|
266
|
+
var searchInput = document.getElementById('searchInput');
|
|
267
|
+
var moreDropdown = document.getElementById('moreDropdown');
|
|
268
|
+
var moreBtn = document.getElementById('moreBtn');
|
|
269
|
+
var moreMenu = document.getElementById('moreMenu');
|
|
270
|
+
var contextMenu = document.getElementById('contextMenu');
|
|
271
|
+
var pinMenuItem = document.getElementById('pinMenuItem');
|
|
272
|
+
var editMenuItem = document.getElementById('editMenuItem');
|
|
273
|
+
var copyMenuItem = document.getElementById('copyMenuItem');
|
|
274
|
+
var updateMenuItem = document.getElementById('updateMenuItem');
|
|
275
|
+
var editPageModal = document.getElementById('editPageModal');
|
|
276
|
+
var copyPageModal = document.getElementById('copyPageModal');
|
|
277
|
+
var deleteConfirmModal = document.getElementById('deleteConfirmModal');
|
|
278
|
+
var editPageNameDisplay = document.getElementById('editPageNameDisplay');
|
|
279
|
+
var editPageTitle = document.getElementById('editPageTitle');
|
|
280
|
+
var editPageCategories = document.getElementById('editPageCategories');
|
|
281
|
+
var editPagePinned = document.getElementById('editPagePinned');
|
|
282
|
+
var editPageLocked = document.getElementById('editPageLocked');
|
|
283
|
+
var editPageShowInAll = document.getElementById('editPageShowInAll');
|
|
284
|
+
var editCancelBtn = document.getElementById('editCancelBtn');
|
|
285
|
+
var editCloseBtn = document.getElementById('editCloseBtn');
|
|
286
|
+
var editSaveBtn = document.getElementById('editSaveBtn');
|
|
287
|
+
var deletePageBtn = document.getElementById('deletePageBtn');
|
|
288
|
+
var copySourcePageDisplay = document.getElementById('copySourcePageDisplay');
|
|
289
|
+
var copyPageTitle = document.getElementById('copyPageTitle');
|
|
290
|
+
var copyPageCategories = document.getElementById('copyPageCategories');
|
|
291
|
+
var copyNewPageName = document.getElementById('copyNewPageName');
|
|
292
|
+
var copyCancelBtn = document.getElementById('copyCancelBtn');
|
|
293
|
+
var copyCloseBtn = document.getElementById('copyCloseBtn');
|
|
294
|
+
var copyConfirmBtn = document.getElementById('copyConfirmBtn');
|
|
295
|
+
var deletePageNameDisplay2 = document.getElementById('deletePageNameDisplay2');
|
|
296
|
+
var deleteCancelBtn = document.getElementById('deleteCancelBtn');
|
|
297
|
+
var deleteCloseBtn = document.getElementById('deleteCloseBtn');
|
|
298
|
+
var deleteConfirmBtn = document.getElementById('deleteConfirmBtn');
|
|
299
|
+
var loadingOverlay = document.getElementById('loadingOverlay');
|
|
300
|
+
var shareMenuItem = document.getElementById('shareMenuItem');
|
|
301
|
+
var importBtn = document.getElementById('importBtn');
|
|
302
|
+
var importFileInput = document.getElementById('importFileInput');
|
|
303
|
+
|
|
304
|
+
// ── Helpers ──
|
|
305
|
+
|
|
306
|
+
function displayName(page) {
|
|
307
|
+
return page.title || page.name;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Page button creation ──
|
|
311
|
+
|
|
312
|
+
function createPageLink(page, isBuilder) {
|
|
313
|
+
var latestVersion = window.pageInfo ? window.pageInfo.latestPageVersion : 0;
|
|
314
|
+
var needsUpgrade = !isBuilder && page.pageVersion < latestVersion;
|
|
315
|
+
|
|
316
|
+
var a = document.createElement('a');
|
|
317
|
+
a.href = '/' + page.name;
|
|
318
|
+
|
|
319
|
+
var title = displayName(page);
|
|
320
|
+
|
|
321
|
+
// Label with scroll-text
|
|
322
|
+
var label = document.createElement('span');
|
|
323
|
+
label.className = 'btn-label';
|
|
324
|
+
var scrollSpan = document.createElement('span');
|
|
325
|
+
scrollSpan.className = 'scroll-text';
|
|
326
|
+
scrollSpan.textContent = title;
|
|
327
|
+
label.appendChild(scrollSpan);
|
|
328
|
+
a.appendChild(label);
|
|
329
|
+
|
|
330
|
+
a.title = title;
|
|
331
|
+
|
|
332
|
+
var classes = 'page-btn';
|
|
333
|
+
if (isBuilder) {
|
|
334
|
+
classes += ' builder-page';
|
|
335
|
+
} else if (needsUpgrade) {
|
|
336
|
+
classes += ' needs-upgrade';
|
|
337
|
+
} else if (page.pinned) {
|
|
338
|
+
classes += ' pinned';
|
|
339
|
+
}
|
|
340
|
+
a.className = classes;
|
|
341
|
+
|
|
342
|
+
if (needsUpgrade) {
|
|
343
|
+
var badge = document.createElement('span');
|
|
344
|
+
badge.className = 'upgrade-badge';
|
|
345
|
+
badge.textContent = 'Update';
|
|
346
|
+
a.appendChild(badge);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (needsUpgrade) {
|
|
350
|
+
a.addEventListener('click', function(e) {
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
upgradePage(page.name);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
a.addEventListener('contextmenu', function(e) {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
showContextMenu(e, page);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
a.addEventListener('mouseenter', function() {
|
|
362
|
+
var textWidth = scrollSpan.scrollWidth;
|
|
363
|
+
var containerWidth = label.clientWidth - 8;
|
|
364
|
+
if (textWidth > containerWidth) {
|
|
365
|
+
var scrollDistance = textWidth - containerWidth + 20;
|
|
366
|
+
var duration = Math.max(2, scrollDistance / 30);
|
|
367
|
+
a.style.setProperty('--scroll-distance', '-' + scrollDistance + 'px');
|
|
368
|
+
a.style.setProperty('--scroll-duration', duration + 's');
|
|
369
|
+
a.classList.add('scrolling');
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
a.addEventListener('mouseleave', function() {
|
|
374
|
+
a.classList.remove('scrolling');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return a;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ── Context menu ──
|
|
381
|
+
|
|
382
|
+
function showContextMenu(e, page) {
|
|
383
|
+
contextTarget = page;
|
|
384
|
+
var isBuilder = page.name === BUILDER_PAGE;
|
|
385
|
+
|
|
386
|
+
pinMenuItem.style.display = isBuilder ? 'none' : '';
|
|
387
|
+
editMenuItem.style.display = isBuilder ? 'none' : '';
|
|
388
|
+
if (!isBuilder) {
|
|
389
|
+
pinMenuItem.querySelector('.flm-contextmenu-item-text').textContent = page.pinned ? 'Unpin from Favorites' : 'Pin to Favorites';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
var latestVersion = window.pageInfo ? window.pageInfo.latestPageVersion : 0;
|
|
393
|
+
updateMenuItem.style.display = page.pageVersion < latestVersion ? '' : 'none';
|
|
394
|
+
|
|
395
|
+
shareMenuItem.style.display = isBuilder ? 'none' : '';
|
|
396
|
+
|
|
397
|
+
contextMenu.style.left = e.clientX + 'px';
|
|
398
|
+
contextMenu.style.top = e.clientY + 'px';
|
|
399
|
+
contextMenu.classList.add('flm-contextmenu--visible');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function hideContextMenu() {
|
|
403
|
+
contextMenu.classList.remove('flm-contextmenu--visible');
|
|
404
|
+
contextTarget = null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function hideMoreMenu() {
|
|
408
|
+
moreMenu.classList.remove('show');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── Rendering ──
|
|
412
|
+
|
|
413
|
+
function renderFavorites() {
|
|
414
|
+
favoritesGrid.innerHTML = '';
|
|
415
|
+
var builderPage = allPages.find(function(p) { return p.name === 'builder'; });
|
|
416
|
+
if (builderPage) favoritesGrid.appendChild(createPageLink(builderPage, true));
|
|
417
|
+
|
|
418
|
+
var pinned = allPages
|
|
419
|
+
.filter(function(p) { return p.pinned && p.name !== 'builder'; })
|
|
420
|
+
.sort(function(a, b) { return displayName(a).localeCompare(displayName(b)); });
|
|
421
|
+
pinned.forEach(function(p) { favoritesGrid.appendChild(createPageLink(p)); });
|
|
422
|
+
|
|
423
|
+
favoritesSection.style.display = (builderPage || pinned.length > 0) ? '' : 'none';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function getAllCategories() {
|
|
427
|
+
var cats = new Set();
|
|
428
|
+
allPages
|
|
429
|
+
.filter(function(p) { return p.name !== 'builder'; })
|
|
430
|
+
.forEach(function(p) { p.categories.forEach(function(c) { cats.add(c); }); });
|
|
431
|
+
return Array.from(cats).sort();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function renderFilterBar() {
|
|
435
|
+
categoryTabs.innerHTML = '';
|
|
436
|
+
moreMenu.innerHTML = '';
|
|
437
|
+
|
|
438
|
+
var allTab = document.createElement('button');
|
|
439
|
+
allTab.className = 'flm-pivot-tab' + (activeCategory === 'All' ? ' flm-pivot-tab--active' : '');
|
|
440
|
+
allTab.textContent = 'All';
|
|
441
|
+
allTab.setAttribute('role', 'tab');
|
|
442
|
+
allTab.dataset.category = 'All';
|
|
443
|
+
allTab.addEventListener('click', function() { setCategory('All'); });
|
|
444
|
+
categoryTabs.appendChild(allTab);
|
|
445
|
+
|
|
446
|
+
visibleCategories.forEach(function(cat) {
|
|
447
|
+
var tab = document.createElement('button');
|
|
448
|
+
tab.className = 'flm-pivot-tab' + (activeCategory === cat ? ' flm-pivot-tab--active' : '');
|
|
449
|
+
tab.textContent = cat;
|
|
450
|
+
tab.setAttribute('role', 'tab');
|
|
451
|
+
tab.dataset.category = cat;
|
|
452
|
+
tab.addEventListener('click', function() { setCategory(cat); });
|
|
453
|
+
categoryTabs.appendChild(tab);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
if (overflowCategories.length > 0) {
|
|
457
|
+
moreDropdown.style.display = '';
|
|
458
|
+
moreBtn.className = 'flm-button flm-button--subtle' + (overflowCategories.includes(activeCategory) ? ' flm-button--primary' : '');
|
|
459
|
+
overflowCategories.forEach(function(cat) {
|
|
460
|
+
var item = document.createElement('button');
|
|
461
|
+
item.className = 'flm-contextmenu-item' + (activeCategory === cat ? ' flm-contextmenu-item--checked' : '');
|
|
462
|
+
var itemText = document.createElement('span');
|
|
463
|
+
itemText.className = 'flm-contextmenu-item-text';
|
|
464
|
+
itemText.textContent = cat;
|
|
465
|
+
item.appendChild(itemText);
|
|
466
|
+
item.addEventListener('click', function() { setCategory(cat); hideMoreMenu(); });
|
|
467
|
+
moreMenu.appendChild(item);
|
|
468
|
+
});
|
|
469
|
+
} else {
|
|
470
|
+
moreDropdown.style.display = 'none';
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function calculateVisibleCategories() {
|
|
475
|
+
var allCats = getAllCategories();
|
|
476
|
+
if (!filterBar) return;
|
|
477
|
+
|
|
478
|
+
var searchBoxEl = document.getElementById('searchBox');
|
|
479
|
+
var availableWidth = filterBar.offsetWidth - (searchBoxEl ? searchBoxEl.offsetWidth + 8 : 0) - 50 - 16;
|
|
480
|
+
visibleCategories = [];
|
|
481
|
+
overflowCategories = [];
|
|
482
|
+
|
|
483
|
+
var temp = document.createElement('button');
|
|
484
|
+
temp.className = 'flm-pivot-tab';
|
|
485
|
+
temp.style.visibility = 'hidden';
|
|
486
|
+
temp.style.position = 'absolute';
|
|
487
|
+
document.body.appendChild(temp);
|
|
488
|
+
|
|
489
|
+
// Measure "More" button width
|
|
490
|
+
temp.textContent = 'More';
|
|
491
|
+
var moreBtnWidth = temp.offsetWidth + 16;
|
|
492
|
+
|
|
493
|
+
// Measure each category tab
|
|
494
|
+
var catWidths = {};
|
|
495
|
+
allCats.forEach(function(cat) {
|
|
496
|
+
temp.textContent = cat;
|
|
497
|
+
catWidths[cat] = temp.offsetWidth + 8;
|
|
498
|
+
});
|
|
499
|
+
document.body.removeChild(temp);
|
|
500
|
+
|
|
501
|
+
// Sort categories: MRU first, then alphabetical
|
|
502
|
+
var sorted = allCats.slice().sort(function(a, b) {
|
|
503
|
+
var ai = pagesMru.indexOf(a);
|
|
504
|
+
var bi = pagesMru.indexOf(b);
|
|
505
|
+
var aInMru = ai !== -1;
|
|
506
|
+
var bInMru = bi !== -1;
|
|
507
|
+
if (aInMru && !bInMru) return -1;
|
|
508
|
+
if (!aInMru && bInMru) return 1;
|
|
509
|
+
if (aInMru && bInMru) return ai - bi;
|
|
510
|
+
return a.localeCompare(b);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
var usedWidth = 0;
|
|
514
|
+
var overflowing = false;
|
|
515
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
516
|
+
var cat = sorted[i];
|
|
517
|
+
var remaining = sorted.length - i;
|
|
518
|
+
if (!overflowing && usedWidth + catWidths[cat] <= availableWidth - (remaining > 1 ? moreBtnWidth : 0)) {
|
|
519
|
+
visibleCategories.push(cat);
|
|
520
|
+
usedWidth += catWidths[cat];
|
|
521
|
+
} else {
|
|
522
|
+
overflowing = true;
|
|
523
|
+
overflowCategories.push(cat);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (overflowCategories.length === 1) {
|
|
527
|
+
if (usedWidth + catWidths[overflowCategories[0]] <= availableWidth) {
|
|
528
|
+
visibleCategories.push(overflowCategories.pop());
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function renderPages() {
|
|
534
|
+
pagesGrid.innerHTML = '';
|
|
535
|
+
var term = searchTerm.toLowerCase();
|
|
536
|
+
allPages.forEach(function(p) {
|
|
537
|
+
if (p.name === 'builder') return;
|
|
538
|
+
var inCategory = activeCategory === 'All'
|
|
539
|
+
? (term || p.showInAll !== false)
|
|
540
|
+
: p.categories.includes(activeCategory);
|
|
541
|
+
if (!inCategory) return;
|
|
542
|
+
if (term && !p.name.toLowerCase().includes(term) && !displayName(p).toLowerCase().includes(term)) return;
|
|
543
|
+
pagesGrid.appendChild(createPageLink(p));
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function setCategory(cat) {
|
|
548
|
+
activeCategory = cat;
|
|
549
|
+
pagesMruTouch(cat);
|
|
550
|
+
renderFilterBar();
|
|
551
|
+
renderPages();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function renderAll() {
|
|
555
|
+
renderFavorites();
|
|
556
|
+
calculateVisibleCategories();
|
|
557
|
+
renderFilterBar();
|
|
558
|
+
renderPages();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ── Upgrade logic ──
|
|
562
|
+
|
|
563
|
+
function showUpgradeToast(message) {
|
|
564
|
+
var toast = document.getElementById('upgradeToast');
|
|
565
|
+
toast.textContent = message;
|
|
566
|
+
toast.classList.add('show');
|
|
567
|
+
setTimeout(function() { toast.classList.remove('show'); }, 3000);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function upgradePage(pageName) {
|
|
571
|
+
loadingOverlay.style.display = 'flex';
|
|
572
|
+
try {
|
|
573
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName) + '/upgrade', {
|
|
574
|
+
method: 'POST',
|
|
575
|
+
headers: { 'Content-Type': 'application/json' }
|
|
576
|
+
});
|
|
577
|
+
var data = await res.json();
|
|
578
|
+
if (!res.ok) throw new Error(data.error || 'Upgrade failed');
|
|
579
|
+
if (!data.upgraded) return false;
|
|
580
|
+
await loadPages();
|
|
581
|
+
showUpgradeToast('Page upgraded successfully');
|
|
582
|
+
return true;
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.error('Error upgrading page:', err);
|
|
585
|
+
alert('Failed to upgrade page: ' + err.message);
|
|
586
|
+
return false;
|
|
587
|
+
} finally {
|
|
588
|
+
loadingOverlay.style.display = 'none';
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ── Dialog helpers ──
|
|
593
|
+
|
|
594
|
+
function showDialog(overlay) {
|
|
595
|
+
overlay.classList.add('flm-dialog-overlay--open');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function hideDialog(overlay) {
|
|
599
|
+
overlay.classList.remove('flm-dialog-overlay--open');
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function closeEditModal() {
|
|
603
|
+
hideDialog(editPageModal);
|
|
604
|
+
currentEditPage = null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function closeCopyModal() {
|
|
608
|
+
hideDialog(copyPageModal);
|
|
609
|
+
currentCopySource = null;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function closeDeleteModal() {
|
|
613
|
+
hideDialog(deleteConfirmModal);
|
|
614
|
+
currentDeletePage = null;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ── Event listeners ──
|
|
618
|
+
|
|
619
|
+
// Search
|
|
620
|
+
searchInput.addEventListener('input', function(e) {
|
|
621
|
+
searchTerm = e.target.value;
|
|
622
|
+
renderPages();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// More dropdown
|
|
626
|
+
moreBtn.addEventListener('click', function(e) {
|
|
627
|
+
e.stopPropagation();
|
|
628
|
+
moreMenu.classList.toggle('show');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Close menus on click
|
|
632
|
+
document.addEventListener('click', function() {
|
|
633
|
+
hideContextMenu();
|
|
634
|
+
hideMoreMenu();
|
|
635
|
+
});
|
|
636
|
+
contextMenu.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
637
|
+
|
|
638
|
+
// ── Pin handler ──
|
|
639
|
+
pinMenuItem.addEventListener('click', async function() {
|
|
640
|
+
if (!contextTarget) return;
|
|
641
|
+
var pageName = contextTarget.name;
|
|
642
|
+
var newPinned = !contextTarget.pinned;
|
|
643
|
+
var categories = contextTarget.categories || [];
|
|
644
|
+
hideContextMenu();
|
|
645
|
+
try {
|
|
646
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName), {
|
|
647
|
+
method: 'POST',
|
|
648
|
+
headers: { 'Content-Type': 'application/json' },
|
|
649
|
+
body: JSON.stringify({ categories: categories, pinned: newPinned })
|
|
650
|
+
});
|
|
651
|
+
if (!res.ok) {
|
|
652
|
+
var errorText = await res.text();
|
|
653
|
+
throw new Error('Failed to update page metadata: ' + errorText);
|
|
654
|
+
}
|
|
655
|
+
var pageIndex = allPages.findIndex(function(p) { return p.name === pageName; });
|
|
656
|
+
if (pageIndex !== -1) allPages[pageIndex].pinned = newPinned;
|
|
657
|
+
renderAll();
|
|
658
|
+
await loadPages();
|
|
659
|
+
} catch (err) {
|
|
660
|
+
console.error('Error toggling pin:', err);
|
|
661
|
+
alert('Failed to update pin status: ' + err.message);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// ── Edit modal ──
|
|
666
|
+
editMenuItem.addEventListener('click', function() {
|
|
667
|
+
if (!contextTarget) return;
|
|
668
|
+
currentEditPage = contextTarget;
|
|
669
|
+
editPageNameDisplay.textContent = contextTarget.name;
|
|
670
|
+
editPageTitle.value = contextTarget.title || '';
|
|
671
|
+
editPageCategories.value = (contextTarget.categories || []).join(', ');
|
|
672
|
+
editPagePinned.checked = contextTarget.pinned || false;
|
|
673
|
+
editPageLocked.checked = contextTarget.mode === 'locked';
|
|
674
|
+
editPageShowInAll.checked = contextTarget.showInAll !== false;
|
|
675
|
+
hideContextMenu();
|
|
676
|
+
showDialog(editPageModal);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
editCancelBtn.addEventListener('click', closeEditModal);
|
|
680
|
+
editCloseBtn.addEventListener('click', closeEditModal);
|
|
681
|
+
|
|
682
|
+
editSaveBtn.addEventListener('click', async function() {
|
|
683
|
+
if (!currentEditPage) return;
|
|
684
|
+
var newTitle = editPageTitle.value.trim();
|
|
685
|
+
var newCategories = editPageCategories.value.split(',').map(function(c) { return c.trim(); }).filter(function(c) { return c.length > 0; });
|
|
686
|
+
var newPinned = editPagePinned.checked;
|
|
687
|
+
var newMode = editPageLocked.checked ? 'locked' : 'unlocked';
|
|
688
|
+
var newShowInAll = editPageShowInAll.checked;
|
|
689
|
+
try {
|
|
690
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(currentEditPage.name), {
|
|
691
|
+
method: 'POST',
|
|
692
|
+
headers: { 'Content-Type': 'application/json' },
|
|
693
|
+
body: JSON.stringify({
|
|
694
|
+
title: newTitle || undefined,
|
|
695
|
+
categories: newCategories,
|
|
696
|
+
pinned: newPinned,
|
|
697
|
+
showInAll: newShowInAll,
|
|
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>
|