documentatie
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# Architectuur van racket-wiki
|
# Architectuur van racket-wiki
|
||||||
|
|
||||||
Deze documentatieset beschrijft de architectuur van racket-wiki zoals die in versie 0.2.94 bestaat. Zij is tegelijk een wegwijzer voor onderhoud: iedere pagina maakt onderscheid tussen bestaand gedrag, bekende grenzen en regels voor verdere ontwikkeling.
|
Deze documentatieset beschrijft de architectuur van racket-wiki zoals die in versie 0.2.122 bestaat. Zij is tegelijk een wegwijzer voor onderhoud: iedere pagina maakt onderscheid tussen bestaand gedrag, bekende grenzen en regels voor verdere ontwikkeling.
|
||||||
|
|
||||||
{{cmap:racket-wiki-architectuur}}
|
{{cmap:racket-wiki-architectuur}}
|
||||||
|
|
||||||
## Architectuur in één alinea
|
## Architectuur in één alinea
|
||||||
|
|
||||||
racket-wiki is een kleine, zelf gehoste webapplicatie. Eén Racket-proces levert de HTTP-server, authenticatie, autorisatie, setup, databaseaanroepen en statische bestanden. PostgreSQL bevat de duurzame toestand: gebruikers, sessies, pagina's, onveranderlijke paginaversies, conceptmaps, CMap-versies, bijlagen en beheerinstellingen. De browser bevat de interactieve applicatie, Markdown-editor en CMap-editor. De grens tussen browser en backend is een JSON/HTTP-API. Normaal gebruik vereist geen externe CDN of afzonderlijke applicatieservice.
|
racket-wiki is een kleine, zelf gehoste webapplicatie. Eén Racket-proces levert de HTTP-server, authenticatie, autorisatie, setup, databaseaanroepen en statische bestanden. PostgreSQL bevat de duurzame toestand: gebruikers, sessies, pagina's, onveranderlijke paginaversies, conceptmaps, CMap-versies, gedeelde conceptdefinities, opgeslagen SVG-weergaven, bijlagen en beheerinstellingen. De browser bevat de interactieve applicatie, Markdown-editor en modulaire CMap-editor. De grens tussen browser en backend is een JSON/HTTP-API. Normaal gebruik vereist geen externe CDN of afzonderlijke applicatieservice.
|
||||||
|
|
||||||
## Leeswijzer
|
## Leeswijzer
|
||||||
|
|
||||||
@@ -37,11 +37,13 @@ De huidige vorm rust op een klein aantal bewuste keuzes:
|
|||||||
5. Markdown wordt in de browser gerenderd en daarna met DOMPurify gesaneerd.
|
5. Markdown wordt in de browser gerenderd en daarna met DOMPurify gesaneerd.
|
||||||
6. Browserbibliotheken worden tijdens setup vastgepind en lokaal geserveerd.
|
6. Browserbibliotheken worden tijdens setup vastgepind en lokaal geserveerd.
|
||||||
7. Pagina-adressen bestaan uit een namespace en stabiele slug. Deze set gebruikt de namespace `racket-wiki`.
|
7. Pagina-adressen bestaan uit een namespace en stabiele slug. Deze set gebruikt de namespace `racket-wiki`.
|
||||||
8. Conceptmaps blijven native CMap-documenten. `{{cmap:...}}` sluit een read-only weergave in Markdown in zonder het CMap-formaat tot Mermaid te reduceren.
|
8. Conceptmaps blijven native CMap-documenten. `{{cmap:...}}` sluit een opgeslagen SVG-weergave als linkbare afbeelding in Markdown in zonder het CMap-formaat tot Mermaid te reduceren.
|
||||||
|
9. CMap-structuur, gedeelde conceptinhoud en afgeleide SVG-weergave hebben afzonderlijke opslagcontracten. De SVG is geen onderdeel van het bewerkbare CMap-JSON-document.
|
||||||
|
10. De CMap-editor gebruikt native ES-modules met expliciete model-, controller-, view- en workspacegrenzen. De workspace coördineert alleen editorcallbacks, dialogen en DOM-events.
|
||||||
|
|
||||||
## Kwaliteitsbeeld
|
## Kwaliteitsbeeld
|
||||||
|
|
||||||
De architectuur past goed bij een persoonlijke of teamwiki: weinig processen, een duidelijke databasebron en volledige historie. De sterkste punten zijn de transactionele inhoudsopslag, eenvoudige deployment en lokale frontend-assets. De voornaamste ontwikkelpunten zijn de omvang van `server.rkt` en `static/js/wiki.js`, het ontbreken van een volwaardige geautomatiseerde testsuite en een nieuwe databaseverbinding per opslagbewerking.
|
De architectuur past goed bij een persoonlijke of teamwiki: weinig processen, een duidelijke databasebron en volledige historie. De sterkste punten zijn de transactionele inhoudsopslag, eenvoudige deployment, lokale frontend-assets en de afgebakende CMap-editor. De voornaamste ontwikkelpunten zijn de omvang van `server.rkt` en `static/js/wiki.js`, beperkte backend- en browsertestdekking en een nieuwe databaseverbinding per opslagbewerking.
|
||||||
|
|
||||||
Deze punten zijn geen reden voor een voorafgaande grote herbouw. De ontwikkelregel is: meet eerst, isoleer het concrete probleem en splits een module wanneer een wijziging daar aantoonbaar eenvoudiger of beter testbaar door wordt.
|
Deze punten zijn geen reden voor een voorafgaande grote herbouw. De ontwikkelregel is: meet eerst, isoleer het concrete probleem en splits een module wanneer een wijziging daar aantoonbaar eenvoudiger of beter testbaar door wordt.
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ De database bevat zowel de huidige toestand als de auditgeschiedenis. De browser
|
|||||||
| `attachment_references` | Huidige en historische verwijzingen vanuit paginaversies |
|
| `attachment_references` | Huidige en historische verwijzingen vanuit paginaversies |
|
||||||
| `concept_maps` | Actuele titel, JSONB-document en versieteller per CMap |
|
| `concept_maps` | Actuele titel, JSONB-document en versieteller per CMap |
|
||||||
| `concept_map_versions` | Expliciete CMap-snapshots en maximaal vijf handmatige opslagversies per CMap |
|
| `concept_map_versions` | Expliciete CMap-snapshots en maximaal vijf handmatige opslagversies per CMap |
|
||||||
|
| `cmap_renders` | Opgeslagen SVG-weergave per CMap en documentversie, voor Markdown-embeds |
|
||||||
| `concept_definitions` | Wiki-brede inhoud per stabiele `conceptId`; plaatsing en opmaak blijven in het CMap-document |
|
| `concept_definitions` | Wiki-brede inhoud per stabiele `conceptId`; plaatsing en opmaak blijven in het CMap-document |
|
||||||
| `people` | Wiki-breed register van actieve en inactieve namen voor getypeerde persoonstags |
|
| `people` | Wiki-breed register van actieve en inactieve namen voor getypeerde persoonstags |
|
||||||
| `password_reset_tokens` | Gehashte, tijdelijke en eenmalige herstelcodes |
|
| `password_reset_tokens` | Gehashte, tijdelijke en eenmalige herstelcodes |
|
||||||
@@ -41,7 +42,7 @@ Een paginaopslag is één atomaire transactie:
|
|||||||
6. Vervang huidige bijlageverwijzingen en leg historische verwijzingen voor de nieuwe versie vast.
|
6. Vervang huidige bijlageverwijzingen en leg historische verwijzingen voor de nieuwe versie vast.
|
||||||
7. Commit alles, of niets.
|
7. Commit alles, of niets.
|
||||||
|
|
||||||
Een CMap-opslag volgt voor de actuele projectie hetzelfde kernpatroon: rij vergrendelen, versienummer vergelijken en actuele JSONB bijwerken. In dezelfde transactie worden de aangeleverde inhoudelijke velden op `conceptId` in `concept_definitions` gezet. Een `conceptId` is een kale, lowercase en globaal unieke UUID; hij bevat bewust geen CMap-slug. De getrimde, hoofdletterongevoelige conceptnaam is wiki-breed uniek; een tweede plaatsing met dezelfde naam krijgt direct de bestaande id. Het opgeslagen CMap-document bevat zelf geen inhoudelijke kopie: `items[]` bewaart alleen structuur en opmaak en `concepts[]` bevat uitsluitend `{id}`-verwijzingen. `concept_definitions` is de enige persistente bron voor naam, samenvatting, aspecten, personen, koppelingen, afbeelding en toelichtingspagina. Bij uitlezen hydrateert de server die verwijzingen voor de editor. Ieder item behalve een verbindingszin is een plaatsing van een concept; `submap`, `page` en `concept` zijn lokale structurele rollen en geen afzonderlijke conceptsoorten. Positie, afmetingen, kleuren, typografie en structurele rol kunnen per plaatsing verschillen. De CMap zelf houdt haar stabiele, leesbare `concept_maps.slug`; die slug identificeert de kaart en staat los van de concept-UUID's. Autosave verhoogt wel het optimistische versienummer, maar maakt geen historieregel. Een expliciete opslag voegt een volledige toestand aan `concept_map_versions` toe en verwijdert oudere handmatige versies boven de grens van vijf. Benoemde snapshots vallen niet onder die grens.
|
Een CMap-opslag volgt voor de actuele projectie hetzelfde kernpatroon: rij vergrendelen, versienummer vergelijken en actuele JSONB bijwerken. In dezelfde transactie worden de aangeleverde inhoudelijke velden op `conceptId` in `concept_definitions` gezet en wordt, wanneer de browser een render aanlevert, de SVG in `cmap_renders` bij dezelfde documentversie geschreven. Een `conceptId` is een kale, lowercase en globaal unieke UUID; hij bevat bewust geen CMap-slug. De getrimde, hoofdletterongevoelige conceptnaam is wiki-breed uniek; een tweede plaatsing met dezelfde naam krijgt direct de bestaande id. Het opgeslagen CMap-document bevat zelf geen inhoudelijke kopie: `items[]` bewaart alleen structuur en opmaak en `concepts[]` bevat uitsluitend `{id}`-verwijzingen. `concept_definitions` is de enige persistente bron voor naam, samenvatting, aspecten, personen, koppelingen, afbeelding en toelichtingspagina. Bij uitlezen hydrateert de server die verwijzingen voor de editor. Ieder item behalve een verbindingszin is een plaatsing van een concept; `submap`, `page` en `concept` zijn lokale structurele rollen en geen afzonderlijke conceptsoorten. Positie, afmetingen, kleuren, typografie en structurele rol kunnen per plaatsing verschillen. De CMap zelf houdt haar stabiele, leesbare `concept_maps.slug`; die slug identificeert de kaart en staat los van de concept-UUID's. Autosave verhoogt wel het optimistische versienummer, maar maakt geen historieregel. Een expliciete opslag voegt een volledige toestand aan `concept_map_versions` toe en verwijdert oudere handmatige versies boven de grens van vijf. Benoemde snapshots vallen niet onder die grens.
|
||||||
|
|
||||||
## Waarom volledige snapshots
|
## Waarom volledige snapshots
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ De volgende regels mogen niet alleen in de browser staan:
|
|||||||
|
|
||||||
- iedere actuele paginaversie heeft een overeenkomstige onveranderlijke snapshot;
|
- iedere actuele paginaversie heeft een overeenkomstige onveranderlijke snapshot;
|
||||||
- CMap-autosave wijzigt alleen de actuele projectie; CMap-historie bevat alleen snapshots en maximaal vijf handmatige versies;
|
- CMap-autosave wijzigt alleen de actuele projectie; CMap-historie bevat alleen snapshots en maximaal vijf handmatige versies;
|
||||||
|
- een SVG-render hoort bij een concrete CMap-documentversie, maar is geen veld van het CMap-JSON-document;
|
||||||
- alle actuele plaatsingen met dezelfde `conceptId` lezen hun inhoud uit dezelfde `concept_definitions`-rij;
|
- alle actuele plaatsingen met dezelfde `conceptId` lezen hun inhoud uit dezelfde `concept_definitions`-rij;
|
||||||
- iedere actuele `conceptId` is een kale lowercase UUID; de CMap-slug blijft afzonderlijk bewaard;
|
- iedere actuele `conceptId` is een kale lowercase UUID; de CMap-slug blijft afzonderlijk bewaard;
|
||||||
- versienummers nemen per object strikt toe;
|
- versienummers nemen per object strikt toe;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ De backend is functioneel opgesplitst. Configuratie, databaseverbinding, migrati
|
|||||||
|
|
||||||
De belangrijkste grens is die tussen `server.rkt` en de opslagmodules. `server.rkt` hoort HTTP te begrijpen; `storage.rkt`, `cmap-storage.rkt` en `auth.rkt` horen domeinbewerkingen en database-invarianten te begrijpen. Geen opslagprocedure mag een webrequest nodig hebben.
|
De belangrijkste grens is die tussen `server.rkt` en de opslagmodules. `server.rkt` hoort HTTP te begrijpen; `storage.rkt`, `cmap-storage.rkt` en `auth.rkt` horen domeinbewerkingen en database-invarianten te begrijpen. Geen opslagprocedure mag een webrequest nodig hebben.
|
||||||
|
|
||||||
De frontend heeft een vergelijkbare grens. `cmap-racket-wiki.js` implementeert een editorcomponent met callbacks voor openen, selectie en wijzigingen. `wiki.js` koppelt die callbacks aan routes, API's en dialoogvensters. Zuivere referentie-, route-, Markdown- en outlinebewerkingen staan in eigen modules; `BreadcrumbTrail` beheert uitsluitend de per-tab paginahistorie en kent de DOM niet. `routes.js` herkent hashes, terwijl `wiki.js` eigenaar blijft van permissiecontrole en het openen van views. De concrete controllers onder `static/js/wiki/admin/` beheren hun eigen formulier- of lijstinteractie. Zij ontvangen alleen de bestaande API- en vertaalfuncties en, wanneer de gewone catalogus werkelijk verandert, één gerichte herlaadfunctie.
|
De frontend heeft een vergelijkbare grens. `cmap-racket-wiki.js` is een dunne editorfacade met callbacks voor openen, selectie en wijzigingen; hij composeert gespecialiseerde document-, geometrie-, relatie-, layout-, selectie-, interactie- en submapcontrollers. Onder `static/js/wiki/cmap/` koppelt `CmapWorkspaceController` die editor aan routes, API's, dialogen en DOM-events. `CmapStorageController` beheert dirty state, autosave en de opslagvolgorde; `CmapNavigationController` beheert routes en browsercontext; `CmapTransferController` beheert import en export; `CmapEditorHost` beheert editorlifecycle; `CmapEditorUiController` beheert standaard toolbaracties; `CmapMapController` beheert opgeslagen kaarten en `CmapConceptController` beheert conceptdialogen en geselecteerde conceptbewerkingen. Zuivere referentie-, route-, Markdown- en outlinebewerkingen staan in eigen modules; `BreadcrumbTrail` beheert uitsluitend de per-tab paginahistorie en kent de DOM niet. `routes.js` herkent hashes, terwijl `wiki.js` eigenaar blijft van permissiecontrole en het openen van views.
|
||||||
|
|
||||||
## Sterke punten
|
## Sterke punten
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ De volgende onderdelen zijn al goed geïsoleerd:
|
|||||||
|
|
||||||
## Huidige concentraties
|
## Huidige concentraties
|
||||||
|
|
||||||
`server.rkt` bevat zowel routing als veel requestparsing en handlerlogica. `static/js/wiki.js` bevat vrijwel de hele single-page applicatie. Dat maakt zoeken eenvoudig, maar vergroot de kans dat een wijziging onverwacht een ander view- of routepad raakt.
|
`server.rkt` bevat zowel routing als veel requestparsing en handlerlogica. `static/js/wiki.js` bevat nog steeds de brede single-page applicatie. De CMap-workspace is daarbinnen echter opgesplitst in eigen modules, waardoor CMap-wijzigingen niet meer in één grote editor- of workspaceklasse samenkomen.
|
||||||
|
|
||||||
`private/storage.rkt` combineert pagina's, historie, zoeken, bookmarks, aliases en uploads. Die onderdelen delen pagina-identiteit en transacties, maar hoeven niet onbeperkt samen te groeien.
|
`private/storage.rkt` combineert pagina's, historie, zoeken, bookmarks, aliases en uploads. Die onderdelen delen pagina-identiteit en transacties, maar hoeven niet onbeperkt samen te groeien.
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ Deze concentraties zijn technische schuld, geen automatische opdracht tot een gr
|
|||||||
|
|
||||||
Wanneer `server.rkt` verder groeit, ligt opsplitsing per adaptergebied voor de hand: sessie/profiel, pagina's, CMaps en beheer. De centrale router kan dan dun blijven. Handlers ontvangen `config` expliciet en roepen dezelfde bestaande diensten aan.
|
Wanneer `server.rkt` verder groeit, ligt opsplitsing per adaptergebied voor de hand: sessie/profiel, pagina's, CMaps en beheer. De centrale router kan dan dun blijven. Handlers ontvangen `config` expliciet en roepen dezelfde bestaande diensten aan.
|
||||||
|
|
||||||
Wanneer `wiki.js` verder groeit, zijn route/state, pagina-editor, CMap-host en admin-views natuurlijke grenzen. Splits alleen met native ES-modules wanneer de setup- en cacheversies van alle scripts tegelijk beheerst worden.
|
Wanneer `wiki.js` verder groeit, zijn route/state, pagina-editor en admin-views natuurlijke grenzen. De CMap-host is al als native ES-modulelaag gescheiden. Houd de workspace als coördinator; extra extracties zijn alleen zinvol bij een nieuwe zelfstandige workflow, niet voor losse DOM-hulpen.
|
||||||
|
|
||||||
Wanneer paginaopslag wordt aangepast, kunnen bookmarks, aliases en uploads later eigen modules krijgen. De pagina-schrijftransactie en afgeleide indices moeten daarbij als één consistente operatie behouden blijven; opsplitsing van bestanden mag geen opsplitsing van de transactie veroorzaken.
|
Wanneer paginaopslag wordt aangepast, kunnen bookmarks, aliases en uploads later eigen modules krijgen. De pagina-schrijftransactie en afgeleide indices moeten daarbij als één consistente operatie behouden blijven; opsplitsing van bestanden mag geen opsplitsing van de transactie veroorzaken.
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ De map `private/` bevat de backendonderdelen:
|
|||||||
| `migrations.rkt` | Opeenvolgende, transactionele schemasprongen |
|
| `migrations.rkt` | Opeenvolgende, transactionele schemasprongen |
|
||||||
| `auth.rkt` | Gebruikers, wachtwoorden, rollen, sessies, CSRF en herstelcodes |
|
| `auth.rkt` | Gebruikers, wachtwoorden, rollen, sessies, CSRF en herstelcodes |
|
||||||
| `storage.rkt` | Pagina's, historie, zoeken, bookmarks, uploads en aliases |
|
| `storage.rkt` | Pagina's, historie, zoeken, bookmarks, uploads en aliases |
|
||||||
| `cmap-storage.rkt` | Conceptmaps en onveranderlijke CMap-versies |
|
| `cmap-storage.rkt` | Conceptmaps, gedeelde conceptdefinities, onveranderlijke CMap-versies en opgeslagen SVG-weergaven |
|
||||||
| `attachment-references.rkt` | Huidige en historische verwijzingen naar bijlagen |
|
| `attachment-references.rkt` | Huidige en historische verwijzingen naar bijlagen |
|
||||||
| `todo.rkt` | Herkennen van wiki-brede `todo(...)`-markeringen |
|
| `todo.rkt` | Herkennen van wiki-brede `todo(...)`-markeringen |
|
||||||
| `mail.rkt` | SMTP-instellingen, STARTTLS, testmail en herstelmail |
|
| `mail.rkt` | SMTP-instellingen, STARTTLS, testmail en herstelmail |
|
||||||
@@ -38,7 +38,9 @@ De map `private/` bevat de backendonderdelen:
|
|||||||
|
|
||||||
`translate.rkt` staat bewust aan de publieke rand: zowel backend als frontend gebruiken dezelfde effectieve vertaaltabel.
|
`translate.rkt` staat bewust aan de publieke rand: zowel backend als frontend gebruiken dezelfde effectieve vertaaltabel.
|
||||||
|
|
||||||
De map `static/` bevat de browserapplicatie. `static/index.html` definieert de views en dialogen. `static/js/wiki.js` beheert routedispatch, API-aanroepen, editor- en paginatoestand en speciale views. De modules onder `static/js/wiki/` bevatten afzonderlijk de referentiesyntaxis, routeherkenning, Markdowntransformaties, paginakoppen en breadcrumbhistorie. De controllers onder `static/js/wiki/admin/` beheren ieder één administratief formulier of overzicht; routes, permissiecontrole, breadcrumbs en algemene adminnavigatie blijven in `wiki.js`. `static/js/cmap/cmap.js` levert de grafische basis; `cmap-racket-wiki.js` voegt wiki-items, selectie, relaties, sub-CMaps, historie en documentserialisatie toe. `combobox.js` is een herbruikbaar klein UI-onderdeel. CSS is verdeeld tussen algemene wiki-opmaak en CMap-opmaak.
|
De map `static/` bevat de browserapplicatie. `static/index.html` definieert de views en dialogen. `static/js/wiki.js` beheert routedispatch, API-aanroepen, editor- en paginatoestand en speciale views. De modules onder `static/js/wiki/` bevatten afzonderlijk de referentiesyntaxis, routeherkenning, Markdowntransformaties, paginakoppen en breadcrumbhistorie. De controllers onder `static/js/wiki/admin/` beheren ieder één administratief formulier of overzicht; routes, permissiecontrole, breadcrumbs en algemene adminnavigatie blijven in `wiki.js`.
|
||||||
|
|
||||||
|
De generieke CMap-component staat onder `static/js/cmap/`. `cmap.js` levert de tekenengine. `cmap-racket-wiki.js` is de wiki-editorfacade en composeert document-, geometrie-, relatie-, layout-, selectie-, interactie- en submapcontrollers. De views voor itemdecoratie, grensverwijzingen en SVG-snapshots houden presentatie buiten het model. De wiki-specifieke hostlaag staat onder `static/js/wiki/cmap/`: `CmapWorkspaceController` bouwt afhankelijkheden en registreert DOM-events; `CmapEditorHost`, `CmapEditorUiController`, `CmapStorageController`, `CmapNavigationController`, `CmapTransferController`, `CmapMapController` en `CmapConceptController` bezitten ieder hun eigen workflow. `CmapEmbedView` toont opgeslagen SVG-renders in Markdown en `CmapEditorPresentation` levert editorpresentatie en gebruikstellingen. CSS is verdeeld tussen algemene wiki-opmaak en CMap-opmaak.
|
||||||
|
|
||||||
## Afhankelijkheidsrichting
|
## Afhankelijkheidsrichting
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ Deze richting houdt de belangrijkste domeinregels buiten de UI. Een rolcontrole
|
|||||||
|
|
||||||
Een pagina heeft een database-id, namespace, slug, actuele Markdown en een versieteller. `page_versions` verwijst naar dezelfde pagina-id. Todo's, bookmarks, aliases en bijlageverwijzingen sluiten via die id aan.
|
Een pagina heeft een database-id, namespace, slug, actuele Markdown en een versieteller. `page_versions` verwijst naar dezelfde pagina-id. Todo's, bookmarks, aliases en bijlageverwijzingen sluiten via die id aan.
|
||||||
|
|
||||||
Een CMap heeft een stabiele slug, titel, JSONB-document en versieteller. `concept_map_versions` bewaart volledige JSONB-snapshots. CMap-items kunnen via `pageSlug` naar een wikipagina verwijzen en via `cmapSlug` naar een andere CMap. De browser gebruikt deze verwijzingen om het gekoppelde doel rechtstreeks te openen.
|
Een CMap heeft een stabiele slug, titel, JSONB-document en versieteller. `concept_map_versions` bewaart volledige JSONB-snapshots; `cmap_renders` bewaart de actuele SVG-weergave per documentversie apart. CMap-items kunnen via `pageSlug` naar een wikipagina verwijzen en via `cmapSlug` naar een andere CMap. De browser gebruikt deze verwijzingen om het gekoppelde doel rechtstreeks te openen.
|
||||||
|
|
||||||
Zie [Data, transacties en versiebeheer](racket-wiki:data-en-versies) voor de invarianten en [Modulariteit en afhankelijkheden](racket-wiki:modulariteit) voor de gewenste grenzen bij uitbreiding.
|
Zie [Data, transacties en versiebeheer](racket-wiki:data-en-versies) voor de invarianten en [Modulariteit en afhankelijkheden](racket-wiki:modulariteit) voor de gewenste grenzen bij uitbreiding.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ De backendfuncties zijn meestal procedureel en krijgen `config` expliciet mee. D
|
|||||||
|
|
||||||
Versie 0.2.93 introduceert wel tests in `architecture/import.rkt`. Die controleren de manifestset, interne paginalinks, CMap-embeds, unieke node-id's, connector-endpoints en de bronmarkeringen waarmee lokaal gewijzigde importinhoud wordt beschermd. Dit is een begin, geen voldoende dekking van de wiki.
|
Versie 0.2.93 introduceert wel tests in `architecture/import.rkt`. Die controleren de manifestset, interne paginalinks, CMap-embeds, unieke node-id's, connector-endpoints en de bronmarkeringen waarmee lokaal gewijzigde importinhoud wordt beschermd. Dit is een begin, geen voldoende dekking van de wiki.
|
||||||
|
|
||||||
Versie 0.2.100 voegt pure Node-tests toe voor de Markdown-export: dieptebegrenzing, cycli, optionele wikipagina's, persoonstags, leesbare relaties en afgeleide sub-CMapweergaven. De extractie van persoonsnamen uit CMap-documenten heeft daarnaast een kleine RackUnit-test; database- en browsergedrag blijven integratietestwerk.
|
De Node-suite bevat 83 tests voor het CMap-model, repositorycontracten, import/export, connectorherbedrading, selectie-layout, lagen, historie, submapnavigatie, instellingen en Markdown-transformaties. De tests draaien zonder bundler met `node --test test/*.test.js test/*.test.mjs`. Database- en volledig browsergedrag blijven integratietestwerk.
|
||||||
|
|
||||||
Versie 0.2.115 voegt pure roundtrip- en validatietests toe voor het JSON-uitwisselingsformaat. Zij controleren dat coördinaten, afmetingen, kleuren, typografie, groepering en connectoren in de diagramlaag blijven, dat gedeelde conceptinhoud apart wordt opgeslagen en bij import wordt teruggekoppeld, en dat losse concepten, dubbele namen/slugs en ongeldige connector-endpoints worden geweigerd.
|
Versie 0.2.115 voegt pure roundtrip- en validatietests toe voor het JSON-uitwisselingsformaat. Zij controleren dat coördinaten, afmetingen, kleuren, typografie, groepering en connectoren in de diagramlaag blijven, dat gedeelde conceptinhoud apart wordt opgeslagen en bij import wordt teruggekoppeld, en dat losse concepten, dubbele namen/slugs en ongeldige connector-endpoints worden geweigerd.
|
||||||
|
|
||||||
@@ -51,6 +51,8 @@ Introduceer geen groot mockframework. Gewone procedures en expliciete parameters
|
|||||||
|
|
||||||
Voor frontendcode zijn browsercomponenttests met een echte DOM geschikter dan het namaken van ieder element. De CMap-adapter moet met een klein vast document getest kunnen worden zonder de hele wiki te starten.
|
Voor frontendcode zijn browsercomponenttests met een echte DOM geschikter dan het namaken van ieder element. De CMap-adapter moet met een klein vast document getest kunnen worden zonder de hele wiki te starten.
|
||||||
|
|
||||||
|
De CMap-controllers zijn bewust via kleine constructorcontracten te testen. Nieuwe gedragstests horen primair bij de eigenaar: documentwijzigingen bij de editorcontrollers, autosave en conflicten bij `CmapStorageController`, routecontext bij `CmapNavigationController`, kaartworkflows bij `CmapMapController` en conceptdialogen, selectie en namespace-migratie bij `CmapConceptController`. Een workspace-test is alleen nodig voor de koppeling van deze grenzen of voor een daadwerkelijk DOM-gebaseerde gebruikersflow.
|
||||||
|
|
||||||
De JavaScript-regressietest `test/cmap-connector-rewire.test.js` simuleert het verslepen van een
|
De JavaScript-regressietest `test/cmap-connector-rewire.test.js` simuleert het verslepen van een
|
||||||
connector naar een andere verbindingszin, klapt de submap in en uit en controleert daarna zowel de
|
connector naar een andere verbindingszin, klapt de submap in en uit en controleert daarna zowel de
|
||||||
tekenlaag als de geserialiseerde `sourceId`.
|
tekenlaag als de geserialiseerde `sourceId`.
|
||||||
|
|||||||
@@ -49,9 +49,17 @@ objects, and CMap state is not persisted in browser storage.
|
|||||||
`view/appearance-editor.js` presents the appearance model in the concept dialog
|
`view/appearance-editor.js` presents the appearance model in the concept dialog
|
||||||
and coordinates explicit style and palette changes with its repository.
|
and coordinates explicit style and palette changes with its repository.
|
||||||
The concrete dialog controllers live in `../wiki/cmap/dialogs/`. They own
|
The concrete dialog controllers live in `../wiki/cmap/dialogs/`. They own
|
||||||
their fields, validation and browser events. The workspace supplies the active
|
their fields, validation and browser events. The wiki-specific modules under
|
||||||
CMap and performs editor transitions, while the controllers use repository
|
`../wiki/cmap/` form the editor host layer. `CmapEditorHost` owns creation and
|
||||||
APIs for stored CMaps and people instead of calling backend routes directly.
|
destruction of the active editor; `CmapEditorUiController` owns the standard
|
||||||
|
toolbar actions; `CmapStorageController` owns dirty state, autosave and save
|
||||||
|
ordering; `CmapNavigationController` owns routes and browser context;
|
||||||
|
`CmapTransferController` owns import and export; `CmapMapController` owns
|
||||||
|
stored-map workflows; and `CmapConceptController` owns dialog-backed concept
|
||||||
|
workflows. `CmapWorkspaceController` composes these modules, supplies the
|
||||||
|
active CMap and registers the remaining dialog and DOM integration. Controllers
|
||||||
|
use repository APIs for stored CMaps and people instead of calling backend
|
||||||
|
routes directly.
|
||||||
`cmap-racket-wiki.js` contains the wiki-specific editor controller, selection
|
`cmap-racket-wiki.js` contains the wiki-specific editor controller, selection
|
||||||
state, content-based initial sizing, resize and relation controls. Its view
|
state, content-based initial sizing, resize and relation controls. Its view
|
||||||
records connect pure model ids to drawing nodes without making those nodes part
|
records connect pure model ids to drawing nodes without making those nodes part
|
||||||
@@ -98,7 +106,11 @@ metadata remain intact, but its document no longer depends on the parent CMap.
|
|||||||
formatting, positions, connectors, recursive submap membership and promoted
|
formatting, positions, connectors, recursive submap membership and promoted
|
||||||
map references. The public `currentModel`, `loadModel` and `replaceModel`
|
map references. The public `currentModel`, `loadModel` and `replaceModel`
|
||||||
methods let the workspace pass complete domain models to and from the editor.
|
methods let the workspace pass complete domain models to and from the editor.
|
||||||
The CMap repository, rather than the workspace, persists those models.
|
The CMap repository, rather than the workspace, persists those models. The
|
||||||
|
editor also provides `snapshotSvg()` for an embed render. That SVG is saved in
|
||||||
|
the separate `cmap_renders` backend relation and is never inserted into the
|
||||||
|
editable CMap document. `CmapEmbedView` renders this stored SVG as a linkable
|
||||||
|
image for `{{cmap:...}}` Markdown embeds.
|
||||||
|
|
||||||
## JSON interchange
|
## JSON interchange
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ export class CmapConceptController {
|
|||||||
this.getDialog()?.openNew(context, this.dialogResources());
|
this.getDialog()?.openNew(context, this.dialogResources());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Apply one validated concept-dialog submission as one editor history step.
|
||||||
|
* pre : record identifies an existing placement, or createContext describes a new placement.
|
||||||
|
* post : Shared concept content and placement presentation are updated and autosave is scheduled.
|
||||||
|
* result : False when the editor or required create context is unavailable.
|
||||||
|
*/
|
||||||
async saveConcept(record, createContext, values) {
|
async saveConcept(record, createContext, values) {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
if ((!record && !createContext) || !editor) return false;
|
if ((!record && !createContext) || !editor) return false;
|
||||||
@@ -101,6 +107,7 @@ export class CmapConceptController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Update selection-dependent layout and concept action availability in the toolbar. */
|
||||||
updateSelectionToolbar(editor, selectedRecords = [], connector = null) {
|
updateSelectionToolbar(editor, selectedRecords = [], connector = null) {
|
||||||
const toolbar = document.getElementById("cmap-selection-toolbar");
|
const toolbar = document.getElementById("cmap-selection-toolbar");
|
||||||
const selected = Array.isArray(selectedRecords) ? selectedRecords : [];
|
const selected = Array.isArray(selectedRecords) ? selectedRecords : [];
|
||||||
@@ -127,6 +134,12 @@ export class CmapConceptController {
|
|||||||
parts.slug.toLocaleLowerCase();
|
parts.slug.toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Move selected concept description pages into the active CMap namespace.
|
||||||
|
* pre : An active CMap namespace exists and selected records may have description references.
|
||||||
|
* post : Existing pages are renamed, editor references are changed and the map is autosaved.
|
||||||
|
* result : False when no applicable selection exists or a target-page conflict is found.
|
||||||
|
*/
|
||||||
async moveSelectedDescriptionsToNamespace() {
|
async moveSelectedDescriptionsToNamespace() {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
const namespace = this.getNamespace();
|
const namespace = this.getNamespace();
|
||||||
@@ -187,6 +200,7 @@ export class CmapConceptController {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Open inline phrase editing or the concept dialog for the selected editor record. */
|
||||||
editSelected(record = null) {
|
editSelected(record = null) {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
const selectedRecord = record || editor?.selected();
|
const selectedRecord = record || editor?.selected();
|
||||||
@@ -201,6 +215,7 @@ export class CmapConceptController {
|
|||||||
this.openDialog(selectedRecord);
|
this.openDialog(selectedRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Group the current editor selection into a named inline submap. */
|
||||||
groupSelection() {
|
groupSelection() {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
if (!editor || !editor.canGroupSelection()) return false;
|
if (!editor || !editor.canGroupSelection()) return false;
|
||||||
@@ -216,6 +231,7 @@ export class CmapConceptController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Populate a newly opened sample submap with its initial child records. */
|
||||||
populateSubmap(record, editor) {
|
populateSubmap(record, editor) {
|
||||||
const baseX = Number(record.node.attr("x"));
|
const baseX = Number(record.node.attr("x"));
|
||||||
const baseY = Number(record.node.attr("y"));
|
const baseY = Number(record.node.attr("y"));
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export class CmapEditorHost {
|
|||||||
this.editor = null;
|
this.editor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Replace any active editor with a newly configured editor instance.
|
||||||
|
* post : The previous editor is destroyed; an optional model is loaded before onCreated runs.
|
||||||
|
* result : The active editor, or null when no factory is configured.
|
||||||
|
*/
|
||||||
create(options = null, model = null) {
|
create(options = null, model = null) {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
if (!this.factory) return null;
|
if (!this.factory) return null;
|
||||||
@@ -23,6 +28,7 @@ export class CmapEditorHost {
|
|||||||
return this.editor;
|
return this.editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Destroy the active editor once and release the host's reference to it. */
|
||||||
destroy() {
|
destroy() {
|
||||||
if (!this.editor) return;
|
if (!this.editor) return;
|
||||||
this.editor.destroy();
|
this.editor.destroy();
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export class CmapEditorUiController {
|
|||||||
this.listeners.push(() => element.removeEventListener(event, listener));
|
this.listeners.push(() => element.removeEventListener(event, listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind every standard CMap toolbar action once.
|
||||||
|
* result : This controller, so the host may retain the initialized instance.
|
||||||
|
*/
|
||||||
initialize() {
|
initialize() {
|
||||||
this.bind("#cmap-save-map", "click", () => this.actions.save?.());
|
this.bind("#cmap-save-map", "click", () => this.actions.save?.());
|
||||||
this.bind("#cmap-undo", "click", (_event, editor) => editor?.undo());
|
this.bind("#cmap-undo", "click", (_event, editor) => editor?.undo());
|
||||||
@@ -47,6 +51,7 @@ export class CmapEditorUiController {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Remove every listener installed by initialize before the host is disposed. */
|
||||||
destroy() {
|
destroy() {
|
||||||
for (const remove of this.listeners) remove();
|
for (const remove of this.listeners) remove();
|
||||||
this.listeners = [];
|
this.listeners = [];
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ export class CmapMapController {
|
|||||||
this.refreshPageConnections = refreshPageConnections;
|
this.refreshPageConnections = refreshPageConnections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Refresh the CMap catalogue and its lightweight global reference index.
|
||||||
|
* post : Workspace CMap lists, selector and usage indicators reflect the repository.
|
||||||
|
* result : The current list of stored CMaps.
|
||||||
|
*/
|
||||||
async loadMaps() {
|
async loadMaps() {
|
||||||
const [conceptMaps, placements] = await Promise.all([
|
const [conceptMaps, placements] = await Promise.all([
|
||||||
this.repository.list(),
|
this.repository.list(),
|
||||||
@@ -65,6 +70,11 @@ export class CmapMapController {
|
|||||||
this.renderSelector();
|
this.renderSelector();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Open a stored CMap or its source map when it is a derived submap view.
|
||||||
|
* pre : slug identifies a stored CMap; a later request supersedes this load safely.
|
||||||
|
* post : Active map state, editor document and saved snapshot are synchronized.
|
||||||
|
*/
|
||||||
async open(slug) {
|
async open(slug) {
|
||||||
if (!slug) return;
|
if (!slug) return;
|
||||||
const loadSequence = ++this.state.cmapLoadSequence;
|
const loadSequence = ++this.state.cmapLoadSequence;
|
||||||
@@ -103,6 +113,7 @@ export class CmapMapController {
|
|||||||
this.status(this.translate("concept-map-loaded", "CMap loaded"), true);
|
this.status(this.translate("concept-map-loaded", "CMap loaded"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Restore the source submap context for a stored derived CMap. */
|
||||||
openDerivedView(conceptMap, derivedView) {
|
openDerivedView(conceptMap, derivedView) {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
const root = editor.itemRecord(derivedView.rootItemId);
|
const root = editor.itemRecord(derivedView.rootItemId);
|
||||||
@@ -124,6 +135,10 @@ export class CmapMapController {
|
|||||||
editor.openSubmapMap(root);
|
editor.openSubmapMap(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Load one immutable CMap version for review or subsequent explicit save.
|
||||||
|
* post : The editor shows the historical model while the current map remains dirty.
|
||||||
|
*/
|
||||||
async loadHistoricalVersion(version) {
|
async loadHistoricalVersion(version) {
|
||||||
const conceptMap = this.getCurrentStorageMap();
|
const conceptMap = this.getCurrentStorageMap();
|
||||||
if (!conceptMap) return;
|
if (!conceptMap) return;
|
||||||
@@ -145,6 +160,11 @@ export class CmapMapController {
|
|||||||
location.hash = this.cmapRoute(conceptMap.slug);
|
location.hash = this.cmapRoute(conceptMap.slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Extract the selected inline submap into a complete stored CMap.
|
||||||
|
* post : Parent and child map are saved consistently; cross-boundary relations remain on the parent.
|
||||||
|
* result : Whether extraction completed or was deliberately cancelled.
|
||||||
|
*/
|
||||||
async promoteSelectedSubmap() {
|
async promoteSelectedSubmap() {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
const record = editor ? editor.selected() : null;
|
const record = editor ? editor.selected() : null;
|
||||||
@@ -263,6 +283,11 @@ export class CmapMapController {
|
|||||||
{ tags: [], summary: "", explanationPageSlug: "" };
|
{ tags: [], summary: "", explanationPageSlug: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Persist map metadata through either the source editor or a derived map model.
|
||||||
|
* post : Namespace, tags, summary and explanation-page reference are stored together.
|
||||||
|
* result : False when there is no editable map or saving fails.
|
||||||
|
*/
|
||||||
async saveMetadata(metadata) {
|
async saveMetadata(metadata) {
|
||||||
const conceptMap = this.state.currentConceptMap;
|
const conceptMap = this.state.currentConceptMap;
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
@@ -290,6 +315,11 @@ export class CmapMapController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Archive the active CMap after explicit title confirmation.
|
||||||
|
* post : Active map state is cleared and navigation continues to another CMap or overview.
|
||||||
|
* result : False when confirmation is cancelled, mismatched or persistence fails.
|
||||||
|
*/
|
||||||
async archive() {
|
async archive() {
|
||||||
this.cancelAutosave();
|
this.cancelAutosave();
|
||||||
const conceptMap = this.state.currentConceptMap;
|
const conceptMap = this.state.currentConceptMap;
|
||||||
@@ -324,6 +354,7 @@ export class CmapMapController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create an explicit, named immutable snapshot of the active stored CMap. */
|
||||||
async createSnapshot() {
|
async createSnapshot() {
|
||||||
if (!this.getCurrentStorageMap()) return false;
|
if (!this.getCurrentStorageMap()) return false;
|
||||||
const description = window.prompt(this.translate("snapshot-description", "Snapshot description"), "");
|
const description = window.prompt(this.translate("snapshot-description", "Snapshot description"), "");
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ export class CmapNavigationController {
|
|||||||
this.location = locationObject;
|
this.location = locationObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Open a stored CMap through the central hash-navigation contract. */
|
||||||
open(slug) {
|
open(slug) {
|
||||||
return this.navigateToHash(this.cmapRoute(slug));
|
return this.navigateToHash(this.cmapRoute(slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Remember the active separate-submap root before following a linked CMap. */
|
||||||
openLinked(record) {
|
openLinked(record) {
|
||||||
if (!record?.cmapSlug) return false;
|
if (!record?.cmapSlug) return false;
|
||||||
this.rememberContext();
|
this.rememberContext();
|
||||||
@@ -25,6 +27,7 @@ export class CmapNavigationController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return to a source CMap when present, otherwise leave one inline submap level. */
|
||||||
openParent() {
|
openParent() {
|
||||||
const source = this.getParentMap();
|
const source = this.getParentMap();
|
||||||
if (source?.slug) {
|
if (source?.slug) {
|
||||||
@@ -39,6 +42,7 @@ export class CmapNavigationController {
|
|||||||
return this.storage.requestTransition(action);
|
return this.storage.requestTransition(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Store the active separate-submap context in the current browser history entry. */
|
||||||
rememberContext() {
|
rememberContext() {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
const current = this.getCurrentMap();
|
const current = this.getCurrentMap();
|
||||||
@@ -50,6 +54,10 @@ export class CmapNavigationController {
|
|||||||
} }, "", this.location.href);
|
} }, "", this.location.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore a remembered separate-submap root after browser navigation.
|
||||||
|
* post : A consumed context is removed from history state to prevent repeated restoration.
|
||||||
|
*/
|
||||||
async restoreContext(slug) {
|
async restoreContext(slug) {
|
||||||
const context = this.history.state?.cmapContext;
|
const context = this.history.state?.cmapContext;
|
||||||
if (!context || context.mapSlug !== slug) return false;
|
if (!context || context.mapSlug !== slug) return false;
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ export class CmapStorageController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Queue one delayed autosave for the current dirty CMap.
|
||||||
|
* post : Any preceding autosave timer is replaced; non-editable or clean maps are not queued.
|
||||||
|
*/
|
||||||
scheduleAutosave() {
|
scheduleAutosave() {
|
||||||
this.cancelAutosave();
|
this.cancelAutosave();
|
||||||
if (!this.canEdit() || !this.getCurrentMap() || !this.hasUnsavedChanges()) return;
|
if (!this.canEdit() || !this.getCurrentMap() || !this.hasUnsavedChanges()) return;
|
||||||
@@ -59,6 +63,10 @@ export class CmapStorageController {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Guard a route or view transition while unsaved CMap changes exist.
|
||||||
|
* result : True only when the action ran after saving, discarding or finding no changes.
|
||||||
|
*/
|
||||||
async requestTransition(action) {
|
async requestTransition(action) {
|
||||||
if (!this.hasUnsavedChanges()) {
|
if (!this.hasUnsavedChanges()) {
|
||||||
await action();
|
await action();
|
||||||
@@ -93,6 +101,11 @@ export class CmapStorageController {
|
|||||||
if (this.savePromise) await this.savePromise;
|
if (this.savePromise) await this.savePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Persist the active editor model and its derived SVG render in one ordered workflow.
|
||||||
|
* post : A successful save updates the current map, saved snapshot and CMap catalogue.
|
||||||
|
* result : False for cancellation, unavailable editor or persistence failure.
|
||||||
|
*/
|
||||||
async save({ automatic = false, force = false, summary = null,
|
async save({ automatic = false, force = false, summary = null,
|
||||||
snapshotVersion = false, historyMode = null } = {}) {
|
snapshotVersion = false, historyMode = null } = {}) {
|
||||||
const editor = this.getEditor();
|
const editor = this.getEditor();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class CmapTransferController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Save pending changes, then create the requested readable Markdown export. */
|
||||||
async exportMarkdown(options) {
|
async exportMarkdown(options) {
|
||||||
const map = this.getCurrentMap();
|
const map = this.getCurrentMap();
|
||||||
if (!map) throw new Error(this.translate("cmap-export-unavailable", "CMap export is unavailable."));
|
if (!map) throw new Error(this.translate("cmap-export-unavailable", "CMap export is unavailable."));
|
||||||
@@ -19,6 +20,7 @@ export class CmapTransferController {
|
|||||||
return this.markdownExporter.export(map, options);
|
return this.markdownExporter.export(map, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Save pending changes, then create the requested portable JSON bundle. */
|
||||||
async exportJson(depth) {
|
async exportJson(depth) {
|
||||||
const map = this.getCurrentMap();
|
const map = this.getCurrentMap();
|
||||||
if (!map) throw new Error(this.translate("cmap-json-unavailable", "CMap JSON export is unavailable."));
|
if (!map) throw new Error(this.translate("cmap-json-unavailable", "CMap JSON export is unavailable."));
|
||||||
@@ -26,6 +28,11 @@ export class CmapTransferController {
|
|||||||
return this.jsonExporter.export(map, depth);
|
return this.jsonExporter.export(map, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* goal : Import a validated CMap bundle after presenting replacement conflicts.
|
||||||
|
* post : Page and CMap catalogues are refreshed and navigation opens the imported root CMap.
|
||||||
|
* result : The importer summary.
|
||||||
|
*/
|
||||||
async importFile(file) {
|
async importFile(file) {
|
||||||
const bundle = await this.jsonImporter.read(file);
|
const bundle = await this.jsonImporter.read(file);
|
||||||
await this.saveBeforeTransfer();
|
await this.saveBeforeTransfer();
|
||||||
|
|||||||
@@ -292,6 +292,19 @@ export class CmapWorkspaceController {
|
|||||||
return text.length > 150 ? `${text.slice(0, 147)}…` : text;
|
return text.length > 150 ? `${text.slice(0, 147)}…` : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showCmapDescriptionTooltip(button) {
|
||||||
|
const item = button.closest("[data-rw-cmap-item-id]");
|
||||||
|
const editor = cmapPrototypeState().editor;
|
||||||
|
const record = item && editor ? editor.itemRecord(Number(item.dataset.rwCmapItemId)) : null;
|
||||||
|
if (record?.descriptionPageSlug) {
|
||||||
|
cmapDescriptionPreview.show(button, record.descriptionPageSlug, tr("loading", "Loading…"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideCmapDescriptionTooltip() {
|
||||||
|
cmapDescriptionPreview.hide();
|
||||||
|
}
|
||||||
|
|
||||||
function addCmapPrototypeNode(options = {}) {
|
function addCmapPrototypeNode(options = {}) {
|
||||||
return conceptController.addNode(options);
|
return conceptController.addNode(options);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user