synthos 0.7.2 → 0.8.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.json +1 -0
- package/default-pages/json_tools.json +1 -1
- package/default-pages/oregon_trail.html +321 -0
- package/default-pages/oregon_trail.json +12 -0
- package/default-pages/sidebar_page.json +1 -0
- package/default-pages/solar_explorer.html +10 -18
- package/default-pages/solar_explorer.json +2 -2
- package/default-pages/two-panel_page.json +1 -0
- package/default-pages/us_map.html +192 -0
- package/default-pages/us_map.json +12 -0
- package/default-pages/us_map_1850.html +325 -0
- package/default-pages/us_map_1850.json +12 -0
- package/default-pages/western_cities_1850.html +526 -0
- package/default-pages/western_cities_1850.json +12 -0
- package/default-themes/{nebula-dawn.css → nebula-dawn.v2.css} +24 -0
- package/default-themes/{nebula-dusk.css → nebula-dusk.v2.css} +24 -0
- package/dist/agents/a2a/a2aProvider.d.ts +3 -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 +30 -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 +19 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/openclaw/gatewayManager.d.ts +113 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -0
- package/dist/agents/openclaw/gatewayManager.js +470 -0
- package/dist/agents/openclaw/gatewayManager.js.map +1 -0
- package/dist/agents/openclaw/openclawProvider.d.ts +3 -0
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -0
- package/dist/agents/openclaw/openclawProvider.js +239 -0
- package/dist/agents/openclaw/openclawProvider.js.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts +23 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.js +340 -0
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -0
- package/dist/agents/types.d.ts +64 -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/connectors/airtable/connector.json +27 -0
- package/dist/connectors/alpha-vantage/connector.json +26 -0
- package/dist/connectors/brave-search/connector.json +26 -0
- package/dist/connectors/cloudinary/connector.json +27 -0
- package/dist/connectors/deepl/connector.json +28 -0
- package/dist/connectors/elevenlabs/connector.json +30 -0
- package/dist/connectors/giphy/connector.json +27 -0
- package/dist/connectors/github/connector.json +29 -0
- package/dist/connectors/huggingface/connector.json +27 -0
- package/dist/connectors/imgur/connector.json +29 -0
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/instagram/connector.json +43 -0
- package/dist/connectors/jira/connector.json +28 -0
- package/dist/connectors/mapbox/connector.json +26 -0
- package/dist/connectors/nasa/connector.json +27 -0
- package/dist/connectors/newsapi/connector.json +27 -0
- package/dist/connectors/notion/connector.json +28 -0
- package/dist/connectors/open-exchange-rates/connector.json +27 -0
- package/dist/connectors/openweathermap/connector.json +26 -0
- package/dist/connectors/pexels/connector.json +27 -0
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +42 -96
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/resend/connector.json +29 -0
- package/dist/connectors/rss2json/connector.json +27 -0
- package/dist/connectors/sendgrid/connector.json +27 -0
- package/dist/connectors/spoonacular/connector.json +28 -0
- package/dist/connectors/stability-ai/connector.json +27 -0
- package/dist/connectors/twilio/connector.json +28 -0
- package/dist/connectors/types.d.ts +23 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/connectors/unsplash/connector.json +27 -0
- package/dist/connectors/wolfram-alpha/connector.json +26 -0
- package/dist/connectors/youtube-data/connector.json +30 -0
- package/dist/files.d.ts +1 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +16 -1
- package/dist/files.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +28 -0
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts +3 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +122 -138
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +22 -0
- package/dist/models/anthropic.d.ts.map +1 -0
- package/dist/models/anthropic.js +76 -0
- package/dist/models/anthropic.js.map +1 -0
- package/dist/models/chainOfThought.d.ts +12 -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 +30 -0
- package/dist/models/fireworksai.d.ts.map +1 -0
- package/dist/models/fireworksai.js +133 -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 +19 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/logCompletePrompt.d.ts +3 -0
- 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 +80 -0
- package/dist/models/openai.js.map +1 -0
- 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 +34 -2
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +16 -0
- package/dist/models/types.js.map +1 -1
- package/dist/models/utils.d.ts +6 -0
- 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/scripts.d.ts +2 -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 +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 +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 +3 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +4 -2
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +74 -6
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +4 -0
- package/dist/service/useAgentRoutes.d.ts.map +1 -0
- package/dist/service/useAgentRoutes.js +389 -0
- package/dist/service/useAgentRoutes.js.map +1 -0
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +157 -16
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +14 -3
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useGatewayRoutes.d.ts +4 -0
- package/dist/service/useGatewayRoutes.d.ts.map +1 -0
- package/dist/service/useGatewayRoutes.js +168 -0
- package/dist/service/useGatewayRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +16 -5
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/settings.d.ts +2 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +4 -8
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +14 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +86 -13
- package/dist/themes.js.map +1 -1
- package/package.json +8 -5
- package/page-scripts/helpers-v2.js +101 -0
- package/page-scripts/page-v2.js +47 -6
- package/required-pages/builder.html +1 -27
- package/required-pages/pages.html +745 -22
- package/required-pages/settings.html +819 -21
- package/required-pages/synthos_apis.html +56 -1
- 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 +559 -0
- package/src/agents/openclaw/openclawProvider.ts +261 -0
- package/src/agents/openclaw/sshTunnelManager.ts +385 -0
- package/src/agents/types.ts +82 -0
- package/src/connectors/airtable/connector.json +27 -0
- package/src/connectors/alpha-vantage/connector.json +26 -0
- package/src/connectors/brave-search/connector.json +26 -0
- package/src/connectors/cloudinary/connector.json +27 -0
- package/src/connectors/deepl/connector.json +28 -0
- package/src/connectors/elevenlabs/connector.json +30 -0
- package/src/connectors/giphy/connector.json +27 -0
- package/src/connectors/github/connector.json +29 -0
- package/src/connectors/huggingface/connector.json +27 -0
- package/src/connectors/imgur/connector.json +29 -0
- package/src/connectors/index.ts +2 -0
- package/src/connectors/instagram/connector.json +43 -0
- package/src/connectors/jira/connector.json +28 -0
- package/src/connectors/mapbox/connector.json +26 -0
- package/src/connectors/nasa/connector.json +27 -0
- package/src/connectors/newsapi/connector.json +27 -0
- package/src/connectors/notion/connector.json +28 -0
- package/src/connectors/open-exchange-rates/connector.json +27 -0
- package/src/connectors/openweathermap/connector.json +26 -0
- package/src/connectors/pexels/connector.json +27 -0
- package/src/connectors/registry.ts +21 -97
- package/src/connectors/resend/connector.json +29 -0
- package/src/connectors/rss2json/connector.json +27 -0
- package/src/connectors/sendgrid/connector.json +27 -0
- package/src/connectors/spoonacular/connector.json +28 -0
- package/src/connectors/stability-ai/connector.json +27 -0
- package/src/connectors/twilio/connector.json +28 -0
- package/src/connectors/types.ts +25 -0
- package/src/connectors/unsplash/connector.json +27 -0
- package/src/connectors/wolfram-alpha/connector.json +26 -0
- package/src/connectors/youtube-data/connector.json +30 -0
- package/src/files.ts +14 -0
- package/src/init.ts +27 -0
- package/src/migrations.ts +121 -138
- package/src/models/anthropic.ts +89 -0
- package/src/models/chainOfThought.ts +56 -0
- package/src/models/fireworksai.ts +136 -0
- package/src/models/index.ts +7 -1
- package/src/models/logCompletePrompt.ts +25 -0
- package/src/models/openai.ts +90 -0
- package/src/models/providers.ts +12 -3
- package/src/models/types.ts +67 -2
- package/src/models/utils.ts +16 -0
- 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 +4 -0
- package/src/service/transformPage.ts +81 -8
- package/src/service/useAgentRoutes.ts +423 -0
- package/src/service/useApiRoutes.ts +173 -18
- package/src/service/useConnectorRoutes.ts +14 -3
- package/src/service/usePageRoutes.ts +20 -6
- package/src/settings.ts +6 -10
- package/src/themes.ts +84 -12
- package/tests/anthropic.spec.ts +84 -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 +517 -0
- package/tests/types.spec.ts +23 -0
- package/default-pages/app_builder.json +0 -1
- package/default-pages/sidebar_builder.json +0 -1
- package/default-pages/two-panel_builder.json +0 -1
- 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/default-pages/{app_builder.html → application.html} +0 -0
- /package/default-pages/{sidebar_builder.html → sidebar_page.html} +0 -0
- /package/default-pages/{two-panel_builder.html → two-panel_page.html} +0 -0
|
@@ -4,18 +4,83 @@
|
|
|
4
4
|
<title>SynthOS - Pages</title>
|
|
5
5
|
<script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
|
|
6
6
|
<link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
|
|
7
|
-
<style
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
<style>
|
|
8
|
+
/* Advanced options */
|
|
9
|
+
.advanced-options { margin-top: 8px }
|
|
10
|
+
.advanced-toggle { background: 0 0; border: none; color: var(--text-secondary); font-size: 13px; cursor: pointer; padding: 8px 0; display: flex; align-items: center; gap: 6px; transition: color .2s }
|
|
11
|
+
.advanced-toggle:hover { color: var(--accent-primary) }
|
|
12
|
+
.toggle-icon { font-size: 10px; transition: transform .2s }
|
|
13
|
+
.toggle-icon.open { transform: rotate(90deg) }
|
|
14
|
+
.advanced-content { padding-left: 16px; border-left: 2px solid var(--border-color); margin-left: 4px }
|
|
15
|
+
|
|
16
|
+
/* Header */
|
|
17
|
+
.saved-pages { font-size: 22px; font-weight: 700; min-height: var(--header-min-height); padding: var(--header-padding-vertical) var(--header-padding-horizontal); line-height: var(--header-line-height); display: flex; align-items: center; justify-content: center; box-sizing: border-box; background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); color: #fff; border-radius: 12px; width: 100%; box-shadow: 0 6px 25px var(--accent-glow); position: relative; z-index: 1; margin-bottom: 20px }
|
|
18
|
+
|
|
19
|
+
/* Category sections */
|
|
20
|
+
.page-category { width: 100%; margin-bottom: 20px; position: relative; z-index: 1 }
|
|
21
|
+
.category-title { font-size: 16px; color: var(--text-secondary); margin-bottom: 12px; padding-left: 5px; border-left: 3px solid var(--accent-tertiary) }
|
|
22
|
+
.page-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px }
|
|
23
|
+
|
|
24
|
+
/* Page links */
|
|
25
|
+
.page-link { padding: 12px 16px; background: linear-gradient(135deg, rgba(102,126,234,.15) 0, rgba(118,75,162,.15) 100%); border: 1px solid var(--border-color); border-radius: 10px; color: var(--text-primary); text-decoration: none; text-align: center; font-size: 14px; transition: .3s; cursor: pointer; user-select: none; white-space: nowrap; overflow: hidden; position: relative }
|
|
26
|
+
.page-link:hover { background: linear-gradient(135deg, rgba(102,126,234,.3) 0, rgba(118,75,162,.3) 100%); border-color: var(--text-secondary); box-shadow: 0 4px 15px var(--accent-glow); color: var(--accent-tertiary); transform: translateY(-2px) }
|
|
27
|
+
.page-link .scroll-text { display: inline-block; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis }
|
|
28
|
+
.page-link.scrolling .scroll-text { text-overflow: clip; overflow: visible; animation: scrollText var(--scroll-duration, 3s) linear infinite }
|
|
29
|
+
@keyframes scrollText { 0%, 10% { transform: translateX(0) } 100%, 90% { transform: translateX(var(--scroll-distance, -50%)) } }
|
|
30
|
+
|
|
31
|
+
/* Pinned pages */
|
|
32
|
+
.page-link.pinned { background: linear-gradient(135deg, rgba(102,126,234,.35) 0, rgba(118,75,162,.35) 100%); border: 2px solid var(--accent-tertiary); box-shadow: 0 2px 12px var(--accent-glow) }
|
|
33
|
+
.page-link.pinned:hover { background: linear-gradient(135deg, rgba(102,126,234,.5) 0, rgba(118,75,162,.5) 100%); box-shadow: 0 4px 20px var(--accent-glow) }
|
|
34
|
+
|
|
35
|
+
/* Builder page */
|
|
36
|
+
.page-link.builder-page { background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); color: #fff; border: none; box-shadow: 0 4px 20px var(--accent-glow); font-weight: 600 }
|
|
37
|
+
.page-link.builder-page:hover { background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); filter: brightness(1.1); box-shadow: 0 6px 25px var(--accent-glow); color: #fff; transform: translateY(-2px) }
|
|
38
|
+
|
|
39
|
+
/* Light mode pinned overrides */
|
|
40
|
+
.light-mode .page-link.pinned { background: linear-gradient(135deg, rgba(102,126,234,.3) 0, rgba(192,84,212,.25) 100%); border-color: var(--accent-secondary) }
|
|
41
|
+
.light-mode .page-link.pinned:hover { background: linear-gradient(135deg, rgba(102,126,234,.45) 0, rgba(192,84,212,.4) 100%) }
|
|
42
|
+
|
|
43
|
+
/* Filter bar */
|
|
44
|
+
.filter-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; flex-wrap: nowrap }
|
|
45
|
+
.filter-buttons-container { display: flex; gap: 8px; flex-shrink: 1; min-width: 0; overflow: hidden }
|
|
46
|
+
.filter-btn { padding: 6px 14px; border-radius: 20px; border: 1px solid var(--border-color); background: 0 0; color: var(--text-secondary); font-size: 13px; cursor: pointer; transition: .2s; white-space: nowrap; flex-shrink: 0 }
|
|
47
|
+
.filter-btn:hover { border-color: var(--accent-primary); color: var(--accent-primary) }
|
|
48
|
+
.filter-btn.active { background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); color: #fff; border-color: transparent }
|
|
49
|
+
|
|
50
|
+
/* More dropdown */
|
|
51
|
+
.more-dropdown { position: relative; flex-shrink: 0 }
|
|
52
|
+
.more-btn { position: relative }
|
|
53
|
+
.more-menu { position: absolute; top: 100%; left: 0; margin-top: 4px; min-width: 150px; max-height: 250px; overflow-y: auto; border-radius: 8px; padding: 4px 0; box-shadow: 0 8px 30px rgba(0,0,0,.4); display: none; border: 1px solid var(--border-color); background: rgba(30,30,50,.95); z-index: 100 }
|
|
54
|
+
.more-menu.show { display: block }
|
|
55
|
+
.more-menu-item { padding: 8px 16px; font-size: 13px; cursor: pointer; color: var(--text-primary); transition: background .15s; white-space: nowrap }
|
|
56
|
+
.more-menu-item:hover { background: linear-gradient(135deg, rgba(102,126,234,.3) 0, rgba(118,75,162,.3) 100%) }
|
|
57
|
+
.more-menu-item.active { background: linear-gradient(135deg, rgba(102,126,234,.2) 0, rgba(118,75,162,.2) 100%); color: var(--accent-tertiary) }
|
|
58
|
+
|
|
59
|
+
/* Search */
|
|
60
|
+
.search-input { margin-left: auto; padding: 6px 12px; border-radius: 20px; border: 1px solid var(--border-color); background: 0 0; color: var(--text-primary); font-size: 13px; outline: 0; width: 180px; min-width: 120px; flex-shrink: 0; transition: border-color .2s }
|
|
61
|
+
.search-input:focus { border-color: var(--accent-primary) }
|
|
62
|
+
.search-input::placeholder { color: var(--text-secondary) }
|
|
63
|
+
|
|
64
|
+
/* Context menu */
|
|
65
|
+
.context-menu { position: fixed; z-index: 1000; min-width: 180px; border-radius: 8px; padding: 4px 0; box-shadow: 0 8px 30px rgba(0,0,0,.4); display: none; border: 1px solid var(--border-color); background: rgba(30,30,50,.95) }
|
|
66
|
+
.context-menu-item { padding: 8px 16px; font-size: 13px; cursor: pointer; color: var(--text-primary); transition: background .15s }
|
|
67
|
+
.context-menu-item:hover { background: linear-gradient(135deg, rgba(102,126,234,.3) 0, rgba(118,75,162,.3) 100%) }
|
|
68
|
+
.light-mode .context-menu, .light-mode .more-menu { background: rgba(255,255,255,.97); box-shadow: 0 8px 30px rgba(0,0,0,.15) }
|
|
69
|
+
|
|
70
|
+
/* Upgrade badge & toast */
|
|
71
|
+
.version-badge { display: block; font-size: 10px; color: var(--accent-tertiary); margin-top: 4px; opacity: .8 }
|
|
72
|
+
.page-link.needs-upgrade { border: 1px dashed var(--accent-tertiary); background: linear-gradient(135deg, rgba(102,126,234,.08) 0, rgba(118,75,162,.08) 100%); animation: 3s ease-in-out infinite upgradeGlow; overflow: visible; z-index: 2 }
|
|
73
|
+
.page-link.needs-upgrade:hover { background: linear-gradient(135deg, rgba(102,126,234,.2) 0, rgba(118,75,162,.2) 100%); border-color: var(--accent-primary) }
|
|
74
|
+
@keyframes upgradeGlow { 0%, 100% { box-shadow: 0 0 0 transparent } 50% { box-shadow: 0 0 12px var(--accent-glow) } }
|
|
75
|
+
.upgrade-badge { position: absolute; top: -6px; right: -6px; font-size: 9px; color: #fff; font-weight: 600; background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); padding: 2px 7px; border-radius: 10px; line-height: 1.3; box-shadow: 0 2px 8px var(--accent-glow); pointer-events: none; z-index: 10 }
|
|
76
|
+
.light-mode .page-link.needs-upgrade { background: linear-gradient(135deg, rgba(102,126,234,.06) 0, rgba(192,84,212,.06) 100%) }
|
|
77
|
+
.upgrade-toast { position: fixed; bottom: 20px; right: 20px; padding: 12px 20px; background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); color: #fff; border-radius: 8px; font-size: 13px; box-shadow: 0 4px 15px var(--accent-glow); z-index: 2000; opacity: 0; transform: translateY(10px); transition: opacity .3s, transform .3s }
|
|
78
|
+
.upgrade-toast.show { opacity: 1; transform: translateY(0) }
|
|
79
|
+
</style>
|
|
15
80
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
|
|
16
81
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
|
|
17
82
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
|
|
18
|
-
<script id="page-info" src="/api/page-info.js?page=
|
|
83
|
+
<script id="page-info" src="/api/page-info.js?page=pages2"></script>
|
|
19
84
|
</head>
|
|
20
85
|
|
|
21
86
|
<body>
|
|
@@ -38,7 +103,8 @@
|
|
|
38
103
|
</form>
|
|
39
104
|
</div>
|
|
40
105
|
<div class="viewer-panel" id="viewerPanel" style="justify-content: flex-start; align-items: stretch;">
|
|
41
|
-
<div class="saved-pages">Pages</div>
|
|
106
|
+
<div class="saved-pages">Pages<button id="importBtn" style="position: absolute; right: 16px; padding: 4px 14px; border-radius: 8px; border: 1px solid rgba(255,255,255,.3); background: rgba(255,255,255,.15); color: #fff; font-size: 13px; cursor: pointer; transition: background .2s; line-height: 1.4;" onmouseover="this.style.background='rgba(255,255,255,.3)'" onmouseout="this.style.background='rgba(255,255,255,.15)'">Import</button></div>
|
|
107
|
+
<input type="file" id="importFileInput" accept=".zip" style="display:none">
|
|
42
108
|
|
|
43
109
|
<!-- Favorites section (hidden when empty) -->
|
|
44
110
|
<div id="favoritesSection" class="page-category" style="display: none;">
|
|
@@ -174,23 +240,680 @@
|
|
|
174
240
|
<div id="editMenuItem" class="context-menu-item">Edit page definition</div>
|
|
175
241
|
<div id="copyMenuItem" class="context-menu-item">Copy to new page</div>
|
|
176
242
|
<div id="updateMenuItem" class="context-menu-item">Update page</div>
|
|
243
|
+
<div style="height: 1px; background: var(--border-color); margin: 4px 0;"></div>
|
|
244
|
+
<div id="shareMenuItem" class="context-menu-item">Download page</div>
|
|
177
245
|
</div>
|
|
178
246
|
<div id="instructions" style="display: none;" data-locked="true"></div>
|
|
179
247
|
<div id="thoughts" style="display: none;" data-locked="true"></div>
|
|
180
|
-
<script id="pages-core">let allPages=[],activeCategory="All",searchTerm="",contextTarget=null;const BUILDER_PAGE="builder";let visibleCategories=[],overflowCategories=[];function displayName(page){return page.title||page.name}function createPageLink(page,isBuilder=!1){const a=document.createElement("a");a.href="/"+page.name,a.textContent=displayName(page);let classes="page-link";return isBuilder?classes+=" builder-page":page.pinned&&(classes+=" pinned"),a.className=classes,isBuilder||a.addEventListener("contextmenu",e=>{e.preventDefault(),showContextMenu(e,page)}),a}function renderFavorites(){const section=document.getElementById("favoritesSection"),grid=document.getElementById("favoritesGrid");grid.innerHTML="";const builderPage=allPages.find(p=>"builder"===p.name);builderPage&&grid.appendChild(createPageLink(builderPage,!0));const pinned=allPages.filter(p=>p.pinned&&"builder"!==p.name).sort((a,b)=>displayName(a).localeCompare(displayName(b)));pinned.forEach(p=>grid.appendChild(createPageLink(p))),builderPage||pinned.length>0?section.style.display="":section.style.display="none"}function getAllCategories(){const cats=new Set;return allPages.filter(p=>"builder"!==p.name).forEach(p=>p.categories.forEach(c=>cats.add(c))),Array.from(cats).sort()}function renderFilterBar(){const container=document.querySelector(".filter-buttons-container"),moreDropdown=document.getElementById("moreDropdown"),moreMenu=document.getElementById("moreMenu");container.innerHTML="",moreMenu.innerHTML="";const allBtn=document.createElement("button");allBtn.className="filter-btn"+("All"===activeCategory?" active":""),allBtn.textContent="All",allBtn.dataset.category="All",allBtn.addEventListener("click",()=>setCategory("All")),container.appendChild(allBtn),visibleCategories.forEach(cat=>{const btn=document.createElement("button");btn.className="filter-btn"+(activeCategory===cat?" active":""),btn.textContent=cat,btn.dataset.category=cat,btn.addEventListener("click",()=>setCategory(cat)),container.appendChild(btn)}),overflowCategories.length>0?(moreDropdown.style.display="",document.getElementById("moreBtn").className="filter-btn more-btn"+(overflowCategories.includes(activeCategory)?" active":""),overflowCategories.forEach(cat=>{const item=document.createElement("div");item.className="more-menu-item"+(activeCategory===cat?" active":""),item.textContent=cat,item.addEventListener("click",()=>{setCategory(cat),hideMoreMenu()}),moreMenu.appendChild(item)})):moreDropdown.style.display="none"}function calculateVisibleCategories(){const allCats=getAllCategories(),filterBar=document.getElementById("filterBar"),searchInput=document.getElementById("searchInput");if(!filterBar)return;let availableWidth=filterBar.offsetWidth-(searchInput.offsetWidth+8)-50-16;visibleCategories=[],overflowCategories=[];const temp=document.createElement("button");temp.className="filter-btn",temp.style.visibility="hidden",temp.style.position="absolute",document.body.appendChild(temp);let usedWidth=0,needsOverflow=!1;for(let i=0;i<allCats.length;i++){temp.textContent=allCats[i];const btnWidth=temp.offsetWidth+8,remainingCats=allCats.length-i;usedWidth+btnWidth+(needsOverflow?0:remainingCats>1?88:0)<=availableWidth&&!needsOverflow?(visibleCategories.push(allCats[i]),usedWidth+=btnWidth):(needsOverflow=!0,overflowCategories.push(allCats[i]))}1===overflowCategories.length&&visibleCategories.length>0&&(temp.textContent=overflowCategories[0],usedWidth+(temp.offsetWidth+8)<=availableWidth&&visibleCategories.push(overflowCategories.pop())),document.body.removeChild(temp)}function renderPages(){const grid=document.getElementById("pagesGrid");grid.innerHTML="";const term=searchTerm.toLowerCase();allPages.forEach(p=>{"builder"!==p.name&&("All"===activeCategory?(p.showInAll!==false):p.categories.includes(activeCategory))&&(!term||p.name.toLowerCase().includes(term)||displayName(p).toLowerCase().includes(term))&&grid.appendChild(createPageLink(p))})}function setCategory(cat){activeCategory=cat,renderFilterBar(),renderPages()}function renderAll(){renderFavorites(),calculateVisibleCategories(),renderFilterBar(),renderPages()}document.getElementById("searchInput").addEventListener("input",e=>{searchTerm=e.target.value,renderPages()});const moreBtn=document.getElementById("moreBtn"),moreMenu=document.getElementById("moreMenu");function hideMoreMenu(){moreMenu.classList.remove("show")}moreBtn.addEventListener("click",e=>{e.stopPropagation(),moreMenu.classList.toggle("show")});const contextMenu=document.getElementById("contextMenu"),pinMenuItem=document.getElementById("pinMenuItem");function showContextMenu(e,page){"builder"!==page.name&&(contextTarget=page,pinMenuItem.textContent=page.pinned?"Unpin from Favorites":"Pin to Favorites",contextMenu.style.left=e.clientX+"px",contextMenu.style.top=e.clientY+"px",contextMenu.style.display="block")}function hideContextMenu(){contextMenu.style.display="none",contextTarget=null}document.addEventListener("click",()=>{hideContextMenu(),hideMoreMenu()}),contextMenu.addEventListener("click",e=>e.stopPropagation()),pinMenuItem.addEventListener("click",async()=>{if(!contextTarget)return;const pageName=contextTarget.name,newPinned=!contextTarget.pinned,categories=contextTarget.categories||[];hideContextMenu();try{const res=await fetch("/api/pages/"+encodeURIComponent(pageName),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({categories:categories,pinned:newPinned})});if(!res.ok){const errorText=await res.text();throw new Error("Failed to update page metadata: "+errorText)}const pageIndex=allPages.findIndex(p=>p.name===pageName);-1!==pageIndex&&(allPages[pageIndex].pinned=newPinned,renderAll()),await loadPages()}catch(err){console.error("Error toggling pin:",err),alert("Failed to update pin status: "+err.message)}});const resizeObserver=new ResizeObserver(()=>{calculateVisibleCategories(),renderFilterBar()});async function loadPages(){try{const res=await fetch("/api/pages");if(!res.ok)throw new Error("Failed to fetch pages");allPages=await res.json(),renderAll()}catch(err){console.error("Error fetching pages:",err)}}resizeObserver.observe(document.getElementById("filterBar")),loadPages();</script><script id="context-menu-override">const originalShowContextMenu=showContextMenu;function createPageLinkUpdated(page,isBuilder=!1){const a=document.createElement("a");a.href="/"+page.name,a.textContent=displayName(page);let classes="page-link";return isBuilder?classes+=" builder-page":page.pinned&&(classes+=" pinned"),a.className=classes,a.addEventListener("contextmenu",e=>{e.preventDefault(),showContextMenu(e,page)}),a}showContextMenu=function(e,page){contextTarget=page;const isBuilder=page.name===BUILDER_PAGE;pinMenuItem.style.display=isBuilder?"none":"",editMenuItem.style.display=isBuilder?"none":"",isBuilder||(pinMenuItem.textContent=page.pinned?"Unpin from Favorites":"Pin to Favorites");var updateItem=document.getElementById("updateMenuItem"),latestVersion=window.pageInfo?window.pageInfo.latestPageVersion:0;updateItem.style.display=page.pageVersion<latestVersion?"":"none",contextMenu.style.left=e.clientX+"px",contextMenu.style.top=e.clientY+"px",contextMenu.style.display="block"},createPageLink=createPageLinkUpdated,allPages.length>0&&renderAll();</script><script id="page-link-scroll">createPageLink=function(page,isBuilder=!1){const a=document.createElement("a");a.href="/"+page.name;const title=displayName(page),span=document.createElement("span");span.className="scroll-text",span.textContent=title,a.appendChild(span),a.title=title;let classes="page-link";return isBuilder?classes+=" builder-page":page.pinned&&(classes+=" pinned"),a.className=classes,a.addEventListener("contextmenu",e=>{e.preventDefault(),showContextMenu(e,page)}),a.addEventListener("mouseenter",function(){const textWidth=span.scrollWidth,containerWidth=a.clientWidth-32;if(textWidth>containerWidth){const scrollDistance=textWidth-containerWidth+20,duration=Math.max(2,scrollDistance/30);a.style.setProperty("--scroll-distance",`-${scrollDistance}px`),a.style.setProperty("--scroll-duration",`${duration}s`),a.classList.add("scrolling")}}),a.addEventListener("mouseleave",function(){a.classList.remove("scrolling")}),a},"undefined"!=typeof allPages&&allPages&&allPages.length>0&&renderAll();</script>
|
|
181
|
-
|
|
182
|
-
<script id="modal-logic">const editPageModal=document.getElementById("editPageModal"),copyPageModal=document.getElementById("copyPageModal"),editMenuItem=document.getElementById("editMenuItem"),copyMenuItem=document.getElementById("copyMenuItem"),editPageName=document.getElementById("editPageName"),editPageTitle=document.getElementById("editPageTitle"),editPageCategories=document.getElementById("editPageCategories"),editPagePinned=document.getElementById("editPagePinned"),editCancelBtn=document.getElementById("editCancelBtn"),editSaveBtn=document.getElementById("editSaveBtn"),deletePageBtn=document.getElementById("deletePageBtn"),copySourcePage=document.getElementById("copySourcePage"),copyNewPageName=document.getElementById("copyNewPageName"),copyCancelBtn=document.getElementById("copyCancelBtn"),copyConfirmBtn=document.getElementById("copyConfirmBtn");let currentEditPage=null;function closeEditModal(){editPageModal.classList.remove("show"),currentEditPage=null}function closeCopyModal(){copyPageModal.classList.remove("show")}editMenuItem.addEventListener("click",()=>{contextTarget&&(currentEditPage=contextTarget,editPageName.value=contextTarget.name,editPageTitle.value=contextTarget.title||"",editPageCategories.value=(contextTarget.categories||[]).join(", "),editPagePinned.checked=contextTarget.pinned||!1,hideContextMenu(),editPageModal.classList.add("show"))}),copyMenuItem.addEventListener("click",()=>{contextTarget&&(copySourcePage.value=displayName(contextTarget),document.getElementById("copyPageTitle").value="",copyNewPageName.value="",hideContextMenu(),copyPageModal.classList.add("show"),document.getElementById("copyPageTitle").focus())}),editCancelBtn.addEventListener("click",closeEditModal),copyCancelBtn.addEventListener("click",closeCopyModal),editPageModal.addEventListener("click",e=>{e.target===editPageModal&&closeEditModal()}),copyPageModal.addEventListener("click",e=>{e.target===copyPageModal&&closeCopyModal()}),document.addEventListener("keydown",e=>{"Escape"===e.key&&(closeEditModal(),closeCopyModal())}),editSaveBtn.addEventListener("click",async()=>{if(!currentEditPage)return;const newTitle=editPageTitle.value.trim(),newCategories=editPageCategories.value.split(",").map(c=>c.trim()).filter(c=>c.length>0),newPinned=editPagePinned.checked,newMode=document.getElementById("editPageLocked").checked?"locked":"unlocked",newShowInAll=document.getElementById("editPageShowInAll").checked;try{if(!(await fetch("/api/pages/"+encodeURIComponent(currentEditPage.name),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({title:newTitle||void 0,categories:newCategories,pinned:newPinned,showInAll:newShowInAll,mode:newMode})})).ok)throw new Error("Failed to update page");closeEditModal(),await loadPages()}catch(err){console.error("Error saving page:",err),alert("Failed to save changes: "+err.message)}});const deleteConfirmModal=document.getElementById("deleteConfirmModal"),deletePageNameDisplay2=document.getElementById("deletePageNameDisplay2"),deleteCancelBtn=document.getElementById("deleteCancelBtn"),deleteConfirmBtn=document.getElementById("deleteConfirmBtn");let currentDeletePage=null;function closeDeleteModal(){deleteConfirmModal.classList.remove("show"),currentDeletePage=null}deleteCancelBtn.addEventListener("click",closeDeleteModal),deleteConfirmModal.addEventListener("click",e=>{e.target===deleteConfirmModal&&closeDeleteModal()}),deletePageBtn.addEventListener("click",()=>{currentEditPage&&(currentDeletePage=currentEditPage,deletePageNameDisplay2.textContent=displayName(currentEditPage),closeEditModal(),deleteConfirmModal.classList.add("show"))}),deleteConfirmBtn.addEventListener("click",async()=>{if(!currentDeletePage)return;const pageName=currentDeletePage.name;closeDeleteModal();try{const res=await fetch("/api/pages/"+encodeURIComponent(pageName),{method:"DELETE"});if(!res.ok){const data=await res.json().catch(()=>({}));throw new Error(data.error||"Failed to delete page")}await loadPages()}catch(err){console.error("Error deleting page:",err),alert("Failed to delete page: "+err.message)}}),document.addEventListener("keydown",e=>{"Escape"===e.key&&closeDeleteModal()});</script>
|
|
183
|
-
<script id="edit-modal-override">const editPageNameDisplay=document.getElementById("editPageNameDisplay");editMenuItem.removeEventListener("click",editMenuItem._handler),editMenuItem._handler=()=>{contextTarget&&(currentEditPage=contextTarget,editPageNameDisplay.textContent=contextTarget.name,editPageTitle.value=contextTarget.title||"",editPageCategories.value=(contextTarget.categories||[]).join(", "),editPagePinned.checked=contextTarget.pinned||!1,document.getElementById("editPageLocked").checked="locked"===contextTarget.mode,document.getElementById("editPageShowInAll").checked=contextTarget.showInAll!==!1,hideContextMenu(),editPageModal.classList.add("show"))},editMenuItem.addEventListener("click",editMenuItem._handler);</script>
|
|
184
|
-
<script id="error" type="application/json">{"message":"Something went wrong try again","details":"delete: node 53 not found"}</script>
|
|
185
|
-
<script id="copy-modal-rebind">const copySourcePageDisplay=document.getElementById("copySourcePageDisplay"),copyPageCategories=document.getElementById("copyPageCategories"),newCopyNewPageName=document.getElementById("copyNewPageName"),newCopyCancelBtn=document.getElementById("copyCancelBtn"),newCopyConfirmBtn=document.getElementById("copyConfirmBtn"),newCopyPageModal=document.getElementById("copyPageModal");let currentCopySource=null;function closeCopyModalNew(){newCopyPageModal.classList.remove("show"),currentCopySource=null}newCopyCancelBtn.addEventListener("click",closeCopyModalNew),newCopyPageModal.addEventListener("click",e=>{e.target===newCopyPageModal&&closeCopyModalNew()}),copyMenuItem.removeEventListener("click",copyMenuItem._handler),copyMenuItem._handler=()=>{contextTarget&&(currentCopySource=contextTarget,copySourcePageDisplay.textContent=displayName(contextTarget),document.getElementById("copyPageTitle").value="",copyPageCategories.value=(contextTarget.categories||[]).join(", "),newCopyNewPageName.value="",hideContextMenu(),newCopyPageModal.classList.add("show"),document.getElementById("copyPageTitle").focus())},copyMenuItem.addEventListener("click",copyMenuItem._handler);</script>
|
|
186
|
-
|
|
187
248
|
|
|
188
|
-
<script id="copy-confirm-handler">newCopyConfirmBtn.removeEventListener("click",newCopyConfirmBtn._handler),newCopyConfirmBtn._handler=async()=>{if(!currentCopySource)return;const newTitle=document.getElementById("copyPageTitle").value.trim();let newName=newCopyNewPageName.value.trim();if(!newName&&newTitle&&(newName=newTitle),!newName)return alert("Please enter a title or page name."),void document.getElementById("copyPageTitle").focus();if(newName=newName.toLowerCase().replace(/\s+/g,"_"),newName=newName.replace(/[^a-z0-9_-]/g,""),!newName)return alert("Page name must contain at least one valid character (letters, numbers, hyphens, or underscores)."),void document.getElementById("copyPageTitle").focus();const newCategories=copyPageCategories.value.split(",").map(c=>c.trim()).filter(c=>c.length>0);try{const body={name:newName,title:newTitle,categories:newCategories},res=await fetch("/api/pages/"+encodeURIComponent(currentCopySource.name)+"/copy",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});if(!res.ok){const data=await res.json().catch(()=>({}));throw new Error(data.error||"Failed to copy page")}closeCopyModalNew(),window.location.href="/"+newName}catch(err){console.error("Error copying page:",err),alert("Failed to copy page: "+err.message)}},newCopyConfirmBtn.addEventListener("click",newCopyConfirmBtn._handler);</script>
|
|
189
|
-
<script id="advanced-toggle">document.getElementById("advancedToggle").addEventListener("click",function(){const content=document.getElementById("advancedContent"),icon=this.querySelector(".toggle-icon");"none"===content.style.display?(content.style.display="block",icon.classList.add("open")):(content.style.display="none",icon.classList.remove("open"))});</script>
|
|
190
|
-
<script id="update-handler">document.getElementById("updateMenuItem").addEventListener("click",async function(){if(contextTarget){var pageName=contextTarget.name;hideContextMenu(),await upgradePage(pageName)}});</script>
|
|
191
249
|
<!-- Toast container -->
|
|
192
250
|
<div id="upgradeToast" class="upgrade-toast"></div>
|
|
193
|
-
|
|
251
|
+
|
|
252
|
+
<script>
|
|
253
|
+
(function() {
|
|
254
|
+
'use strict';
|
|
255
|
+
|
|
256
|
+
// ── State ──
|
|
257
|
+
var allPages = [];
|
|
258
|
+
var activeCategory = 'All';
|
|
259
|
+
var searchTerm = '';
|
|
260
|
+
var contextTarget = null;
|
|
261
|
+
var currentEditPage = null;
|
|
262
|
+
var currentCopySource = null;
|
|
263
|
+
var currentDeletePage = null;
|
|
264
|
+
var visibleCategories = [];
|
|
265
|
+
var overflowCategories = [];
|
|
266
|
+
var BUILDER_PAGE = 'builder';
|
|
267
|
+
|
|
268
|
+
// MRU: most-recently-used categories get priority for visible slots
|
|
269
|
+
var pagesMru = (function() {
|
|
270
|
+
try { return JSON.parse(localStorage.getItem('synthos_pagesMru')) || []; }
|
|
271
|
+
catch(e) { return []; }
|
|
272
|
+
})();
|
|
273
|
+
function pagesMruTouch(cat) {
|
|
274
|
+
if (cat === 'All') return;
|
|
275
|
+
pagesMru = pagesMru.filter(function(c) { return c !== cat; });
|
|
276
|
+
pagesMru.unshift(cat);
|
|
277
|
+
if (pagesMru.length > 20) pagesMru.length = 20;
|
|
278
|
+
try { localStorage.setItem('synthos_pagesMru', JSON.stringify(pagesMru)); } catch(e) {}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── DOM refs ──
|
|
282
|
+
var favoritesSection = document.getElementById('favoritesSection');
|
|
283
|
+
var favoritesGrid = document.getElementById('favoritesGrid');
|
|
284
|
+
var filterBar = document.getElementById('filterBar');
|
|
285
|
+
var pagesGrid = document.getElementById('pagesGrid');
|
|
286
|
+
var searchInput = document.getElementById('searchInput');
|
|
287
|
+
var moreDropdown = document.getElementById('moreDropdown');
|
|
288
|
+
var moreBtn = document.getElementById('moreBtn');
|
|
289
|
+
var moreMenu = document.getElementById('moreMenu');
|
|
290
|
+
var contextMenu = document.getElementById('contextMenu');
|
|
291
|
+
var pinMenuItem = document.getElementById('pinMenuItem');
|
|
292
|
+
var editMenuItem = document.getElementById('editMenuItem');
|
|
293
|
+
var copyMenuItem = document.getElementById('copyMenuItem');
|
|
294
|
+
var updateMenuItem = document.getElementById('updateMenuItem');
|
|
295
|
+
var editPageModal = document.getElementById('editPageModal');
|
|
296
|
+
var copyPageModal = document.getElementById('copyPageModal');
|
|
297
|
+
var deleteConfirmModal = document.getElementById('deleteConfirmModal');
|
|
298
|
+
var editPageNameDisplay = document.getElementById('editPageNameDisplay');
|
|
299
|
+
var editPageTitle = document.getElementById('editPageTitle');
|
|
300
|
+
var editPageCategories = document.getElementById('editPageCategories');
|
|
301
|
+
var editPagePinned = document.getElementById('editPagePinned');
|
|
302
|
+
var editPageLocked = document.getElementById('editPageLocked');
|
|
303
|
+
var editPageShowInAll = document.getElementById('editPageShowInAll');
|
|
304
|
+
var editCancelBtn = document.getElementById('editCancelBtn');
|
|
305
|
+
var editSaveBtn = document.getElementById('editSaveBtn');
|
|
306
|
+
var deletePageBtn = document.getElementById('deletePageBtn');
|
|
307
|
+
var copySourcePageDisplay = document.getElementById('copySourcePageDisplay');
|
|
308
|
+
var copyPageTitle = document.getElementById('copyPageTitle');
|
|
309
|
+
var copyPageCategories = document.getElementById('copyPageCategories');
|
|
310
|
+
var copyNewPageName = document.getElementById('copyNewPageName');
|
|
311
|
+
var copyCancelBtn = document.getElementById('copyCancelBtn');
|
|
312
|
+
var copyConfirmBtn = document.getElementById('copyConfirmBtn');
|
|
313
|
+
var deletePageNameDisplay2 = document.getElementById('deletePageNameDisplay2');
|
|
314
|
+
var deleteCancelBtn = document.getElementById('deleteCancelBtn');
|
|
315
|
+
var deleteConfirmBtn = document.getElementById('deleteConfirmBtn');
|
|
316
|
+
var loadingOverlay = document.getElementById('loadingOverlay');
|
|
317
|
+
var shareMenuItem = document.getElementById('shareMenuItem');
|
|
318
|
+
var importBtn = document.getElementById('importBtn');
|
|
319
|
+
var importFileInput = document.getElementById('importFileInput');
|
|
320
|
+
|
|
321
|
+
// ── Helpers ──
|
|
322
|
+
|
|
323
|
+
function displayName(page) {
|
|
324
|
+
return page.title || page.name;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Page link creation (final version: scroll-text + upgrade badge + context menu) ──
|
|
328
|
+
|
|
329
|
+
function createPageLink(page, isBuilder) {
|
|
330
|
+
var latestVersion = window.pageInfo ? window.pageInfo.latestPageVersion : 0;
|
|
331
|
+
var needsUpgrade = !isBuilder && page.pageVersion < latestVersion;
|
|
332
|
+
|
|
333
|
+
var a = document.createElement('a');
|
|
334
|
+
a.href = '/' + page.name;
|
|
335
|
+
|
|
336
|
+
var title = displayName(page);
|
|
337
|
+
var span = document.createElement('span');
|
|
338
|
+
span.className = 'scroll-text';
|
|
339
|
+
span.textContent = title;
|
|
340
|
+
a.appendChild(span);
|
|
341
|
+
a.title = title;
|
|
342
|
+
|
|
343
|
+
var classes = 'page-link';
|
|
344
|
+
if (isBuilder) {
|
|
345
|
+
classes += ' builder-page';
|
|
346
|
+
} else if (needsUpgrade) {
|
|
347
|
+
classes += ' needs-upgrade';
|
|
348
|
+
} else if (page.pinned) {
|
|
349
|
+
classes += ' pinned';
|
|
350
|
+
}
|
|
351
|
+
a.className = classes;
|
|
352
|
+
|
|
353
|
+
if (needsUpgrade) {
|
|
354
|
+
var badge = document.createElement('span');
|
|
355
|
+
badge.className = 'upgrade-badge';
|
|
356
|
+
badge.textContent = 'Update';
|
|
357
|
+
a.appendChild(badge);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (needsUpgrade) {
|
|
361
|
+
a.addEventListener('click', function(e) {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
upgradePage(page.name);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
a.addEventListener('contextmenu', function(e) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
showContextMenu(e, page);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
a.addEventListener('mouseenter', function() {
|
|
373
|
+
var textWidth = span.scrollWidth;
|
|
374
|
+
var containerWidth = a.clientWidth - 32;
|
|
375
|
+
if (textWidth > containerWidth) {
|
|
376
|
+
var scrollDistance = textWidth - containerWidth + 20;
|
|
377
|
+
var duration = Math.max(2, scrollDistance / 30);
|
|
378
|
+
a.style.setProperty('--scroll-distance', '-' + scrollDistance + 'px');
|
|
379
|
+
a.style.setProperty('--scroll-duration', duration + 's');
|
|
380
|
+
a.classList.add('scrolling');
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
a.addEventListener('mouseleave', function() {
|
|
385
|
+
a.classList.remove('scrolling');
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return a;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ── Context menu (final version: hides builder items, shows update for outdated) ──
|
|
392
|
+
|
|
393
|
+
function showContextMenu(e, page) {
|
|
394
|
+
contextTarget = page;
|
|
395
|
+
var isBuilder = page.name === BUILDER_PAGE;
|
|
396
|
+
|
|
397
|
+
pinMenuItem.style.display = isBuilder ? 'none' : '';
|
|
398
|
+
editMenuItem.style.display = isBuilder ? 'none' : '';
|
|
399
|
+
if (!isBuilder) {
|
|
400
|
+
pinMenuItem.textContent = page.pinned ? 'Unpin from Favorites' : 'Pin to Favorites';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
var latestVersion = window.pageInfo ? window.pageInfo.latestPageVersion : 0;
|
|
404
|
+
updateMenuItem.style.display = page.pageVersion < latestVersion ? '' : 'none';
|
|
405
|
+
|
|
406
|
+
shareMenuItem.style.display = isBuilder ? 'none' : '';
|
|
407
|
+
|
|
408
|
+
contextMenu.style.left = e.clientX + 'px';
|
|
409
|
+
contextMenu.style.top = e.clientY + 'px';
|
|
410
|
+
contextMenu.style.display = 'block';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function hideContextMenu() {
|
|
414
|
+
contextMenu.style.display = 'none';
|
|
415
|
+
contextTarget = null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function hideMoreMenu() {
|
|
419
|
+
moreMenu.classList.remove('show');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ── Rendering ──
|
|
423
|
+
|
|
424
|
+
function renderFavorites() {
|
|
425
|
+
favoritesGrid.innerHTML = '';
|
|
426
|
+
var builderPage = allPages.find(function(p) { return p.name === 'builder'; });
|
|
427
|
+
if (builderPage) favoritesGrid.appendChild(createPageLink(builderPage, true));
|
|
428
|
+
|
|
429
|
+
var pinned = allPages
|
|
430
|
+
.filter(function(p) { return p.pinned && p.name !== 'builder'; })
|
|
431
|
+
.sort(function(a, b) { return displayName(a).localeCompare(displayName(b)); });
|
|
432
|
+
pinned.forEach(function(p) { favoritesGrid.appendChild(createPageLink(p)); });
|
|
433
|
+
|
|
434
|
+
favoritesSection.style.display = (builderPage || pinned.length > 0) ? '' : 'none';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function getAllCategories() {
|
|
438
|
+
var cats = new Set();
|
|
439
|
+
allPages
|
|
440
|
+
.filter(function(p) { return p.name !== 'builder'; })
|
|
441
|
+
.forEach(function(p) { p.categories.forEach(function(c) { cats.add(c); }); });
|
|
442
|
+
return Array.from(cats).sort();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function renderFilterBar() {
|
|
446
|
+
var container = document.querySelector('.filter-buttons-container');
|
|
447
|
+
container.innerHTML = '';
|
|
448
|
+
moreMenu.innerHTML = '';
|
|
449
|
+
|
|
450
|
+
var allBtn = document.createElement('button');
|
|
451
|
+
allBtn.className = 'filter-btn' + (activeCategory === 'All' ? ' active' : '');
|
|
452
|
+
allBtn.textContent = 'All';
|
|
453
|
+
allBtn.dataset.category = 'All';
|
|
454
|
+
allBtn.addEventListener('click', function() { setCategory('All'); });
|
|
455
|
+
container.appendChild(allBtn);
|
|
456
|
+
|
|
457
|
+
visibleCategories.forEach(function(cat) {
|
|
458
|
+
var btn = document.createElement('button');
|
|
459
|
+
btn.className = 'filter-btn' + (activeCategory === cat ? ' active' : '');
|
|
460
|
+
btn.textContent = cat;
|
|
461
|
+
btn.dataset.category = cat;
|
|
462
|
+
btn.addEventListener('click', function() { setCategory(cat); });
|
|
463
|
+
container.appendChild(btn);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (overflowCategories.length > 0) {
|
|
467
|
+
moreDropdown.style.display = '';
|
|
468
|
+
moreBtn.className = 'filter-btn more-btn' + (overflowCategories.includes(activeCategory) ? ' active' : '');
|
|
469
|
+
overflowCategories.forEach(function(cat) {
|
|
470
|
+
var item = document.createElement('div');
|
|
471
|
+
item.className = 'more-menu-item' + (activeCategory === cat ? ' active' : '');
|
|
472
|
+
item.textContent = cat;
|
|
473
|
+
item.addEventListener('click', function() { setCategory(cat); hideMoreMenu(); });
|
|
474
|
+
moreMenu.appendChild(item);
|
|
475
|
+
});
|
|
476
|
+
} else {
|
|
477
|
+
moreDropdown.style.display = 'none';
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function calculateVisibleCategories() {
|
|
482
|
+
var allCats = getAllCategories();
|
|
483
|
+
if (!filterBar) return;
|
|
484
|
+
|
|
485
|
+
var availableWidth = filterBar.offsetWidth - (searchInput.offsetWidth + 8) - 50 - 16;
|
|
486
|
+
visibleCategories = [];
|
|
487
|
+
overflowCategories = [];
|
|
488
|
+
|
|
489
|
+
var temp = document.createElement('button');
|
|
490
|
+
temp.className = 'filter-btn';
|
|
491
|
+
temp.style.visibility = 'hidden';
|
|
492
|
+
temp.style.position = 'absolute';
|
|
493
|
+
document.body.appendChild(temp);
|
|
494
|
+
|
|
495
|
+
// Measure "More" button width
|
|
496
|
+
temp.textContent = 'More \u25BE';
|
|
497
|
+
var moreBtnWidth = temp.offsetWidth + 8;
|
|
498
|
+
|
|
499
|
+
// Measure each category button
|
|
500
|
+
var catWidths = {};
|
|
501
|
+
allCats.forEach(function(cat) {
|
|
502
|
+
temp.textContent = cat;
|
|
503
|
+
catWidths[cat] = temp.offsetWidth + 8;
|
|
504
|
+
});
|
|
505
|
+
document.body.removeChild(temp);
|
|
506
|
+
|
|
507
|
+
// Sort categories: MRU first, then alphabetical
|
|
508
|
+
var sorted = allCats.slice().sort(function(a, b) {
|
|
509
|
+
var ai = pagesMru.indexOf(a);
|
|
510
|
+
var bi = pagesMru.indexOf(b);
|
|
511
|
+
var aInMru = ai !== -1;
|
|
512
|
+
var bInMru = bi !== -1;
|
|
513
|
+
if (aInMru && !bInMru) return -1;
|
|
514
|
+
if (!aInMru && bInMru) return 1;
|
|
515
|
+
if (aInMru && bInMru) return ai - bi;
|
|
516
|
+
return a.localeCompare(b);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
var usedWidth = 0;
|
|
520
|
+
var overflowing = false;
|
|
521
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
522
|
+
var cat = sorted[i];
|
|
523
|
+
var remaining = sorted.length - i;
|
|
524
|
+
if (!overflowing && usedWidth + catWidths[cat] <= availableWidth - (remaining > 1 ? moreBtnWidth : 0)) {
|
|
525
|
+
visibleCategories.push(cat);
|
|
526
|
+
usedWidth += catWidths[cat];
|
|
527
|
+
} else {
|
|
528
|
+
overflowing = true;
|
|
529
|
+
overflowCategories.push(cat);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (overflowCategories.length === 1) {
|
|
533
|
+
if (usedWidth + catWidths[overflowCategories[0]] <= availableWidth) {
|
|
534
|
+
visibleCategories.push(overflowCategories.pop());
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function renderPages() {
|
|
540
|
+
pagesGrid.innerHTML = '';
|
|
541
|
+
var term = searchTerm.toLowerCase();
|
|
542
|
+
allPages.forEach(function(p) {
|
|
543
|
+
if (p.name === 'builder') return;
|
|
544
|
+
var inCategory = activeCategory === 'All'
|
|
545
|
+
? (term || p.showInAll !== false)
|
|
546
|
+
: p.categories.includes(activeCategory);
|
|
547
|
+
if (!inCategory) return;
|
|
548
|
+
if (term && !p.name.toLowerCase().includes(term) && !displayName(p).toLowerCase().includes(term)) return;
|
|
549
|
+
pagesGrid.appendChild(createPageLink(p));
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function setCategory(cat) {
|
|
554
|
+
activeCategory = cat;
|
|
555
|
+
pagesMruTouch(cat);
|
|
556
|
+
renderFilterBar();
|
|
557
|
+
renderPages();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function renderAll() {
|
|
561
|
+
renderFavorites();
|
|
562
|
+
calculateVisibleCategories();
|
|
563
|
+
renderFilterBar();
|
|
564
|
+
renderPages();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ── Upgrade logic ──
|
|
568
|
+
|
|
569
|
+
function showUpgradeToast(message) {
|
|
570
|
+
var toast = document.getElementById('upgradeToast');
|
|
571
|
+
toast.textContent = message;
|
|
572
|
+
toast.classList.add('show');
|
|
573
|
+
setTimeout(function() { toast.classList.remove('show'); }, 3000);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async function upgradePage(pageName) {
|
|
577
|
+
loadingOverlay.style.display = 'flex';
|
|
578
|
+
try {
|
|
579
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName) + '/upgrade', {
|
|
580
|
+
method: 'POST',
|
|
581
|
+
headers: { 'Content-Type': 'application/json' }
|
|
582
|
+
});
|
|
583
|
+
var data = await res.json();
|
|
584
|
+
if (!res.ok) throw new Error(data.error || 'Upgrade failed');
|
|
585
|
+
if (!data.upgraded) return false;
|
|
586
|
+
await loadPages();
|
|
587
|
+
showUpgradeToast('Page upgraded successfully');
|
|
588
|
+
return true;
|
|
589
|
+
} catch (err) {
|
|
590
|
+
console.error('Error upgrading page:', err);
|
|
591
|
+
alert('Failed to upgrade page: ' + err.message);
|
|
592
|
+
return false;
|
|
593
|
+
} finally {
|
|
594
|
+
loadingOverlay.style.display = 'none';
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ── Modal helpers ──
|
|
599
|
+
|
|
600
|
+
function closeEditModal() {
|
|
601
|
+
editPageModal.classList.remove('show');
|
|
602
|
+
currentEditPage = null;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function closeCopyModal() {
|
|
606
|
+
copyPageModal.classList.remove('show');
|
|
607
|
+
currentCopySource = null;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function closeDeleteModal() {
|
|
611
|
+
deleteConfirmModal.classList.remove('show');
|
|
612
|
+
currentDeletePage = null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ── Event listeners ──
|
|
616
|
+
|
|
617
|
+
// Search
|
|
618
|
+
searchInput.addEventListener('input', function(e) {
|
|
619
|
+
searchTerm = e.target.value;
|
|
620
|
+
renderPages();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// More dropdown
|
|
624
|
+
moreBtn.addEventListener('click', function(e) {
|
|
625
|
+
e.stopPropagation();
|
|
626
|
+
moreMenu.classList.toggle('show');
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// Close menus on click
|
|
630
|
+
document.addEventListener('click', function() {
|
|
631
|
+
hideContextMenu();
|
|
632
|
+
hideMoreMenu();
|
|
633
|
+
});
|
|
634
|
+
contextMenu.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
635
|
+
|
|
636
|
+
// ── Pin handler ──
|
|
637
|
+
pinMenuItem.addEventListener('click', async function() {
|
|
638
|
+
if (!contextTarget) return;
|
|
639
|
+
var pageName = contextTarget.name;
|
|
640
|
+
var newPinned = !contextTarget.pinned;
|
|
641
|
+
var categories = contextTarget.categories || [];
|
|
642
|
+
hideContextMenu();
|
|
643
|
+
try {
|
|
644
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName), {
|
|
645
|
+
method: 'POST',
|
|
646
|
+
headers: { 'Content-Type': 'application/json' },
|
|
647
|
+
body: JSON.stringify({ categories: categories, pinned: newPinned })
|
|
648
|
+
});
|
|
649
|
+
if (!res.ok) {
|
|
650
|
+
var errorText = await res.text();
|
|
651
|
+
throw new Error('Failed to update page metadata: ' + errorText);
|
|
652
|
+
}
|
|
653
|
+
var pageIndex = allPages.findIndex(function(p) { return p.name === pageName; });
|
|
654
|
+
if (pageIndex !== -1) allPages[pageIndex].pinned = newPinned;
|
|
655
|
+
renderAll();
|
|
656
|
+
await loadPages();
|
|
657
|
+
} catch (err) {
|
|
658
|
+
console.error('Error toggling pin:', err);
|
|
659
|
+
alert('Failed to update pin status: ' + err.message);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// ── Edit modal ──
|
|
664
|
+
editMenuItem.addEventListener('click', function() {
|
|
665
|
+
if (!contextTarget) return;
|
|
666
|
+
currentEditPage = contextTarget;
|
|
667
|
+
editPageNameDisplay.textContent = contextTarget.name;
|
|
668
|
+
editPageTitle.value = contextTarget.title || '';
|
|
669
|
+
editPageCategories.value = (contextTarget.categories || []).join(', ');
|
|
670
|
+
editPagePinned.checked = contextTarget.pinned || false;
|
|
671
|
+
editPageLocked.checked = contextTarget.mode === 'locked';
|
|
672
|
+
editPageShowInAll.checked = contextTarget.showInAll !== false;
|
|
673
|
+
hideContextMenu();
|
|
674
|
+
editPageModal.classList.add('show');
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
editCancelBtn.addEventListener('click', closeEditModal);
|
|
678
|
+
|
|
679
|
+
editSaveBtn.addEventListener('click', async function() {
|
|
680
|
+
if (!currentEditPage) return;
|
|
681
|
+
var newTitle = editPageTitle.value.trim();
|
|
682
|
+
var newCategories = editPageCategories.value.split(',').map(function(c) { return c.trim(); }).filter(function(c) { return c.length > 0; });
|
|
683
|
+
var newPinned = editPagePinned.checked;
|
|
684
|
+
var newMode = editPageLocked.checked ? 'locked' : 'unlocked';
|
|
685
|
+
var newShowInAll = editPageShowInAll.checked;
|
|
686
|
+
try {
|
|
687
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(currentEditPage.name), {
|
|
688
|
+
method: 'POST',
|
|
689
|
+
headers: { 'Content-Type': 'application/json' },
|
|
690
|
+
body: JSON.stringify({
|
|
691
|
+
title: newTitle || undefined,
|
|
692
|
+
categories: newCategories,
|
|
693
|
+
pinned: newPinned,
|
|
694
|
+
showInAll: newShowInAll,
|
|
695
|
+
mode: newMode
|
|
696
|
+
})
|
|
697
|
+
});
|
|
698
|
+
if (!res.ok) throw new Error('Failed to update page');
|
|
699
|
+
closeEditModal();
|
|
700
|
+
await loadPages();
|
|
701
|
+
} catch (err) {
|
|
702
|
+
console.error('Error saving page:', err);
|
|
703
|
+
alert('Failed to save changes: ' + err.message);
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Edit modal mousedown-guarded close
|
|
708
|
+
var editModalMouseDown = null;
|
|
709
|
+
editPageModal.addEventListener('mousedown', function(e) { editModalMouseDown = e.target; });
|
|
710
|
+
editPageModal.addEventListener('click', function(e) {
|
|
711
|
+
if (e.target === editPageModal && editModalMouseDown === editPageModal) closeEditModal();
|
|
712
|
+
editModalMouseDown = null;
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// ── Delete modal ──
|
|
716
|
+
deletePageBtn.addEventListener('click', function() {
|
|
717
|
+
if (!currentEditPage) return;
|
|
718
|
+
currentDeletePage = currentEditPage;
|
|
719
|
+
deletePageNameDisplay2.textContent = displayName(currentEditPage);
|
|
720
|
+
closeEditModal();
|
|
721
|
+
deleteConfirmModal.classList.add('show');
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
deleteCancelBtn.addEventListener('click', closeDeleteModal);
|
|
725
|
+
|
|
726
|
+
deleteConfirmBtn.addEventListener('click', async function() {
|
|
727
|
+
if (!currentDeletePage) return;
|
|
728
|
+
var pageName = currentDeletePage.name;
|
|
729
|
+
closeDeleteModal();
|
|
730
|
+
try {
|
|
731
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName), { method: 'DELETE' });
|
|
732
|
+
if (!res.ok) {
|
|
733
|
+
var data = await res.json().catch(function() { return {}; });
|
|
734
|
+
throw new Error(data.error || 'Failed to delete page');
|
|
735
|
+
}
|
|
736
|
+
await loadPages();
|
|
737
|
+
} catch (err) {
|
|
738
|
+
console.error('Error deleting page:', err);
|
|
739
|
+
alert('Failed to delete page: ' + err.message);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Delete modal mousedown-guarded close
|
|
744
|
+
var deleteModalMouseDown = null;
|
|
745
|
+
deleteConfirmModal.addEventListener('mousedown', function(e) { deleteModalMouseDown = e.target; });
|
|
746
|
+
deleteConfirmModal.addEventListener('click', function(e) {
|
|
747
|
+
if (e.target === deleteConfirmModal && deleteModalMouseDown === deleteConfirmModal) closeDeleteModal();
|
|
748
|
+
deleteModalMouseDown = null;
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// ── Copy modal ──
|
|
752
|
+
copyMenuItem.addEventListener('click', function() {
|
|
753
|
+
if (!contextTarget) return;
|
|
754
|
+
currentCopySource = contextTarget;
|
|
755
|
+
copySourcePageDisplay.textContent = displayName(contextTarget);
|
|
756
|
+
copyPageTitle.value = '';
|
|
757
|
+
copyPageCategories.value = (contextTarget.categories || []).join(', ');
|
|
758
|
+
copyNewPageName.value = '';
|
|
759
|
+
hideContextMenu();
|
|
760
|
+
copyPageModal.classList.add('show');
|
|
761
|
+
copyPageTitle.focus();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
copyCancelBtn.addEventListener('click', closeCopyModal);
|
|
765
|
+
|
|
766
|
+
copyConfirmBtn.addEventListener('click', async function() {
|
|
767
|
+
if (!currentCopySource) return;
|
|
768
|
+
var newTitle = copyPageTitle.value.trim();
|
|
769
|
+
var newName = copyNewPageName.value.trim();
|
|
770
|
+
if (!newName && newTitle) newName = newTitle;
|
|
771
|
+
if (!newName) {
|
|
772
|
+
alert('Please enter a title or page name.');
|
|
773
|
+
copyPageTitle.focus();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
newName = newName.toLowerCase().replace(/\s+/g, '_');
|
|
777
|
+
newName = newName.replace(/[^a-z0-9_-]/g, '');
|
|
778
|
+
if (!newName) {
|
|
779
|
+
alert('Page name must contain at least one valid character (letters, numbers, hyphens, or underscores).');
|
|
780
|
+
copyPageTitle.focus();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
var newCategories = copyPageCategories.value.split(',').map(function(c) { return c.trim(); }).filter(function(c) { return c.length > 0; });
|
|
784
|
+
try {
|
|
785
|
+
var body = { name: newName, title: newTitle, categories: newCategories };
|
|
786
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(currentCopySource.name) + '/copy', {
|
|
787
|
+
method: 'POST',
|
|
788
|
+
headers: { 'Content-Type': 'application/json' },
|
|
789
|
+
body: JSON.stringify(body)
|
|
790
|
+
});
|
|
791
|
+
if (!res.ok) {
|
|
792
|
+
var data = await res.json().catch(function() { return {}; });
|
|
793
|
+
throw new Error(data.error || 'Failed to copy page');
|
|
794
|
+
}
|
|
795
|
+
closeCopyModal();
|
|
796
|
+
window.location.href = '/' + newName;
|
|
797
|
+
} catch (err) {
|
|
798
|
+
console.error('Error copying page:', err);
|
|
799
|
+
alert('Failed to copy page: ' + err.message);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// Copy modal mousedown-guarded close
|
|
804
|
+
var copyModalMouseDown = null;
|
|
805
|
+
copyPageModal.addEventListener('mousedown', function(e) { copyModalMouseDown = e.target; });
|
|
806
|
+
copyPageModal.addEventListener('click', function(e) {
|
|
807
|
+
if (e.target === copyPageModal && copyModalMouseDown === copyPageModal) closeCopyModal();
|
|
808
|
+
copyModalMouseDown = null;
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// ── Advanced toggle ──
|
|
812
|
+
document.getElementById('advancedToggle').addEventListener('click', function() {
|
|
813
|
+
var content = document.getElementById('advancedContent');
|
|
814
|
+
var icon = this.querySelector('.toggle-icon');
|
|
815
|
+
if (content.style.display === 'none') {
|
|
816
|
+
content.style.display = 'block';
|
|
817
|
+
icon.classList.add('open');
|
|
818
|
+
} else {
|
|
819
|
+
content.style.display = 'none';
|
|
820
|
+
icon.classList.remove('open');
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
// ── Update menu item ──
|
|
825
|
+
updateMenuItem.addEventListener('click', async function() {
|
|
826
|
+
if (!contextTarget) return;
|
|
827
|
+
var pageName = contextTarget.name;
|
|
828
|
+
hideContextMenu();
|
|
829
|
+
await upgradePage(pageName);
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
// ── Share handler ──
|
|
833
|
+
shareMenuItem.addEventListener('click', async function() {
|
|
834
|
+
if (!contextTarget) return;
|
|
835
|
+
var page = contextTarget;
|
|
836
|
+
var pageName = page.name;
|
|
837
|
+
hideContextMenu();
|
|
838
|
+
try {
|
|
839
|
+
var res = await fetch('/api/pages/' + encodeURIComponent(pageName) + '/export');
|
|
840
|
+
if (!res.ok) throw new Error('Failed to export page');
|
|
841
|
+
var blob = await res.blob();
|
|
842
|
+
var fileName = pageName + '.zip';
|
|
843
|
+
var url = URL.createObjectURL(blob);
|
|
844
|
+
var a = document.createElement('a');
|
|
845
|
+
a.href = url;
|
|
846
|
+
a.download = fileName;
|
|
847
|
+
document.body.appendChild(a);
|
|
848
|
+
a.click();
|
|
849
|
+
document.body.removeChild(a);
|
|
850
|
+
URL.revokeObjectURL(url);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
console.error('Error sharing page:', err);
|
|
853
|
+
alert('Failed to share page: ' + err.message);
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// ── Import handler ──
|
|
858
|
+
importBtn.addEventListener('click', function() {
|
|
859
|
+
importFileInput.click();
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
importFileInput.addEventListener('change', async function() {
|
|
863
|
+
var file = importFileInput.files[0];
|
|
864
|
+
if (!file) return;
|
|
865
|
+
importFileInput.value = '';
|
|
866
|
+
loadingOverlay.style.display = 'flex';
|
|
867
|
+
try {
|
|
868
|
+
var buffer = await file.arrayBuffer();
|
|
869
|
+
var res = await fetch('/api/pages/import', {
|
|
870
|
+
method: 'POST',
|
|
871
|
+
headers: { 'Content-Type': 'application/zip' },
|
|
872
|
+
body: buffer
|
|
873
|
+
});
|
|
874
|
+
var data = await res.json();
|
|
875
|
+
if (!res.ok) throw new Error(data.error || 'Import failed');
|
|
876
|
+
await loadPages();
|
|
877
|
+
showUpgradeToast('Imported page: ' + (data.title || data.name));
|
|
878
|
+
} catch (err) {
|
|
879
|
+
console.error('Error importing page:', err);
|
|
880
|
+
alert('Failed to import page: ' + err.message);
|
|
881
|
+
} finally {
|
|
882
|
+
loadingOverlay.style.display = 'none';
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// ── Escape key closes all modals ──
|
|
887
|
+
document.addEventListener('keydown', function(e) {
|
|
888
|
+
if (e.key === 'Escape') {
|
|
889
|
+
closeEditModal();
|
|
890
|
+
closeCopyModal();
|
|
891
|
+
closeDeleteModal();
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// ── Resize observer for filter bar ──
|
|
896
|
+
var resizeObserver = new ResizeObserver(function() {
|
|
897
|
+
calculateVisibleCategories();
|
|
898
|
+
renderFilterBar();
|
|
899
|
+
});
|
|
900
|
+
resizeObserver.observe(filterBar);
|
|
901
|
+
|
|
902
|
+
// ── Load pages and render ──
|
|
903
|
+
async function loadPages() {
|
|
904
|
+
try {
|
|
905
|
+
var res = await fetch('/api/pages');
|
|
906
|
+
if (!res.ok) throw new Error('Failed to fetch pages');
|
|
907
|
+
allPages = await res.json();
|
|
908
|
+
renderAll();
|
|
909
|
+
} catch (err) {
|
|
910
|
+
console.error('Error fetching pages:', err);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
loadPages();
|
|
915
|
+
})();
|
|
916
|
+
</script>
|
|
194
917
|
<script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
|
|
195
918
|
<script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
|
|
196
|
-
</body></html>
|
|
919
|
+
</body></html>
|