Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

Bundled fonts (src/fonts/): the Noto Sans font families bundled with this
package are NOT covered by the MIT license above. They are licensed under the
SIL Open Font License, Version 1.1 (OFL-1.1) — a permissive, non-copyleft
license compatible with MIT. See src/fonts/OFL.txt for the full license text
and src/fonts/NOTICE for attribution. Fonts are content/assets, not linked
code; bundling and redistribution under OFL-1.1 is expressly permitted.
85 changes: 78 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# cwl-editor
# Inkspan

> **Inkspan** ([inkspan.io](https://inkspan.io)) — the product. Repository slug:
> `cwl-editor`. npm package: `@contextualwisdomlab/cwl-editor`.

**Commercial-grade Markdown + HTML WYSIWYG editor** built on
[TipTap v2](https://tiptap.dev/) / [ProseMirror](https://prosemirror.net/)
(both MIT), with **inline base64 images** and a **standalone base64 converter**.
(both MIT), with **inline base64 images**, a **standalone base64 converter**, and
**bundled offline multilingual fonts**.

- 📝 **Two modes** — a Markdown editor and an HTML WYSIWYG editor sharing one
toolbar, keyboard shortcuts, and paste handling.
Expand All @@ -11,15 +15,20 @@
`<img src="data:…">`). Nothing is uploaded to a server, so the content is fully
self-contained and a downstream **LLM can read the image bytes straight from
the text**. Configurable size guard + downscaling.
- 🌏 **Bundled offline fonts** — self-contained [Noto Sans](https://fonts.google.com/noto)
web fonts covering **Korean, English, Japanese, Chinese (Simplified +
Traditional) and Vietnamese**. No CDN, no Google Fonts URL — every glyph
renders with **zero network fetch**, so it works in **air-gapped / 폐쇄망**
environments. All fonts are **SIL OFL 1.1** (no copyright/licensing issues).
- 🔁 **Round-trip safe** — the embedded data URI survives Markdown ⇄ HTML
conversion in both directions.
- 🧩 **Standalone base64 converter** — `File`/`Blob`/`ArrayBuffer` → data URI and
back, with MIME sniffing and a size guard. **Zero dependencies, no React** —
reusable on its own (e.g. by the *naruon* / DOM-understanding pipeline).
- 📦 **Standalone _and_ embeddable** — own Vite build + demo, publishable as an
npm package, or vendorable as a git submodule.
- ⚖️ **MIT** — permissive licenses only (TipTap MIT, ProseMirror MIT). No
GPL/AGPL.
- ⚖️ **MIT** code + **OFL-1.1** fonts — permissive licenses only (TipTap MIT,
ProseMirror MIT, Noto Sans OFL-1.1). No GPL/AGPL.

All configuration comes from **props / KV**, never from `process.env` or OS
environment lookups at runtime.
Expand All @@ -40,6 +49,7 @@ pnpm add react react-dom
import { useState } from 'react';
import { CwlEditor } from '@contextualwisdomlab/cwl-editor';
import '@contextualwisdomlab/cwl-editor/styles.css';
import '@contextualwisdomlab/cwl-editor/fonts.css'; // bundled offline fonts (see below)

export function Example() {
const [md, setMd] = useState('# Hello\n\nDrop an image below 👇');
Expand Down Expand Up @@ -74,6 +84,58 @@ Both modes embed images as inline base64.
`ImageConfig`: `{ maxSizeBytes?: number; maxDimension?: number; quality?: number }`
— defaults `10 MB`, `1600 px`, `0.85`. Set `maxDimension: 0` to disable downscaling.

## Bundled offline fonts (Korean / English / Japanese / Chinese / Vietnamese)

Inkspan ships **self-contained web fonts** so the editor renders all five
scripts **without any network fetch** — ideal for **air-gapped / 폐쇄망**
deployments. The fonts are the [Noto Sans](https://fonts.google.com/noto)
family under the **SIL Open Font License 1.1** (no copyright/licensing issues,
compatible with the MIT code):

| Family | Scripts | Weights |
| -------------- | ----------------------------------------- | -------- |
| Noto Sans | Latin, Latin-ext, **Vietnamese**, Cyrillic | 400, 700 |
| Noto Sans KR | **Korean** (Hangul) | 400 |
| Noto Sans JP | **Japanese** (Kana + Kanji) | 400 |
| Noto Sans SC | **Chinese, Simplified** | 400 |
| Noto Sans TC | **Chinese, Traditional** | 400 |

The `@font-face` rules point at **woff2 files bundled inside the package**
(`src/fonts/files/`), split by `unicode-range` — never a CDN or Google Fonts
URL. Because they are `unicode-range`-subset, a browser only downloads the
subset files whose glyphs actually appear, and everything resolves from local
bundled bytes.

```tsx
// Full multilingual stack (all five scripts):
import '@contextualwisdomlab/cwl-editor/fonts.css';

// …or Latin/Vietnamese only — opt out of the ~9 MB of CJK to keep it tiny:
import '@contextualwisdomlab/cwl-editor/fonts-latin.css';
```

The default editor font stack
(`--cwl-font: 'Noto Sans', 'Noto Sans KR', 'Noto Sans JP', 'Noto Sans SC',
'Noto Sans TC', …`) is set in `styles.css`, so once you import a fonts CSS the
scripts render automatically. Override `--cwl-font` to re-theme.

**Size tradeoff.** Full CJK coverage is inherently large: the complete bundle is
**≈ 9.7 MB across ~470 woff2 subset files** (Latin 400+700 ≈ 0.8 MB; each CJK
family at weight 400 ≈ 1.8–2.7 MB). To control this:

- **Tree-shake CJK** — import `fonts-latin.css` instead of `fonts.css` if you
only need Latin/Vietnamese (a few hundred KB).
- **Runtime cost is small** — `unicode-range` subsetting means only the subset
files for glyphs on the page are ever fetched from the bundle.
- **CJK bold is synthesized** — CJK families ship weight 400 only; browsers
render bold headings with faux-bold. Regenerate with 700 via
`scripts/fetch-fonts.mjs` if you need true CJK bold (roughly doubles CJK size).
- **Regenerate** the bundle any time with `node scripts/fetch-fonts.mjs` (the
only step that touches the network; rendering never does).

License: [`src/fonts/OFL.txt`](src/fonts/OFL.txt) +
[`src/fonts/NOTICE`](src/fonts/NOTICE).

## Standalone base64 converter (no React)

```ts
Expand Down Expand Up @@ -163,15 +225,24 @@ src/
markdown/ marked + turndown serializers (base64-image round-trip safe)
extensions/ Base64Image TipTap extension + shared extension kit
components/ CwlEditor React component + Toolbar
fonts/ Bundled Noto Sans woff2 (offline) + fonts.css / fonts-latin.css
+ OFL.txt + NOTICE
styles.css Self-contained, theme-aware styling
demo/ Standalone Vite demo app
demo/ Standalone Vite demo app (renders all five scripts offline)
scripts/ copy-styles + fetch-fonts (font bundle generator)
docs/papers/ CommonMark spec + citations (see docs/papers/README.md)
```

## Licenses

MIT. Dependency licenses are all permissive: TipTap (MIT), ProseMirror (MIT),
`marked` (MIT), `turndown` (MIT), `turndown-plugin-gfm` (MIT). No GPL/AGPL.
**Code: MIT.** Dependency licenses are all permissive: TipTap (MIT), ProseMirror
(MIT), `marked` (MIT), `turndown` (MIT), `turndown-plugin-gfm` (MIT). No
GPL/AGPL.

**Fonts: SIL OFL 1.1.** The bundled Noto Sans families are under the SIL Open
Font License 1.1 — permissive and compatible with MIT (fonts are content, not
linked code). Full text: [`src/fonts/OFL.txt`](src/fonts/OFL.txt); attribution:
[`src/fonts/NOTICE`](src/fonts/NOTICE).

See [`docs/papers/README.md`](docs/papers/README.md) for the CommonMark
specification and citations.
Expand Down
26 changes: 20 additions & 6 deletions demo/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { useMemo, useState } from 'react';
import { CwlEditor, type EditorMode } from '../src/index.js';

const SAMPLE_MD = `# cwl-editor
const SAMPLE_MD = `# Inkspan

A **commercial-grade** Markdown + HTML editor built on TipTap v2.
A **commercial-grade** Markdown + HTML editor built on TipTap v2, with
bundled offline fonts for five scripts.

## Multilingual (bundled Noto Sans, no network)

- **Korean** — 한국어: 폐쇄망에서도 완벽하게 렌더링됩니다.
- **English** — The quick brown fox jumps over the lazy dog.
- **Japanese** — 日本語:オフラインでも綺麗に表示されます。
- **Chinese (Simplified)** — 简体中文:完全离线渲染。
- **Chinese (Traditional)** — 繁體中文:完全離線渲染。
- **Vietnamese** — Tiếng Việt: hiển thị đầy đủ dấu, không cần mạng.

## Features

Expand All @@ -30,7 +40,7 @@ export function App() {
// Reset content to a sensible sample when switching modes for the demo.
const onModeChange = (next: EditorMode) => {
setMode(next);
setValue(next === 'markdown' ? SAMPLE_MD : '<h1>cwl-editor</h1><p>HTML mode. Try <strong>bold</strong> and drop an image.</p>');
setValue(next === 'markdown' ? SAMPLE_MD : '<h1>Inkspan</h1><p>HTML mode. Try <strong>bold</strong>, 한국어, 日本語, 中文, Tiếng Việt, and drop an image.</p>');
};

const byteInfo = useMemo(() => {
Expand All @@ -41,8 +51,11 @@ export function App() {
return (
<div className="demo">
<header className="demo__header">
<h1>cwl-editor</h1>
<p>Markdown + HTML WYSIWYG with inline base64 images.</p>
<h1>Inkspan</h1>
<p>
Markdown + HTML WYSIWYG with inline base64 images and bundled offline
fonts (한국어 · English · 日本語 · 中文 · Tiếng Việt).
</p>
<div className="demo__modes">
<button
className={mode === 'markdown' ? 'active' : ''}
Expand Down Expand Up @@ -78,7 +91,8 @@ export function App() {
</main>

<footer className="demo__footer">
MIT · TipTap/ProseMirror · base64-inline images ·{' '}
Inkspan · MIT · TipTap/ProseMirror · base64-inline images · Noto Sans
(OFL-1.1) ·{' '}
<a href="https://github.com/ContextualWisdomLab/cwl-editor">source</a>
</footer>
</div>
Expand Down
2 changes: 1 addition & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>cwl-editor — demo</title>
<title>Inkspan — demo</title>
</head>
<body>
<div id="root"></div>
Expand Down
4 changes: 4 additions & 0 deletions demo/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App.js';
// Bundled, self-contained multilingual fonts (KR / EN+VI / JP / SC / TC).
// Imported from source so the demo build inlines the woff2 files locally and
// renders all five scripts with zero network fetch (air-gapped / 폐쇄망).
import '../src/fonts/fonts.css';
import '../src/styles.css';
import './demo.css';

Expand Down
26 changes: 19 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "@contextualwisdomlab/cwl-editor",
"version": "0.1.0",
"description": "Commercial-grade Markdown + HTML WYSIWYG editor module (TipTap/ProseMirror, MIT) with base64-inline images and a standalone base64 converter.",
"description": "Inkspan — commercial-grade Markdown + HTML WYSIWYG editor module (TipTap/ProseMirror, MIT) with base64-inline images, a standalone base64 converter, and bundled offline multilingual Noto Sans fonts (KR/EN/JP/SC/TC/VI, OFL-1.1).",
"license": "MIT",
"author": "ContextualWisdomLab",
"type": "module",
"packageManager": "pnpm@10.34.4",
"keywords": [
"inkspan",
"editor",
"wysiwyg",
"markdown",
Expand All @@ -15,17 +16,24 @@
"react",
"base64",
"rich-text",
"llm"
"llm",
"fonts",
"noto-sans",
"offline",
"i18n",
"cjk"
],
"repository": {
"type": "git",
"url": "git+https://github.com/ContextualWisdomLab/cwl-editor.git"
"url": "git+https://github.com/ContextualWisdomLab/inkspan.git"
},
"homepage": "https://github.com/ContextualWisdomLab/cwl-editor#readme",
"homepage": "https://github.com/ContextualWisdomLab/inkspan#readme",
"bugs": {
"url": "https://github.com/ContextualWisdomLab/cwl-editor/issues"
"url": "https://github.com/ContextualWisdomLab/inkspan/issues"
},
"sideEffects": false,
"sideEffects": [
"**/*.css"
],
"main": "./dist/cwl-editor.cjs",
"module": "./dist/cwl-editor.js",
"types": "./dist/index.d.ts",
Expand All @@ -40,7 +48,10 @@
"import": "./dist/cwl-converter.js",
"require": "./dist/cwl-converter.cjs"
},
"./styles.css": "./dist/cwl-editor.css"
"./styles.css": "./dist/cwl-editor.css",
"./fonts.css": "./src/fonts/fonts.css",
"./fonts-latin.css": "./src/fonts/fonts-latin.css",
"./fonts/*": "./src/fonts/*"
},
"files": [
"dist",
Expand All @@ -52,6 +63,7 @@
"dev": "vite",
"build": "tsc --noEmit && vite build && vite build --config vite.converter.config.ts && node ./scripts/copy-styles.mjs",
"build:demo": "vite build --config vite.demo.config.ts",
"fonts": "node ./scripts/fetch-fonts.mjs",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"test": "vitest run",
Expand Down
Loading
Loading