Wing personalizzate

Una wing personalizzata è più di un pulsante della barra. È un'estensione dichiarativa che tiene insieme struttura del documento salvato, comandi, conversione HTML e Markdown, regole di importazione e comportamento della vista. Il registry la convalida prima che esista un editor, impedendo a strutture non valide di entrare nei documenti.

Parti dalla factory più limitata

La maggior parte della formattazione non richiede una dichiarazione completa. Usa simpleMark() per un mark inline senza valore, valueMark() per un mark con un insieme di valori limitato, boxObject() per un blocco senza figli e listFamily() per un elenco.

ts
import { createNabiWith, simpleMark, wings } from 'nabi-note'

const exStrong = simpleMark({
  w: 'exStrong',
  toHtml: (_node, children, ctx) => ctx.element('strong', children()),
})

const { nabi, registry } = createNabiWith(wings().allBasic().use(exStrong))

Crea diversi tipi di wing

Ogni esempio seguente ha una forma archiviata diversa. Registra prima una wing e ispeziona getJson() e getHtml(). Aggiungi comandi e pulsanti solo dopo che la struttura funziona.

1. Mark inline senza valore: enfasi

Usa simpleMark() quando una funzione avvolge soltanto testo. Questo archivia exStrong e lo renderizza come <strong>.

ts
import { simpleMark } from 'nabi-note'

export const exStrong = simpleMark({
  w: 'exStrong',
  clearable: true,
  toHtml: (_node, children, ctx) => ctx.element('strong', children()),
  styles: '.nabi-content strong { font-weight: 700; }',
})

Con clearable: true, Cancella formattazione rimuove anche questo mark. Prima di aggiungere un pulsante, applicalo con nabi.applyCommand() o un altro comando personalizzato. Lo stesso selettore .nabi-content strong applica lo stile a editor e contenuto pubblicato.

2. Mark inline con valore: tonalità di stato

Usa valueMark() per un colore, una dimensione o uno stato scelto da un insieme consentito. Il valore viene archiviato in a.v; i valori fuori elenco vengono rimossi durante repair().

ts
import { valueMark } from 'nabi-note'

export const exTone = valueMark({
  w: 'exTone',
  key: 'v',
  values: ['quiet', 'loud'],
  clearable: true,
  toHtml: (node, children, ctx) =>
    ctx.element('span', children(), { 'data-ex-tone': String(node.a?.v ?? '') }),
  styles: `
    .nabi-content [data-ex-tone="quiet"] { opacity: .65; }
    .nabi-content [data-ex-tone="loud"] { color: var(--nabi-accent); font-weight: 700; }
  `,
})

La sua forma salvata è { "w": "exTone", "a": { "v": "loud" }, "ch": ["Important"] }. Il CSS punta al valore archiviato, quindi modifica anche il contenuto pubblicato. Non rimuovere con leggerezza valori da un elenco esistente: i documenti salvati in precedenza potrebbero perderli alla lettura.

3. Blocco senza figli: separatore

Usa boxObject() per un oggetto autonomo senza figli, come immagine, video o separatore.

ts
import { boxObject } from 'nabi-note'

export const exDivider = boxObject({
  w: 'exDivider',
  toHtml: (_node, _children, ctx) => ctx.element('hr', ''),
  styles: '.nabi-content hr { border-color: var(--nabi-line); }',
})

Per un oggetto con valori come URL o larghezza, dichiara la convalida in attrs e inserisci i valori richiesti in requires. Rifiuta un valore non verificabile con null invece di sostituire silenziosamente un valore predefinito.

4. Blocco con più paragrafi: richiamo

Per un blocco che contiene contenuto del documento, dichiara un container. holds: 'blocks' consente figli di tipo paragrafo, elenco e blocco-oggetto.

ts
import type { Wing } from 'nabi-note'

export const exCallout: Wing = {
  w: 'exCallout',
  place: 'container',
  holds: 'blocks',
  toHtml: (_node, children, ctx) =>
    ctx.element('aside', children(), { class: 'ex-callout' }),
  styles: `
    .nabi-content .ex-callout {
      border-inline-start: 4px solid var(--nabi-accent);
      background: var(--nabi-soft);
      padding: 1rem;
    }
  `,
}

Questa dichiarazione da sola non crea un modo per avvolgere i paragrafi selezionati. Aggiungi un comando puro in commands e un button che lo invochi prima di esporre la funzione nell'interfaccia dell'editor.

5. Coppia corrispondente di elenco ed elemento

Usa listFamily() quando elenco ed elemento devono comparire sempre insieme.

ts
import { listFamily } from 'nabi-note'

export const exList = listFamily({
  w: 'exList',
  item: 'exListItem',
  toHtml: (_node, children, ctx) => ctx.element('ul', children(), { class: 'ex-list' }),
  itemHtml: (_node, children, ctx) => ctx.element('li', children()),
  styles: '.nabi-content .ex-list { border-inline-start: 2px solid var(--nabi-line); }',
})

listFamily() ripara un blocco dentro l'elenco avvolgendolo in un elemento. Aggiungi itemDecl e repairItem per un valore a livello di elemento, come lo stato selezionato.

Registra in un'unica selezione ordinata

Usa sul server le stesse dichiarazioni nello stesso ordine del browser.

ts
const selected = wings()
  .allBasic()
  .use(exStrong)
  .use(exTone)
  .use(exDivider)
  .use(exCallout)
  .use(exList)

const { nabi, registry } = createNabiWith(selected, { locale: 'it' })

Definisci nomi e struttura del documento

I nomi che entrano in un documento devono corrispondere a ex[A-Z0-9].... Un nome come exCallout impedisce che una futura wing ufficiale cambi il significato del contenuto salvato.

place determina la forma archiviata: mark avvolge contenuto inline, void è un blocco senza figli, container contiene figli, attr cambia attributi del paragrafo e tool non crea nodi documento. Un container richiede holds: 'blocks' | 'inline' e toHtml().

ts
const exNote = {
  w: 'exNote',
  place: 'container',
  holds: 'blocks',
  toHtml: (_node, children, ctx) => ctx.element('aside', children()),
} as const

attrs, boolAttrs, allows, requiresAnyOf e parts dichiarano vincoli strutturali. Una dichiarazione parts richiede anche partHtml per ogni parte. Usa attrKey e attrValues per vincolare una wing che seleziona valori.

Tutte le opzioni di dichiarazione

Dichiara soltanto ciò che serve alla wing. Una factory fornisce già alcuni campi.

AreaOpzioniScopo
Basew, place, basic, stylesNome, tipo strutturale, appartenenza al catalogo base, CSS predefinito
Strutturaholds, singleParagraph, attrs, boolAttrsTipo di figlio, comportamento di Invio, attributi consentiti, attributi booleani
Strutturaparts, allows, noAlign, requiresAnyOfParti interne, figli consentiti, esclusione dell'allineamento, dipendenza della wing
ValoriattrKey, attrValues, currentValueChiave e lista del valore archiviato, rilevamento del valore corrente
Comandi e inputcommands, onKey, escapeKeys, doubleKeys, inputRulesComandi, gestione tasti, comportamento Escape/doppio tasto, regole di autoformattazione
Comportamento della superficieattachComportamento DOM e pulizia di una superficie
ConversionetoHtml, partHtml, toMd, partMdOutput HTML e Markdown
Importazione e riparazioneclaim, ioFilter, repair, partRepairImportazione HTML, gestione file, convalida e riparazione JSON
UIbutton, buttons, contextDichiarazioni dell'interfaccia della barra e del contesto
Cancella formattazioneclearableSe Cancella formattazione la rimuove

w e place sono sempre obbligatori. Le wing mark, void e container che producono nodi richiedono anche toHtml(). Un container necessita di holds; ogni parte dichiarata richiede il proprio partHtml corrispondente.

Mantieni insieme HTML, Markdown e JSON

toHtml() renderizza in HTML un nodo salvato, mentre toMd() esporta Markdown. Senza un builder Markdown, l'HTML generato viene conservato per non perdere informazioni. Durante l'importazione usa claim() per riconoscere solo il tuo elemento HTML e gli attributi convalidati.

repair() viene eseguito al caricamento del JSON e di nuovo dopo i comandi. Restituisci un nodo corretto per un attributo non valido, oppure null per un nodo che non può essere mantenuto. Costruisci l'HTML con ctx.element(), ctx.escape() e ctx.url(); non concatenare mai tag, attributi o URL aggirando questi controlli.

Separa i comandi dal comportamento della vista

Un comando è una funzione pura di documento e selezione che restituisce il documento successivo e una selezione al suo interno. Non legge né modifica mai il DOM e restituisce null quando non può effettuare una modifica valida. Dai ai comandi nomi lower camel case che iniziano con un verbo, come insertNote.

Inserisci in attach(host) il comportamento esclusivo del DOM, come la selezione trascinata delle tabelle. Registra subito la pulizia di ogni listener o attributo modificato con host.onDispose(), così anche una configurazione fallita viene ripulita. Non modificare il DOM del testo in composizione né la mappatura della selezione della superficie.

Dichiara i controlli della barra e del contesto con button, buttons e context; duplicarne le regole dei comandi nell'interfaccia dell'applicazione può far divergere UI e modello del documento.

Stili CSS

Inserisci in styles il CSS di base richiesto da una wing. Gli stili delle wing integrate sono già inclusi in nabi-note/nabi.css. Un browser che assembla gli stili del registry selezionato può usare collectSheets() e injectSheets(); SSR dovrebbe invece collegare il file CSS.

Usa le stesse classi e attributi data per la modifica e il contenuto pubblicato, ma non modificare struttura, display o white-space di [data-key] in modifica. Il CSS deve cambiare soltanto l'aspetto, non la mappatura del cursore.

ts
const exCallout = {
  w: 'exCallout',
  place: 'container',
  holds: 'blocks',
  toHtml: (_node, children, ctx) =>
    ctx.element('aside', children(), { class: 'ex-callout' }),
  styles: `
    .nabi-content .ex-callout {
      padding: 1rem;
      border-inline-start: 4px solid var(--nabi-accent);
      background: var(--nabi-soft);
      border-radius: var(--nabi-radius);
    }
  `,
} as const

Indirizza solo classi o attributi data creati da toHtml(). Mantieni più ristrette le modifiche specifiche del servizio, ad esempio .article-body .ex-callout.

Verifica l'intero contratto

Verifica che un documento JSON salvato venga ricaricato con la stessa struttura e lo stesso HTML. Verifica che il registry rifiuti nomi non validi, comandi duplicati, builder mancanti e dipendenze insoddisfatte. Copri l'importazione HTML non valida e l'input di repair(), la gestione della selezione dei comandi, l'output SSR e una vista pubblicata con stile.

Per i tipi completi e gli argomenti delle factory, controlla le dichiarazioni installate e il riferimento API in inglese.