{"_id":"@databis/docs-module","_rev":"2-9c80b6102eb08e22163a47c9dcfe8b40","name":"@databis/docs-module","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@databis/docs-module","version":"1.0.0","keywords":["documentation","web-component","custom-element","tiptap","prosemirror","rich-text-editor","laravel","framework-agnostic"],"license":"CC-BY-NC-SA-4.0","_id":"@databis/docs-module@1.0.0","maintainers":[{"name":"databis","email":"hello@databis.net"}],"dist":{"shasum":"9ea105ec334e038af637b9680ed848566af020fe","tarball":"https://registry.npmjs.org/@databis/docs-module/-/docs-module-1.0.0.tgz","fileCount":54,"integrity":"sha512-8tdh3xBaIpuzoQA4yBDWuKQYGx9MgB9i1Lhd9Qa8o/a/vcwG9GjaryYwW6gKDLJfi1fbGCjPLvUK3JaxLQSOXA==","signatures":[{"sig":"MEUCIQDIRUvby/GoHRIEmWpVnZzlEJfs0GeVcgtTFiz4nXriPAIgBtPrilwskc6EwjZv20mYfORKZ05RxSMWR2HIZR+IPdE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14878177},"main":"dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js","require":"./dist/index.cjs"},"./api":{"types":"./dist/api.d.ts","import":"./dist/api.js","default":"./dist/api.js","require":"./dist/api.cjs"},"./editor":{"types":"./dist/editor.d.ts","import":"./dist/editor.js","default":"./dist/editor.js","require":"./dist/editor.cjs"},"./schema":{"types":"./dist/schema.d.ts","import":"./dist/schema.js","default":"./dist/schema.js","require":"./dist/schema.cjs"},"./renderer":{"types":"./dist/renderer.d.ts","import":"./dist/renderer.js","default":"./dist/renderer.js","require":"./dist/renderer.cjs"},"./style.css":"./dist/style.css","./local-worker":{"types":"./dist/local-worker.d.ts","import":"./dist/local-worker.js","default":"./dist/local-worker.js","require":"./dist/local-worker.cjs"}},"gitHead":"62762debc2e4b5774a6996d66237a0be6aad1eb3","scripts":{"test":"tsx --test test/api-client.test.ts test/docs-element.test.ts test/editor.test.ts test/local-worker.test.ts test/schema.test.ts test/tiptap-renderer.test.ts","build":"tsup --config tsup.config.ts && tsc --project tsconfig.build.json && tsx scripts/copy-build-assets.ts && tsc --project tsconfig.package.json && tsx scripts/verify-build.ts","check":"npm run typecheck","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"databis","email":"hello@databis.net"},"_npmVersion":"10.9.0","description":"Framework-agnostic documentation UI and API client for databis/laravel-docs-module","directories":{},"sideEffects":["./dist/style.css"],"_nodeVersion":"22.12.0","dependencies":{"@tiptap/pm":"^3.29.1","@tiptap/core":"^3.29.1","@tiptap/starter-kit":"^3.29.1","@tiptap/extension-image":"^3.29.1","@tiptap/extension-table":"^3.29.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","tsup":"^8.0.0","jsdom":"^29.1.1","typescript":"^7.0.2","@types/node":"^24.0.0","@types/jsdom":"^27.0.0"},"_npmOperationalInternal":{"tmp":"tmp/docs-module_1.0.0_1788185804038_0.23760361826314935","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@databis/docs-module","version":"1.0.1","description":"Framework-agnostic documentation UI and API client for databis/laravel-docs-module","type":"module","main":"dist/index.cjs","module":"dist/index.js","types":"./dist/index.d.ts","sideEffects":["./dist/style.css"],"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs","default":"./dist/index.js"},"./style.css":"./dist/style.css","./api":{"types":"./dist/api.d.ts","import":"./dist/api.js","require":"./dist/api.cjs","default":"./dist/api.js"},"./local-worker":{"types":"./dist/local-worker.d.ts","import":"./dist/local-worker.js","require":"./dist/local-worker.cjs","default":"./dist/local-worker.js"},"./renderer":{"types":"./dist/renderer.d.ts","import":"./dist/renderer.js","require":"./dist/renderer.cjs","default":"./dist/renderer.js"},"./editor":{"types":"./dist/editor.d.ts","import":"./dist/editor.js","require":"./dist/editor.cjs","default":"./dist/editor.js"},"./schema":{"types":"./dist/schema.d.ts","import":"./dist/schema.js","require":"./dist/schema.cjs","default":"./dist/schema.js"}},"scripts":{"test":"tsx --test test/api-client.test.ts test/docs-element.test.ts test/editor.test.ts test/local-worker.test.ts test/schema.test.ts test/tiptap-renderer.test.ts","typecheck":"tsc --noEmit","check":"npm run typecheck","build":"tsup --config tsup.config.ts && tsc --project tsconfig.build.json && tsx scripts/copy-build-assets.ts && tsc --project tsconfig.package.json && tsx scripts/verify-build.ts","prepublishOnly":"npm run build"},"keywords":["documentation","web-component","custom-element","tiptap","prosemirror","rich-text-editor","laravel","framework-agnostic"],"license":"MIT","engines":{"node":">=18"},"publishConfig":{"access":"public"},"dependencies":{"@tiptap/core":"^3.29.1","@tiptap/extension-image":"^3.29.1","@tiptap/extension-table":"^3.29.1","@tiptap/pm":"^3.29.1","@tiptap/starter-kit":"^3.29.1"},"devDependencies":{"@types/jsdom":"^27.0.0","@types/node":"^24.0.0","jsdom":"^29.1.1","tsup":"^8.0.0","tsx":"^4.0.0","typescript":"^7.0.2"},"_id":"@databis/docs-module@1.0.1","gitHead":"4163beb67b13987e907ae5f22bbe4114cfbe0d85","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-j1gPvXfmvdDqPKmgJqr1ZtBnFY0UfjdsfQkH8o0+NmjUPdLqogOK1Zjp2UBSahaM8lkZMDQXcPMT2kMSpz2+3Q==","shasum":"d31faf9802f74de3696b4adde8e9f8eeb24b9fe8","tarball":"https://registry.npmjs.org/@databis/docs-module/-/docs-module-1.0.1.tgz","fileCount":54,"unpackedSize":14983278,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD+LUA7G9tthKFL/9bQCJ3Ofnwt7k2i+c/E0znnwLEGCwIgJxppuQT8UpR2yZF2IN94XHO01utgQzq0jo0OOBm8Bd4="}]},"_npmUser":{"name":"databis","email":"hello@databis.net"},"directories":{},"maintainers":[{"name":"databis","email":"hello@databis.net"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/docs-module_1.0.1_1788448477102_0.25205610941590084"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T14:16:43.806Z","modified":"2026-09-03T15:14:37.756Z","1.0.0":"2026-08-31T14:16:44.314Z","1.0.1":"2026-09-03T15:14:37.371Z"},"license":"MIT","keywords":["documentation","web-component","custom-element","tiptap","prosemirror","rich-text-editor","laravel","framework-agnostic"],"description":"Framework-agnostic documentation UI and API client for databis/laravel-docs-module","maintainers":[{"name":"databis","email":"hello@databis.net"}],"readme":"# @databis/docs-module\n\nFramework-agnostic UI and API client for\n[`databis/laravel-docs-module`](../laravel-docs-module/README.md). It is built on\nstandard Custom Elements, `fetch`, HTML and CSS, and installs no framework into\nthe host application — no Vue, no React.\n\nIts one real dependency is **TipTap**, which powers the editor. Everything else\nis the platform. TipTap earns its place: ProseMirror's schema enforcement, input\nrules, history and paste handling are exactly the parts that are painful to\nreimplement and that quietly corrupt documents when they are subtly wrong.\n\nThe package contains:\n\n- `<databis-docs>`, a responsive documentation browser with category\n  navigation, article search, a table of contents and rich-text editing for\n  authorized users.\n- `createArticleEditor`, the editor on its own, for a custom UI.\n- `DocsApiClient`, for applications that want to build everything themselves.\n- `renderTiptapDocument`, a dependency-free DOM renderer for reading. Pages that\n  only display documentation never load the editor.\n- The shared schema definition, so the editor cannot drift from what the backend\n  accepts.\n- Strict TypeScript source with declarations generated from the implementation.\n\n## Requirements\n\n- A modern browser with Custom Elements and `fetch`.\n- A modern ESM-aware bundler, Node.js CommonJS loader, or direct browser import\n  of the compiled ESM entry.\n- `databis/laravel-docs-module` with the same major version.\n\n| npm | Composer |\n| --- | --- |\n| `@databis/docs-module ^1.0` | `databis/laravel-docs-module ^1.0` |\n\n## Install\n\n```bash\nnpm install @databis/docs-module\n```\n\nImport the module and its styles once:\n\n```js\nimport '@databis/docs-module'\nimport '@databis/docs-module/style.css'\n```\n\nThen use the element in HTML:\n\n```html\n<databis-docs\n  base-url=\"/api/docs\"\n  storage-base-url=\"/storage\"\n  locale=\"es\"\n  sync-hash\n></databis-docs>\n```\n\n`base-url` is required in normal mode and is omitted when the component\nreceives `testing: true`. `storage-base-url` is only used for image `src`\nvalues that are not already absolute, and defaults to `/storage`. With `sync-hash`, the current location looks like\n`#category-slug/article-slug` and survives a reload.\n\n## Local testing without a backend\n\nPass the `testing` variable to the main component to replace `DocsApiClient`\nwith `LocalWorker`. This mode never calls `fetch`: reads, categories, articles,\nedits, publication state, revisions and testing images are kept locally.\n\n```js\nconst testing = true\nconst docs = document.querySelector('databis-docs')\n\ndocs.options = {\n  testing,\n  locale: 'es',\n  testingStorage: 'localStorage',\n  testingStorageKey: '@databis/docs-module/my-test',\n  testingData: {\n    categories: [],\n    articles: []\n  }\n}\n```\n\n`localStorage` is the default, so `testingStorage` may be omitted. The\n`testingData` value only seeds an empty store; reloading the page keeps all\nlater changes. The current normalized state is also available from\n`docs.testingData`.\n\nFor an inspectable global variable instead of `localStorage`, select the global\nadapter and name the property that will contain all data:\n\n```js\nwindow.docsTestingData = {\n  categories: [],\n  articles: []\n}\n\ndocs.options = {\n  testing: true,\n  testingStorage: 'global',\n  testingGlobalKey: 'docsTestingData',\n  testingData: window.docsTestingData\n}\n```\n\nThe boolean HTML attribute is supported too:\n\n```html\n<databis-docs testing locale=\"es\"></databis-docs>\n```\n\nWhen `testing` is false or absent, the component uses the normal API client and\n`baseURL` is required. `examples/index.html` runs in testing mode by default;\nadding `?storage=global` stores its state in `window.docsTestingData`.\n\n## Authentication and JavaScript options\n\nFunctions cannot be represented as HTML attributes. Assign `options` when a\ntoken or custom messages are needed:\n\n```js\nconst docs = document.querySelector('databis-docs')\n\ndocs.options = {\n  baseURL: '/api/docs',\n  storageBaseURL: '/storage',\n  locale: 'es',\n  getAuthToken: () => localStorage.getItem('token'),\n  messages: {\n    search: 'Buscar en el manual'\n  }\n}\n```\n\n### Laravel Sanctum cookies\n\nFor Sanctum cookie authentication, omit `getAuthToken`. Cookie credentials are\nincluded by default. Before the first authenticated write, the host application\nmust obtain the CSRF cookie in the normal Sanctum login flow:\n\n```js\nawait fetch('http://127.0.0.1:8000/sanctum/csrf-cookie', {\n  credentials: 'include'\n})\n\ndocs.options = {\n  baseURL: 'http://127.0.0.1:8000/api/docs',\n  storageBaseURL: 'http://127.0.0.1:8000/storage',\n  credentials: 'include'\n}\n```\n\nOn every `POST`, `PUT`, `PATCH` and `DELETE`, `DocsApiClient` reads the current\n`XSRF-TOKEN` cookie, URL-decodes it and sends it as `X-XSRF-TOKEN`. The browser\ncontinues to attach the cookie itself; JavaScript is not allowed to set a\n`Cookie` request header.\n\nDo not copy a token into the package options. Laravel rotates it with the\nsession, so the client reads the current cookie before every write. If the\nfrontend and backend use different ports, use the same hostname for both\n(`127.0.0.1` or `localhost`, but do not mix them).\n\nThe names can be changed for a customized backend:\n\n```js\ndocs.options = {\n  baseURL: '/api/docs',\n  xsrfCookieName: 'XSRF-TOKEN',\n  xsrfHeaderName: 'X-XSRF-TOKEN',\n  withXsrfToken: true\n}\n```\n\n## Imperative mounting\n\n`createDocsModule` is useful when the host owns the target element:\n\n```js\nimport { createDocsModule } from '@databis/docs-module'\nimport '@databis/docs-module/style.css'\n\nconst docs = createDocsModule(document.querySelector('#documentation'), {\n  baseURL: '/api/docs',\n  storageBaseURL: '/storage',\n  locale: 'en'\n})\n\nawait docs.selectCategory('getting-started')\n```\n\n## Framework integration\n\nThe element exposes `category` and `article` properties and emits native\nbubbling events. Framework adapters only have to synchronize those values.\n\n### Vue\n\n```vue\n<script setup>\nimport '@databis/docs-module'\nimport '@databis/docs-module/style.css'\n\nfunction navigate(event) {\n  router.push({\n    name: 'docs',\n    params: event.detail\n  })\n}\n</script>\n\n<template>\n  <databis-docs\n    base-url=\"/api/docs\"\n    storage-base-url=\"/storage\"\n    :category=\"$route.params.category\"\n    :article=\"$route.params.article\"\n    @docs:navigate=\"navigate\"\n  />\n</template>\n```\n\n### React\n\nReact passes string attributes to Custom Elements. Attach the namespaced event\nwith a ref because it is a native `CustomEvent`:\n\n```jsx\nimport { useEffect, useRef } from 'react'\nimport '@databis/docs-module'\nimport '@databis/docs-module/style.css'\n\nexport function Documentation({ category, article, navigate }) {\n  const docsRef = useRef(null)\n\n  useEffect(() => {\n    const element = docsRef.current\n    element.options = {\n      baseURL: '/api/docs',\n      getAuthToken: () => localStorage.getItem('token')\n    }\n    element.addEventListener('docs:navigate', navigate)\n\n    return () => element.removeEventListener('docs:navigate', navigate)\n  }, [navigate])\n\n  return <databis-docs ref={docsRef} category={category} article={article} />\n}\n```\n\nAngular and Svelte can consume the same element as a normal Web Component. In\nAngular, add `CUSTOM_ELEMENTS_SCHEMA` to the module or standalone component.\n\n## Events\n\n| Event | `detail` | When |\n| --- | --- | --- |\n| `docs:ready` | `{ categories, capabilities }` | Initial data and permissions are available |\n| `docs:navigate` | `{ category, article }` | The selected route changes |\n| `docs:category-create` | `{ category }` | A category is created |\n| `docs:create` | `{ article }` | An article is created |\n| `docs:change` | `{ article }` | An article is saved |\n| `docs:delete` | `{ article }` | An article is deleted |\n| `docs:category-delete` | `{ category }` | A category is deleted |\n| `docs:error` | `{ error }` | An API or rendering operation fails |\n\nAll events bubble.\n\n## Headless API usage\n\n```js\nimport { DocsApiClient } from '@databis/docs-module/api'\n\nconst api = new DocsApiClient({\n  baseURL: '/api/docs',\n  getAuthToken: () => sessionStorage.getItem('token')\n})\n\nconst { data: categories } = await api.getCategories({ locale: 'es' })\nconst { data: article } = await api.getArticle('guides', 'installation')\n```\n\nThe client covers categories, articles, capabilities, search, publishing,\nrevisions and image uploads. Failed responses throw `DocsApiError`, whose\n`status`, `data` and `response` properties contain the server response.\n\n## Rendering only\n\n```js\nimport { renderTiptapDocument } from '@databis/docs-module/renderer'\n\nconst { element, headings } = renderTiptapDocument(article.content, {\n  storageBaseURL: '/storage'\n})\n\ndocument.querySelector('main').replaceChildren(element)\n```\n\nThe renderer is independent of TipTap — importing it does not pull the editor\ninto the bundle. It creates DOM nodes and assigns text through `textContent`; it\ndoes not inject article HTML. Link protocols are restricted to HTTP(S), mail, phone,\nroot-relative and in-page URLs. The Laravel package must still sanitize the\nJSON when it is saved.\n\n## Theming\n\nPass one primary color through the package configuration. The complete palette\n(interactive states, soft backgrounds, borders, text tones and shadows) is\nderived from it. The default is `#3176A1`.\n\n```js\nconst docs = createDocsModule(document.querySelector('#documentation'), {\n  baseURL: '/api/docs',\n  primaryColor: '#6750A4'\n})\n```\n\nThe HTML attribute and JavaScript property are supported too:\n\n```html\n<databis-docs base-url=\"/api/docs\" primary-color=\"#6750A4\"></databis-docs>\n```\n\n```js\ndocument.querySelector('databis-docs').primaryColor = '#6750A4'\n```\n\nLayout variables can still be customized independently:\n\n```css\ndatabis-docs {\n  --docs-height: calc(100dvh - 4rem);\n  --docs-editor-height: 24rem;\n  --docs-navigation-width: 20rem;\n  --docs-content-width: 60rem;\n}\n```\n\n`--docs-height` defaults to `100dvh`. The category and article navigation, the\nopen article, and the table of contents scroll independently within that\nheight. `--docs-editor-height` caps the editing surface the same way, at\n`31.25rem` by default.\n\nThe component renders in the light DOM deliberately. This lets a design system\nstyle it without piercing a Shadow DOM boundary.\n\n## Editing\n\nThe element requests `GET /me/capabilities` during startup. When\n`can.create_category` is true, a **New category** button appears next to the\ncategory list, including when the installation has no content yet. The\nadministrator can create a root or nested category from there.\n\nOnce at least one category exists, `can.create_article` shows a **New article**\nbutton next to the article list. The form lets the administrator choose a\ncategory, enter the title and excerpt, and write the content in TipTap. New\narticles are created as drafts; if their resource returns `can.publish: true`,\nthey can then be published from the article header.\n\nThe empty-installation flow is therefore:\n\n1. Select **+** beside Categories and create the first category.\n2. Select **+** beside Articles and create a draft.\n3. Open the draft and select **Publish**.\n\nArticle listings request `?drafts=1` only when `can.view_drafts` is true. The\nbackend authorizes that query again, so sending the parameter manually never\nexposes drafts to a reader.\n\nWhen an article resource returns `can.update: true`, its edit button opens the\nsame TipTap form. It supports bold, italic, strikethrough, inline code,\nheadings, lists, quotes, code blocks, links, images, tables, dividers and\nundo/redo. The category, title, excerpt and document can all be changed.\n\n### Who sees the article\n\nWhen capabilities come back with `roles_enabled: true`, the element also fetches\n`GET /roles` and both forms — article and category — grow a **Visible to**\nmultiple select. Leave it empty and everyone sees the document; pick one or more\nroles and only readers holding one of them do. Anyone who may edit the\ndocumentation goes on seeing all of it regardless.\n\nNothing is rendered and no `roles` key is sent when the backend has the feature\noff, so an omitted field never wipes an existing assignment. See the backend\nREADME for how the list of roles is configured.\n\nThe writing surface scrolls inside its own frame, capped by\n`--docs-editor-height` (`31.25rem`, and never more than `70vh`), so the toolbar\nstays in place however long the document gets. Letting the pane scroll instead\npushed the toolbar off the top, and `overflow: hidden` on the editor frame\nmeant a sticky toolbar could not have fixed it.\n\n### Tables\n\nA second toolbar row appears under the formatting one whenever the caret is\ninside a table, and hides again when it leaves. It inserts and deletes columns\nand rows, merges or splits cells, toggles the header row and header column, and\ndeletes the table. Controls that would do nothing on the current selection —\ndeleting the only column, splitting a cell that was never merged — are disabled\nrather than silently ignored.\n\nColumns are resizable by dragging their border; the drag handle is drawn in the\naccent colour so it is visible. Column widths are stored as `colwidth` on the\ncell, which the backend sanitizer keeps.\n\nSaving sends `updated_at` for optimistic locking. A `409 Conflict` loads the\ncurrent server version and warns, instead of overwriting another editor's work.\n\nThe UI never trusts a role name supplied by the host. It renders every action\nfrom the API capabilities and per-resource `can` blocks, while Laravel\nauthorizes every write again.\n\n### Images\n\nPasting or dropping an image file uploads it to `POST /images` and inserts the\nresult. The toolbar button does the same through a file picker.\n\nThe **internal path is stored, not the absolute URL** — the same reasoning that\nkeeps `path` rather than a URL in the database, so content survives a change of\ndomain or storage disk. The editor resolves it against `storageBaseURL` for\ndisplay only.\n\n`storageBaseURL` **defaults to `/storage`**, which is where `php artisan\nstorage:link` publishes the public disk, so images load without any extra\nconfiguration. Set it only when storage lives somewhere else — another host, a\nCDN, a non-default disk. Leaving it empty no longer means \"use the bare path\":\nthat resolved against the *current page*, so an article opened at\n`/view-admin/sistema/users` requested\n`/view-admin/sistema/users/docs-images/...` and every image 404'd.\n\nThis is also why pasting has to be intercepted: left alone, a pasted screenshot\nbecomes a base64 data URI, the backend sanitizer rejects `data:` as a scheme,\nand the image silently disappears on save.\n\n#### Uploads that go nowhere\n\nThe upload happens the moment the file is dropped, which is long before\nanything is saved — and possibly before the article exists. So the element\nremembers what it uploaded while the editor was open, and on the way out\ndeletes whatever did not end up in the document that got stored:\n\n- **Cancel** — every upload of that session goes, since cancelling returns to\n  the article as it stands on the server.\n- **Save** — the ones that were inserted and then taken out again before\n  saving. The backend handles the rest: it compares the two versions of the\n  article and collects what the save dropped.\n\nNothing uploaded means nothing requested, so the ordinary edit costs no extra\nround trip. Deletions are fire-and-forget: the article is already saved or\nalready discarded by then, and `docs:prune-images` remains the backstop for\nanything that outlives its request — a closed tab, a lost connection.\n\n### Using the editor on its own\n\n```js\nimport { createArticleEditor } from '@databis/docs-module/editor'\n\nconst editor = createArticleEditor({\n  mount: document.querySelector('#editor'),\n  content: article.content,\n  storageBaseURL: '/storage',\n  uploadImage: (file) => api.uploadImage(file).then((response) => response.data),\n  onUpdate: () => { dirty = true }\n})\n\nawait api.updateArticle(article.id, {\n  title,\n  content: editor.getJSON(),\n  updated_at: article.updated_at\n})\n\neditor.destroy()\n```\n\n`editor.editor` is the underlying TipTap instance, for commands this package\ndoes not wrap. **Always call `destroy()`** before unmounting: a ProseMirror view\nthat outlives its host keeps the whole document alive.\n\n### Staying in step with the backend\n\nThe extension set is pinned to the node and mark lists the Laravel package\naccepts, exported from `@databis/docs-module/schema`:\n\n```js\nimport { allowedMarks, allowedNodes } from '@databis/docs-module/schema'\n```\n\nAdding a TipTap extension without adding its node to `content.allowed_nodes` in\nthe backend config produces the module's nastiest failure mode: writing looks\nfine, the save returns 200, and the content comes back with whole nodes missing\nbecause the sanitizer stripped what it did not recognise. No error is raised\nanywhere. `test/schema.test.ts` asserts both directions of that mapping.\n\n## Browser import without a bundler\n\nThe compiled ESM entry includes the TipTap runtime, so an import map only needs\nto resolve the package name:\n\n```html\n<script type=\"importmap\">\n{\n  \"imports\": {\n    \"@databis/docs-module\": \"/node_modules/@databis/docs-module/dist/index.js\"\n  }\n}\n</script>\n<link rel=\"stylesheet\" href=\"/node_modules/@databis/docs-module/dist/style.css\">\n<script type=\"module\">\n  import '@databis/docs-module'\n</script>\n```\n\n`examples/index.html` imports the local compiled entry and can be opened directly\nagainst a running backend after `npm run build`.\n\n### Read-only pages\n\nA page that only displays documentation should import the renderer rather than\nthe whole module. It has no dependencies at all, so TipTap never reaches the\nbrowser:\n\n```js\nimport { renderTiptapDocument } from '@databis/docs-module/renderer'\n```\n\n## Development\n\n```bash\nnpm install\nnpm run typecheck\nnpm run build\nnpm test\nnpm run check\n```\n\n## License\n","readmeFilename":"README.md"}