From 7d4efa6c9d357808ea9c21296358100e6b237b08 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:27:38 +0000 Subject: [PATCH 01/16] docs: add Chronicle Map domain glossary Start CONTEXT.md with Chronicle as the aggregate root for the planned chronicle-map feature. Co-authored-by: Sad --- CONTEXT.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 CONTEXT.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000000..9b5efa883b --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,9 @@ +# Chronicle Map + +A first-class chronicle system inside Wiki.js for pinning historical (or fictional) events onto era-specific maps, with page embeds and a master overview. + +## Language + +**Chronicle**: +A first-class aggregate that owns era maps, events, and its own tags. Wiki pages embed or link to it; they do not own its data. +_Avoid_: Map collection, project, atlas (as the top-level container) From c4a7ad6dff5a3df131687cffa3f35b120b3559e0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:36:48 +0000 Subject: [PATCH 02/16] docs: define Event as Chronicle-owned pin entity Events are first-class pins with optional Wiki Page links, not page-equivalent content or marker-only stubs. Co-authored-by: Sad --- CONTEXT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index 9b5efa883b..c8c36f6395 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,3 +7,7 @@ A first-class chronicle system inside Wiki.js for pinning historical (or fiction **Chronicle**: A first-class aggregate that owns era maps, events, and its own tags. Wiki pages embed or link to it; they do not own its data. _Avoid_: Map collection, project, atlas (as the top-level container) + +**Event**: +A pin-able record inside a Chronicle — title, summary, when/where it occurred, and tags. May optionally link to one or more Wiki Pages for long-form detail; the pin itself is not a Page. +_Avoid_: Page (for the pin), marker-only stub with no domain identity From a8ae901942f16a0ba8eda0016181b4eb6d28a05f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:37:31 +0000 Subject: [PATCH 03/16] docs: define Era Map as dated Chronicle basemap Era Maps are per-era basemaps with alignment to a shared spatial reference, not single-layer filters or per-event assets. Co-authored-by: Sad --- CONTEXT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index c8c36f6395..b53e66b19b 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -11,3 +11,7 @@ _Avoid_: Map collection, project, atlas (as the top-level container) **Event**: A pin-able record inside a Chronicle — title, summary, when/where it occurred, and tags. May optionally link to one or more Wiki Pages for long-form detail; the pin itself is not a Page. _Avoid_: Page (for the pin), marker-only stub with no domain identity + +**Era Map**: +A dated basemap (2D imagery or 3D scene) owned by a Chronicle, covering a time range. Multiple Era Maps coexist; each can be aligned to a shared spatial reference so the same Event can appear across eras despite scale and position drift. +_Avoid_: Layer (as the map entity), basemap-only asset with no era identity From 3faaed13bc8a6765dc44916ee8e1c548d401b967 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:38:51 +0000 Subject: [PATCH 04/16] docs: define Canonical Position, Alignment, Pin Override Hybrid placement: Events store one canonical position; Era Maps align via transforms; Pin Override is the per-map escape hatch. Co-authored-by: Sad --- CONTEXT.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index b53e66b19b..cf6e021def 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -15,3 +15,15 @@ _Avoid_: Page (for the pin), marker-only stub with no domain identity **Era Map**: A dated basemap (2D imagery or 3D scene) owned by a Chronicle, covering a time range. Multiple Era Maps coexist; each can be aligned to a shared spatial reference so the same Event can appear across eras despite scale and position drift. _Avoid_: Layer (as the map entity), basemap-only asset with no era identity + +**Canonical Position**: +The Event's single authoritative location in the Chronicle's shared spatial reference (typically modern lat/lng, or a custom CRS for fictional worlds). Default source for projecting pins onto every Era Map. +_Avoid_: Per-map coordinates (as the primary store), "roughly around here" without a stored point + +**Alignment**: +A transform on an Era Map (control points / warp) that maps Canonical Positions onto that basemap's pixel or local space, correcting scale and position drift between eras. +_Avoid_: Manual re-pinning every Event per map, ad-hoc UI pan/zoom offsets + +**Pin Override**: +An optional per–Event-per–Era-Map position that replaces the Alignment projection when the default transform is wrong for that pin. Escape hatch only — Canonical Position remains the default. +_Avoid_: Making per-map pins the normal path for every Event From f32abca9d7b7ebe5c535d3231dbef2c3a9ecbbe9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:39:22 +0000 Subject: [PATCH 05/16] docs: define Occurrence and Visibility Override Events show on Era Maps by time-range intersection by default, with force include/exclude as the editor escape hatch. Co-authored-by: Sad --- CONTEXT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index cf6e021def..cc049b8f17 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -27,3 +27,11 @@ _Avoid_: Manual re-pinning every Event per map, ad-hoc UI pan/zoom offsets **Pin Override**: An optional per–Event-per–Era-Map position that replaces the Alignment projection when the default transform is wrong for that pin. Escape hatch only — Canonical Position remains the default. _Avoid_: Making per-map pins the normal path for every Event + +**Occurrence**: +When an Event happened — a point or interval on the Chronicle timeline (may be approximate). Used to decide default Era Map visibility by intersection with each Era Map's time range. +_Avoid_: "Date" alone when an interval or fuzzy span is meant; publication date of the wiki page + +**Visibility Override**: +An optional per–Event-per–Era-Map force include or force exclude that replaces the default Occurrence∩Era-Map time-range rule. +_Avoid_: Manually listing every Era Map for every Event as the normal path From 7e05cb2ccd997a83ee8e4cb30f7e868843544f9f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:42:46 +0000 Subject: [PATCH 06/16] docs: separate Chronicle Tag from Page Tag Map filters use Chronicle-scoped tags; linked wiki pages keep existing Page Tags without merging the taxonomies. Co-authored-by: Sad --- CONTEXT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index cc049b8f17..96d9a51f37 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -35,3 +35,11 @@ _Avoid_: "Date" alone when an interval or fuzzy span is meant; publication date **Visibility Override**: An optional per–Event-per–Era-Map force include or force exclude that replaces the default Occurrence∩Era-Map time-range rule. _Avoid_: Manually listing every Era Map for every Event as the normal path + +**Chronicle Tag**: +A Chronicle-scoped label for filtering and grouping Events on maps (e.g. haunt, traffic death, urban, outlying islands). Independent of Wiki.js Page Tags. +_Avoid_: Page Tag (for map filters), global taxonomy shared across unrelated Chronicles + +**Page Tag**: +The existing Wiki.js flat tag on a Wiki Page. Used only for wiki content; not the primary map filter. An Event's linked Pages keep their own Page Tags. +_Avoid_: Reusing Page Tags as the Chronicle's event taxonomy From 76d4c9c73b073b841f55be11c550be6902a61f42 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:43:55 +0000 Subject: [PATCH 07/16] docs: define Chronicle Overlay as optional multi-chronicle view Worlds stay as separate Chronicles; mixing is a toggleable overlay view, not an in-chronicle world-layer dimension. Co-authored-by: Sad --- CONTEXT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index 96d9a51f37..c4f9130c0f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -43,3 +43,7 @@ _Avoid_: Page Tag (for map filters), global taxonomy shared across unrelated Chr **Page Tag**: The existing Wiki.js flat tag on a Wiki Page. Used only for wiki content; not the primary map filter. An Event's linked Pages keep their own Page Tags. _Avoid_: Reusing Page Tags as the Chronicle's event taxonomy + +**Chronicle Overlay**: +An optional map view mode that shows Events (and optionally Era Maps) from more than one Chronicle on the same canvas, with each Chronicle toggleable. Chronicles remain separate aggregates; overlay is a view concern, not a merge of data. +_Avoid_: World Layer inside a single Chronicle; permanently merging taxonomies or alignments across Chronicles From 4e0ba5f265d6cfaee9b35195e5fa529f913f644d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:45:30 +0000 Subject: [PATCH 08/16] docs: define Map View and Chronicle Embed One interactive Map View; master chronicle routes and in-page embeds are two entry points with optional narrowed scope. Co-authored-by: Sad --- CONTEXT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index c4f9130c0f..4b44afbe74 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -47,3 +47,11 @@ _Avoid_: Reusing Page Tags as the Chronicle's event taxonomy **Chronicle Overlay**: An optional map view mode that shows Events (and optionally Era Maps) from more than one Chronicle on the same canvas, with each Chronicle toggleable. Chronicles remain separate aggregates; overlay is a view concern, not a merge of data. _Avoid_: World Layer inside a single Chronicle; permanently merging taxonomies or alignments across Chronicles + +**Map View**: +The single interactive map experience for exploring Chronicles — Era Maps, Event pins, filters, overlays, and (for editors) alignment tools. Reused everywhere; not duplicated per surface. +_Avoid_: Separate “simple embed map” and “full map” products with different data rules + +**Chronicle Embed**: +A Map View instance placed inside a Wiki Page, usually with a locked or narrowed scope (one Chronicle, Era Map, Tag set, or Event focus). Same underlying Map View as the master route. +_Avoid_: Static thumbnail that is not a Map View; treating the embed as a second data store From ae1944d8c2fdb54b9e5d66d2f37b791f4c2753e9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:46:14 +0000 Subject: [PATCH 09/16] docs: define Event Draft, Ingest API, and AI Analysis Three intake paths share the Event model; AI and external ingest create Event Drafts that require human confirmation. Co-authored-by: Sad --- CONTEXT.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index 4b44afbe74..2c610f39b0 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -55,3 +55,15 @@ _Avoid_: Separate “simple embed map” and “full map” products with differ **Chronicle Embed**: A Map View instance placed inside a Wiki Page, usually with a locked or narrowed scope (one Chronicle, Era Map, Tag set, or Event focus). Same underlying Map View as the master route. _Avoid_: Static thumbnail that is not a Map View; treating the embed as a second data store + +**Event Draft**: +A proposed Event (from Ingest API or AI Analysis) that is not yet a live pin. Becomes an Event only after human confirmation. Same fields as Event, different lifecycle. +_Avoid_: Writing AI/API output straight to live Events; a separate parallel data model unrelated to Event + +**Ingest API**: +An HTTP/GraphQL API for submitting structured Event or Event Draft payloads from external systems. Does not bypass confirmation when the payload is marked as draft/AI-sourced. +_Avoid_: Ad-hoc DB inserts; scraping hooks that publish live pins with no review path + +**AI Analysis**: +A process that turns unstructured input (text, URL, bulk notes) into one or more Event Drafts — suggested title, Occurrence, Canonical Position, Chronicle Tags — for human review. +_Avoid_: AI as the source of truth; AI that only fills a form with no draft record From 0d4c524bc3dea65b93cd689a4aefd9851e21f03b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:51:42 +0000 Subject: [PATCH 10/16] docs: define Map Mode on Era Map Era Maps support 2d, 3d, or both as presentation modes without splitting into separate map entity types. Co-authored-by: Sad --- CONTEXT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index 2c610f39b0..b841fd5b6e 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -67,3 +67,7 @@ _Avoid_: Ad-hoc DB inserts; scraping hooks that publish live pins with no review **AI Analysis**: A process that turns unstructured input (text, URL, bulk notes) into one or more Event Drafts — suggested title, Occurrence, Canonical Position, Chronicle Tags — for human review. _Avoid_: AI as the source of truth; AI that only fills a form with no draft record + +**Map Mode**: +How an Era Map is presented in Map View: `2d`, `3d`, or `both` (user-switchable). Events still use a 2D Canonical Position by default; 3D is primarily a presentation concern, with optional elevation later. +_Avoid_: Separate 2D-only and 3D-only Era Map entity types; treating 3D as a different aggregate From c8da72bd4982c8ee772a2419b81d8c2828523451 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:52:53 +0000 Subject: [PATCH 11/16] docs: define Basemap Source kinds on Era Map Public provider, manual upload, and AI/API-derived basemaps share one Era Map source field with human confirm for derived. Co-authored-by: Sad --- CONTEXT.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index b841fd5b6e..fddb98f5df 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -71,3 +71,19 @@ _Avoid_: AI as the source of truth; AI that only fills a form with no draft reco **Map Mode**: How an Era Map is presented in Map View: `2d`, `3d`, or `both` (user-switchable). Events still use a 2D Canonical Position by default; 3D is primarily a presentation concern, with optional elevation later. _Avoid_: Separate 2D-only and 3D-only Era Map entity types; treating 3D as a different aggregate + +**Basemap Source**: +Where an Era Map's imagery or tiles come from. One of: Public Basemap, Uploaded Basemap, or Derived Basemap. Source type lives on the Era Map; it is not a separate aggregate. +_Avoid_: Iframe-only external maps outside the Chronicle model; one-off assets with no source metadata + +**Public Basemap**: +A Basemap Source that connects to an open or configured tile/map provider (e.g. OSM, satellite, government open layers) by provider id or URL. Preferred for real-world geographies already in a known CRS. +_Avoid_: Re-uploading public tiles as static images when a provider works + +**Uploaded Basemap**: +A Basemap Source from manually uploaded imagery or tile packs — fantasy maps, scanned historical maps, custom art. Usually requires Alignment. +_Avoid_: Treating uploads as already georeferenced without Alignment + +**Derived Basemap**: +A Basemap Source produced by AI/API conversion from an image or other map (e.g. suggested Alignment control points, cleaned tile asset). Stored as a draft asset until a human confirms and attaches it to an Era Map. +_Avoid_: AI silently publishing live basemaps; calling this a third aggregate beside Era Map From 9cb618b0879180ab68cbcdbe3118859bced73c0a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:53:40 +0000 Subject: [PATCH 12/16] docs: define optional Event Footprint beside Canonical Position Events always have a point pin; line/polygon footprint is optional display extent and not the alignment primary. Co-authored-by: Sad --- CONTEXT.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index fddb98f5df..90485de3c8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -87,3 +87,7 @@ _Avoid_: Treating uploads as already georeferenced without Alignment **Derived Basemap**: A Basemap Source produced by AI/API conversion from an image or other map (e.g. suggested Alignment control points, cleaned tile asset). Stored as a draft asset until a human confirms and attaches it to an Era Map. _Avoid_: AI silently publishing live basemaps; calling this a third aggregate beside Era Map + +**Footprint**: +Optional line or polygon geometry on an Event that shows extent (building, road stretch, district). Canonical Position remains required; Footprint does not replace it for alignment, overrides, or default AI geocoding. +_Avoid_: Polygon-only Events with no point; treating Footprint as the primary pin From 0f8a432f7743cfa59bf3c8bdc7defd41a1c27b45 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 01:54:40 +0000 Subject: [PATCH 13/16] docs: add Chronicle Permissions and core ADRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Record independent chronicle access rules and ADRs for first-class Chronicle, hybrid placement, Event Drafts, and permissions — pending shared-understanding confirmation. Co-authored-by: Sad --- CONTEXT.md | 4 ++++ docs/adr/0001-chronicle-as-first-class-aggregate.md | 3 +++ docs/adr/0002-hybrid-event-placement.md | 3 +++ docs/adr/0003-event-drafts-for-ingest.md | 3 +++ docs/adr/0004-independent-chronicle-permissions.md | 3 +++ 5 files changed, 16 insertions(+) create mode 100644 docs/adr/0001-chronicle-as-first-class-aggregate.md create mode 100644 docs/adr/0002-hybrid-event-placement.md create mode 100644 docs/adr/0003-event-drafts-for-ingest.md create mode 100644 docs/adr/0004-independent-chronicle-permissions.md diff --git a/CONTEXT.md b/CONTEXT.md index 90485de3c8..b8598415c2 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -91,3 +91,7 @@ _Avoid_: AI silently publishing live basemaps; calling this a third aggregate be **Footprint**: Optional line or polygon geometry on an Event that shows extent (building, road stretch, district). Canonical Position remains required; Footprint does not replace it for alignment, overrides, or default AI geocoding. _Avoid_: Polygon-only Events with no point; treating Footprint as the primary pin + +**Chronicle Permission**: +Access rules owned by the Chronicle domain — read map data, write Events, approve Event Drafts / Derived Basemaps, and manage Era Maps / Alignment. Independent of Wiki Page permissions; may be global or per-Chronicle via groups. +_Avoid_: Inferring Chronicle edit rights from “can edit this wiki page”; admin-only as the only write model diff --git a/docs/adr/0001-chronicle-as-first-class-aggregate.md b/docs/adr/0001-chronicle-as-first-class-aggregate.md new file mode 100644 index 0000000000..5bdbe1396d --- /dev/null +++ b/docs/adr/0001-chronicle-as-first-class-aggregate.md @@ -0,0 +1,3 @@ +# Chronicle as a first-class aggregate + +Wiki pages must not own chronicle map data. A Chronicle is its own aggregate (Era Maps, Events, Chronicle Tags, drafts). Pages only embed or link via Map View / Chronicle Embed. This keeps master-map, overlays, and cross-page reuse from being trapped in `page.render`, at the cost of a new module surface beside pages. diff --git a/docs/adr/0002-hybrid-event-placement.md b/docs/adr/0002-hybrid-event-placement.md new file mode 100644 index 0000000000..10e696d16c --- /dev/null +++ b/docs/adr/0002-hybrid-event-placement.md @@ -0,0 +1,3 @@ +# Hybrid event placement across Era Maps + +Each Event stores one Canonical Position in the Chronicle’s shared spatial reference. Each Era Map carries an Alignment transform to project that position onto the basemap. When projection fails for a specific pin, an optional Pin Override stores a per–Event-per–Era-Map position. Chosen over per-map-only pins (no cross-era reuse) and pure automatic projection (too brittle for scanned or fantasy maps). diff --git a/docs/adr/0003-event-drafts-for-ingest.md b/docs/adr/0003-event-drafts-for-ingest.md new file mode 100644 index 0000000000..8b06b4acad --- /dev/null +++ b/docs/adr/0003-event-drafts-for-ingest.md @@ -0,0 +1,3 @@ +# Event Drafts for API and AI ingest + +Manual create may write Events directly (subject to Chronicle Permissions). Ingest API and AI Analysis produce Event Drafts (and Derived Basemap drafts) that become live only after human approval. Chosen so paranormal/fiction sources can be bulk-imported without letting geocoding or tag mistakes publish unchecked, mirroring the site’s existing pending-review mindset for pages. diff --git a/docs/adr/0004-independent-chronicle-permissions.md b/docs/adr/0004-independent-chronicle-permissions.md new file mode 100644 index 0000000000..5a3b80fd44 --- /dev/null +++ b/docs/adr/0004-independent-chronicle-permissions.md @@ -0,0 +1,3 @@ +# Independent Chronicle Permissions + +Chronicle read/write/approve/manage rights are separate from Wiki Page permissions. Page edit rights must not imply chronicle edit rights. This lets public maps stay world-readable while draft approval and alignment stay with trusted groups, and avoids coupling access to whichever page happens to embed the map. From ccf1f06b72e3101bbe599f7f0cbc593f00671217 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 5 Aug 2026 02:17:27 +0000 Subject: [PATCH 14/16] feat: implement Chronicle Map foundation Add first-class Chronicle aggregates with Era Maps, Events, tags, hybrid placement/visibility, Event Drafts, GraphQL API, admin UI, /chronicle Map View (Leaflet 2D), and page embeds. Co-authored-by: Sad --- client/client-app.js | 1 + client/components/admin.vue | 4 + client/components/admin/admin-chronicles.vue | 388 ++++++++++++++ .../admin/admin-groups-edit-permissions.vue | 28 ++ client/components/chronicle-map.vue | 320 ++++++++++++ .../default/components/chronicle-embed.vue | 31 ++ client/themes/default/components/page.vue | 2 + docs/adr/0005-leaflet-for-2d-map-view.md | 3 + server/controllers/common.js | 12 + server/db/migrations-sqlite/2.5.133.js | 112 +++++ server/db/migrations/2.5.133.js | 126 +++++ server/graph/resolvers/chronicle.js | 472 ++++++++++++++++++ server/graph/schemas/chronicle.graphql | 285 +++++++++++ server/helpers/chronicle-map.js | 106 ++++ server/models/chronicleEraMaps.js | 117 +++++ server/models/chronicleEvents.js | 243 +++++++++ server/models/chroniclePinOverrides.js | 21 + server/models/chronicleTags.js | 64 +++ server/models/chronicleVisibilityOverrides.js | 18 + server/models/chronicles.js | 150 ++++++ .../html-chronicle-embed/definition.yml | 8 + .../html-chronicle-embed/renderer.js | 39 ++ .../rendering/html-security/renderer.js | 4 +- server/setup.js | 2 +- server/test/helpers/chronicle-map.test.js | 67 +++ server/views/chronicle.pug | 5 + 26 files changed, 2625 insertions(+), 3 deletions(-) create mode 100644 client/components/admin/admin-chronicles.vue create mode 100644 client/components/chronicle-map.vue create mode 100644 client/themes/default/components/chronicle-embed.vue create mode 100644 docs/adr/0005-leaflet-for-2d-map-view.md create mode 100644 server/db/migrations-sqlite/2.5.133.js create mode 100644 server/db/migrations/2.5.133.js create mode 100644 server/graph/resolvers/chronicle.js create mode 100644 server/graph/schemas/chronicle.graphql create mode 100644 server/helpers/chronicle-map.js create mode 100644 server/models/chronicleEraMaps.js create mode 100644 server/models/chronicleEvents.js create mode 100644 server/models/chroniclePinOverrides.js create mode 100644 server/models/chronicleTags.js create mode 100644 server/models/chronicleVisibilityOverrides.js create mode 100644 server/models/chronicles.js create mode 100644 server/modules/rendering/html-chronicle-embed/definition.yml create mode 100644 server/modules/rendering/html-chronicle-embed/renderer.js create mode 100644 server/test/helpers/chronicle-map.test.js create mode 100644 server/views/chronicle.pug diff --git a/client/client-app.js b/client/client-app.js index f3dc05a8b6..88f40853f3 100644 --- a/client/client-app.js +++ b/client/client-app.js @@ -170,6 +170,7 @@ Vue.component('Register', () => import(/* webpackChunkName: "register" */ './com Vue.component('SearchResults', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/search-results.vue')) Vue.component('SocialSharing', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/social-sharing.vue')) Vue.component('Tags', () => import(/* webpackChunkName: "tags" */ './components/tags.vue')) +Vue.component('ChronicleMap', () => import(/* webpackChunkName: "chronicle" */ './components/chronicle-map.vue')) Vue.component('Unauthorized', () => import(/* webpackChunkName: "unauthorized" */ './components/unauthorized.vue')) Vue.component('VCardChin', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-chin.vue')) Vue.component('VCardInfo', () => import(/* webpackPrefetch: true, webpackChunkName: "ui-extra" */ './components/common/v-card-info.vue')) diff --git a/client/components/admin.vue b/client/components/admin.vue index aa6ad23c01..6721398309 100644 --- a/client/components/admin.vue +++ b/client/components/admin.vue @@ -39,6 +39,9 @@ :dark='pendingReviewsCount > 0' ) .caption(:class='pendingReviewsCount > 0 ? `` : `grey--text`') {{ pendingReviewsCount }} + v-list-item(to='/chronicles', color='primary', v-if='hasPermission([`manage:system`, `manage:chronicles`])') + v-list-item-avatar(size='24', tile): v-icon mdi-map-clock-outline + v-list-item-title Chronicles v-list-item(to='/tags', v-if='hasPermission([`manage:system`])') v-list-item-avatar(size='24', tile): v-icon mdi-tag-multiple v-list-item-title {{ $t('admin:tags.title') }} @@ -163,6 +166,7 @@ const router = new VueRouter({ { path: '/pages/:id(\\d+)', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-edit.vue') }, { path: '/pages/visualize', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-pages-visualize.vue') }, { path: '/page-reviews', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-page-reviews.vue') }, + { path: '/chronicles', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-chronicles.vue') }, { path: '/tags', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-tags.vue') }, { path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-theme.vue') }, { path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups.vue') }, diff --git a/client/components/admin/admin-chronicles.vue b/client/components/admin/admin-chronicles.vue new file mode 100644 index 0000000000..48b88e9f17 --- /dev/null +++ b/client/components/admin/admin-chronicles.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/client/components/admin/admin-groups-edit-permissions.vue b/client/components/admin/admin-groups-edit-permissions.vue index 0e955c7053..5f5d9cabd9 100644 --- a/client/components/admin/admin-groups-edit-permissions.vue +++ b/client/components/admin/admin-groups-edit-permissions.vue @@ -155,6 +155,34 @@ export default { warning: false, restrictedForSystem: true, disabled: false + }, + { + permission: 'read:chronicles', + hint: 'Can view Chronicle maps and event pins', + warning: false, + restrictedForSystem: false, + disabled: false + }, + { + permission: 'write:chronicles', + hint: 'Can create and edit Chronicle events (and submit drafts)', + warning: false, + restrictedForSystem: true, + disabled: false + }, + { + permission: 'approve:chronicles', + hint: 'Can approve Event Drafts and Derived Basemaps', + warning: true, + restrictedForSystem: true, + disabled: false + }, + { + permission: 'manage:chronicles', + hint: 'Can create Chronicles, Era Maps, Alignment, and manage all chronicle data', + warning: true, + restrictedForSystem: true, + disabled: false } ] }, diff --git a/client/components/chronicle-map.vue b/client/components/chronicle-map.vue new file mode 100644 index 0000000000..b7a8e454dd --- /dev/null +++ b/client/components/chronicle-map.vue @@ -0,0 +1,320 @@ + + + + + diff --git a/client/themes/default/components/chronicle-embed.vue b/client/themes/default/components/chronicle-embed.vue new file mode 100644 index 0000000000..4547b53cb4 --- /dev/null +++ b/client/themes/default/components/chronicle-embed.vue @@ -0,0 +1,31 @@ + + + diff --git a/client/themes/default/components/page.vue b/client/themes/default/components/page.vue index 73f314f671..d3836bb314 100644 --- a/client/themes/default/components/page.vue +++ b/client/themes/default/components/page.vue @@ -393,6 +393,7 @@