Documentation
¶
Overview ¶
Package richtext is a full Rich Text block editor plugin for GoFastr, built on ProseMirror and delivered as a genuinely third-party, isolated heavy-JS plugin.
Architecture (see ../docs/PLAN.md and ../docs/design/protocol-v1.md for the authoritative Phase-0 contract):
- The editor's JavaScript is a prebuilt bundle embedded via go:embed and served same-origin (CSP-clean). It runs inside an opaque-origin sandboxed iframe (sandbox="allow-scripts" WITHOUT allow-same-origin), so it cannot reach host cookies, localStorage, the CSRF token, or the host DOM.
- Host and editor communicate ONLY over a versioned postMessage capability bridge. Capabilities reuse GoFastr's existing resource:verb auth scopes (document:read, document:write, upload:images, theme:read).
- The canonical document is ProseMirror block-JSON (stored opaquely); a markdown export is emitted alongside for portability, form/entity round-tripping, and the no-JS SSR read view.
Identity/route constants (Name, Version, RoutePrefix, the *URL consts, SchemaVersion) live in plugin.go alongside the Plugin type.
Status: Phase 0 (the isolation spike). The public surface is defined by protocol-v1.md §10.
Index ¶
- Constants
- Variables
- func DefaultCapabilities() []string
- func Mount(cfg MountConfig) render.HTML
- func UIHostOption() uihost.Option
- type MountConfig
- type Option
- func WithCapabilities(caps ...string) Option
- func WithDemoPage() Option
- func WithDemoRoute(path string) Option
- func WithDevGrantAll() Option
- func WithSaveHandler(fn func(ctx context.Context, req SaveRequest) error) Option
- func WithTrustedMount() Option
- func WithUploadHandler(fn func(ctx context.Context, req UploadRequest) (UploadResult, error)) Option
- type Plugin
- type SaveRequest
- type UploadRequest
- type UploadResult
Constants ¶
const ( Name = "richtext" Version = "0.1.0-phase0" RoutePrefix = "/__gofastr/plugin/richtext" EditorHTMLURL = RoutePrefix + "/editor.html" EditorJSURL = RoutePrefix + "/editor.js" EditorCSSURL = RoutePrefix + "/editor.css" BrokerScriptURL = RoutePrefix + "/broker.js" SaveURL = RoutePrefix + "/save" UploadURL = RoutePrefix + "/upload" ReadURL = RoutePrefix + "/read" // no-JS SSR read view (?doc=<id>) DemoURL = "/" // self-contained themed demo page (only with WithDemoPage) // Trusted in-page mount (DECISIONS.md "secure by default, opt out"). These // routes exist ONLY when the host opts in via [WithTrustedMount]. InlineJSURL = RoutePrefix + "/editor-inline.js" // window.__gofastrRichText mount API ScopedCSSURL = RoutePrefix + "/editor-scoped.css" // stylesheet rescoped under .gofastr-richtext-trusted TrustedDemoURL = RoutePrefix + "/trusted" // frameless demo page SchemaVersion = "richtext-v1" )
Identity + route constants (protocol-v1.md §2 / §10). Both this plugin and host/broker.js hard-code these exactly — they ARE the contract.
Variables ¶
var ErrConflict = errors.New("richtext: save conflict")
ErrConflict is the sentinel a WithSaveHandler hook returns to signal that the save lost an optimistic-concurrency check — the stored document changed under the editor since it loaded. handleSave maps it to HTTP 409 (E_CONFLICT) rather than the generic 500 (E_SAVE), which is the one status the host broker relays back to the frame as a distinct saveResult so the editor can keep the doc dirty and warn the user instead of silently dropping their edits. Wrap it (fmt.Errorf("...: %w", richtext.ErrConflict)) to add context; handleSave uses errors.Is.
Functions ¶
func DefaultCapabilities ¶
func DefaultCapabilities() []string
DefaultCapabilities is the Phase-0 grant set advertised to the editor in init.capabilities when WithCapabilities is not used.
func Mount ¶
func Mount(cfg MountConfig) render.HTML
Mount renders the mount marker div plus the two hidden inputs the host broker syncs on docChanged (protocol-v1.md §6/§10). It wraps the platform pluginhost.MountMarker (generic data-fui-plugin* marker) and adds the richtext-specific data-fui-plugin-for attribute naming the JSON + markdown hidden fields. Drop it into a form. All interpolated values are HTML-escaped via render.Escape inside pluginhost.MountMarker.
func UIHostOption ¶
UIHostOption returns the uihost.Option that injects the host scripts into every UIHost-rendered page: the generic platform broker first, then this plugin's adapter (host/broker.js). Order matters — the adapter registers with the broker the former defines. Apps using a UIHost pass this to uihost.New; the self-contained demo page includes both <script>s itself.
Types ¶
type MountConfig ¶
type MountConfig struct {
DocID string // persistence key (default "demo")
JSONField string // hidden input name for canonical block-JSON (default "body_json")
MDField string // hidden input name for markdown export (default "body_md")
MinHeight string // initial iframe height before first resize (default "240px")
Doc string // optional initial doc JSON, server-rendered for reload round-trip
}
MountConfig configures Mount.
type Option ¶
type Option func(*Plugin)
Option configures a Plugin.
func WithCapabilities ¶
WithCapabilities overrides the grant set advertised to the editor in init.capabilities. Default: DefaultCapabilities.
func WithDemoPage ¶
func WithDemoPage() Option
WithDemoPage registers the self-contained themed demo page at DemoURL.
func WithDemoRoute ¶ added in v0.2.0
WithDemoRoute overrides where WithDemoPage mounts the demo (default DemoURL, "/"). A host that wants "/" for its own landing page — e.g. the example app's plugin gallery — moves the richtext demo aside with this.
func WithDevGrantAll ¶
func WithDevGrantAll() Option
WithDevGrantAll bypasses the auth.HasScope gate on save/upload so the Phase-0 demo runs without standing up auth. Default OFF (enforcing). Phase 1 removes this.
func WithSaveHandler ¶
func WithSaveHandler(fn func(ctx context.Context, req SaveRequest) error) Option
WithSaveHandler overrides the persistence hook. The default stores the canonical doc JSON + markdown in an in-memory map keyed by DocID.
func WithTrustedMount ¶
func WithTrustedMount() Option
WithTrustedMount OPTS OUT of the sandbox for this plugin: it serves the in-page editor bundle + scoped stylesheet and a frameless demo page at TrustedDemoURL. The editor then runs with FULL page access — no opaque origin, no capability boundary the browser enforces. Per docs/DECISIONS.md ("secure by default, opt out") this is never a default and never plugin-selectable: only the app owner compiles this option in, vouching for the plugin bundle and its dependency tree.
func WithUploadHandler ¶
func WithUploadHandler(fn func(ctx context.Context, req UploadRequest) (UploadResult, error)) Option
WithUploadHandler overrides the upload hook. The default returns a data: URL echoing the bytes, enough to prove the round trip.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the Phase-0 Rich Text plugin. It implements framework.Plugin.
func New ¶
New constructs a Plugin with the given options. Unset options fall back to Phase-0 defaults so the demo and tests work with zero configuration.
func (*Plugin) Capabilities ¶
Capabilities returns the grant set this plugin advertises to the editor.
func (*Plugin) Init ¶
Init implements framework.Plugin. It registers every asset and RPC route from protocol-v1.md §10 on the app's router.
type SaveRequest ¶
type SaveRequest struct {
DocID string // persistence key
DocJSON string // canonical ProseMirror doc JSON (opaque blob)
Markdown string // lossy markdown export
SchemaVersion string // interchange version ("richtext-v1")
}
SaveRequest is the persistence payload handed to the save handler.
type UploadRequest ¶
UploadRequest is the upload payload handed to the upload handler. Bytes is the raw image body; Name/Type come from the X-Upload-Name / X-Upload-Type headers the host broker sends with the raw-body POST.
type UploadResult ¶
type UploadResult struct {
URL string
}
UploadResult is the upload handler's response. URL is what the editor embeds into the document.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package ssr renders canonical Rich Text block-JSON (a ProseMirror doc, schema version "richtext-v1") into design-token HTML for the no-JS first paint.
|
Package ssr renders canonical Rich Text block-JSON (a ProseMirror doc, schema version "richtext-v1") into design-token HTML for the no-JS first paint. |