Components

WikiEditor

Editor delle pagine wiki basato su Tiptap 3, con barre degli strumenti, menu dei comandi /, blocchi del modulo, immagini, link risolti e commenti ancorati al testo.

Panoramica

WikiEditor scrive e mostra una pagina wiki in HTML (v-model). Oltre alla formattazione di base offre blocchi di codice evidenziati, tabelle, immagini, callout e sezioni richiudibili; il modulo lo estende con i propri comandi / e i propri blocchi, e ne riceve link e commenti come eventi.

Lo stesso componente serve in modifica e in lettura (readonly): in lettura la barra degli strumenti sparisce, i link si aprono con un clic e, se il modulo lo prevede, si può comunque commentare.

Anteprima

Titoli, elenco, callout, blocco di codice, tabella e sezione richiudibile. Scrivi / all'inizio di una riga per il menu dei comandi; con il cursore in una tabella, in un blocco di codice o in un callout compare la seconda riga della barra.

Quick start

<script setup lang="ts">
import { ref } from 'vue'
import { WikiEditor, type ComandoPowerbox, type CommentoAncorato } from '@pzeta/vue-wiki'

const contenuto = ref('')
const commenti = ref<CommentoAncorato[]>([])

const comandi: ComandoPowerbox[] = [
  {
    id: 'indice',
    etichetta: 'Indice',
    descrizione: 'Articoli annidati del padre',
    icona: 'pi pi-list-check',
    inserisci: ({ inserisciBlocco }) => inserisciBlocco('indice', { idarticolo: 12 }),
  },
]

const caricaAllegato = async (file: File) => {
  const allegato = await allegatiService.carica(file) // servizio del modulo
  return { src: allegato.url, id: String(allegato.idallegato) }
}

const cercaArticoli = async (ricerca: string) =>
  (await articoliService.cerca(ricerca)).map((a) => ({
    etichetta: a.titolo,
    href: `/knowledge/articolo/${a.idarticolo}`,
  }))
</script>

<template>
  <WikiEditor
    v-model="contenuto"
    :readonly="!puoModificare"
    :comandi="comandi"
    :carica-immagine="caricaAllegato"
    :risolvi-link="cercaArticoli"
    :commenti="commenti"
    :max-length="100000"
    @commenta="apriFormCommento"
    @apri-link="(href) => router.push(href)"
    @apri-commento="evidenziaCommento"
  >
    <template #blocco-personalizzato="{ tipo, dati }">
      <IndiceFigli v-if="tipo === 'indice'" :id-articolo="Number(dati.idarticolo)" />
    </template>
  </WikiEditor>
</template>

Props

PropTipoDefaultDescrizione
modelValuestring''Contenuto HTML (v-model). Un documento vuoto è '', non <p></p>
readonlybooleanfalseArea non modificabile e senza barra degli strumenti. Si può cambiare a runtime: non emette update:modelValue
comandiComandoPowerbox[][]Comandi / del modulo, dopo quelli dell'editor. Vedi Comandi e blocchi
caricaImmagine(file: File) => Promise<ImmagineCaricata>—Carica un'immagine scelta, incollata o trascinata e ne restituisce { src, id }. Senza, il comando Immagine non c'è e le immagini incollate sono scartate
risolviLink(ricerca: string) => Promise<LinkRisolto[]>—Risultati del dialogo del link mentre si scrive (per esempio gli articoli). Senza, il dialogo accetta solo indirizzi
commentiCommentoAncorato[]—Commenti del contenuto. Con un array, anche vuoto, la barra sulla selezione offre «Commenta» e compare il margine dei commenti. Vedi Commenti ancorati
maxLengthnumber0Oltre questo numero di caratteri compare un avviso e l'area diventa aria-invalid; la scrittura non viene bloccata. 0 = nessun limite
placeholderstring«Scrivi, oppure digita / per i comandi»Testo mostrato a documento vuoto, in modifica
ariaLabelstring«Contenuto»Nome accessibile dell'area di testo
emitDebouncenumber200Ritardo (ms) di update:modelValue dall'ultima modifica

Eventi

EventoPayloadQuando
update:modelValuestringIl contenuto cambia. Mai in sola lettura, mai per un valore arrivato da modelValue
commentaSelezioneCommento ({ testo, ancora, html })«Commenta» sulla selezione: l'ancora è già applicata al testo
apriLinkstring (href)Clic su un link in sola lettura, o Ctrl/⌘+clic in modifica
apriCommentostring (id)Clic su un indicatore nel margine dei commenti

Nel template gli eventi si scrivono in kebab-case: @apri-link, @apri-commento.

La navigazione è del modulo. L'editor non apre nessun link da sé: emette apriLink con l'indirizzo, già controllato (solo http, https, mailto, tel e relativi). Un link a un articolo si apre col router, uno esterno con window.open.

Slot

SlotScopeDescrizione
toolbar-extra{ editor }Pulsanti aggiuntivi in fondo alla barra degli strumenti
blocco-personalizzatoBloccoPersonalizzatoProps ({ tipo, dati, readonly, aggiorna, elimina })Disegna i blocchi inseriti dai comandi del modulo
<WikiEditor v-model="contenuto">
  <template #toolbar-extra="{ editor }">
    <button type="button" class="px-2 text-sm" @click="editor.commands.clearContent(true)">
      Svuota
    </button>
  </template>
</WikiEditor>

Metodi esposti

Con un template ref (ref="wiki"):

MetodoDescrizione
editorIstanza Tiptap (undefined prima del montaggio)
focus()Porta il focus nell'editor
inserisciBlocco(tipo, dati?)Inserisce un blocco personalizzato al cursore
rimuoviAncora(ancora)Toglie un'ancora di commento dal contenuto (commento annullato o eliminato)
mostraAncora(ancora)Porta in vista il testo di un'ancora; false se non è nel contenuto

Barra degli strumenti

Compare solo in modifica. Da sinistra:

GruppoComandi
Tipo di bloccoTesto, Titolo 1–6
TestoGrassetto, corsivo, sottolineato, barrato, codice in linea, link
BlocchiElenco puntato, elenco numerato, citazione, blocco di codice, callout (inserisce o toglie), sezione richiudibile, tabella, immagine (con caricaImmagine), separatore
CronologiaAnnulla, ripeti
Slottoolbar-extra

Sotto compare una seconda riga con i comandi del blocco in cui si trova il cursore:

BloccoComandi
TabellaRiga sopra, riga sotto, colonna a sinistra, colonna a destra, riga di intestazione, elimina riga, elimina colonna, elimina tabella
Blocco di codiceLinguaggio: rilevamento automatico, Bash, C#, CSS, Go, HTML / XML, Java, JavaScript, JSON, Markdown, PHP, Testo, Python, Rust, SQL, TypeScript, YAML
CalloutTipo (Informazione, Successo, Avviso, Pericolo), rimuovi il callout

Barra sulla selezione

Selezionando del testo compare sopra la selezione una barra con grassetto, corsivo, sottolineato, barrato, codice in linea, link e — se c'è la prop commenti — Commenta. In sola lettura resta solo «Commenta».

Non compare per una selezione vuota o di soli spazi, per un blocco selezionato per intero (immagine, blocco del modulo) e dentro un blocco di codice, dove né formattazione né ancore sono ammesse.

Digitando / all'inizio di una riga o dopo uno spazio si apre il menu, filtrato mentre si scrive (maiuscole e accenti non contano; ogni parola digitata deve comparire nell'etichetta, nella descrizione o nelle parole chiave). Frecce per scegliere, Invio per eseguire, Esc per chiudere. Non si apre in sola lettura né dentro un blocco di codice.

ComandoParole chiaveEffetto
Testo—Paragrafo
Titolo 1–6h1…h6, headingIntestazione del livello
Elenco puntato / numeratoul, ol, listaElenco
Citazionequote, appuntiCitazione
Blocco di codicecode, snippetCodice evidenziato
Tabellatable, grigliaTabella 3 × 3 con intestazione
Immagineimage, fotoScelta del file (solo con caricaImmagine)
Linkurl, collegamentoDialogo del link
Calloutnota, avviso, admonitionCallout informativo attorno al blocco corrente
Sezione richiudibiletoggle, accordion, dettagliSezione attorno al blocco corrente
Separatorehr, lineaLinea orizzontale

Seguono i comandi del modulo (comandi): vedi Comandi e blocchi.

  • Dialogo: dalla barra, dal menu / o da un comando del modulo (chiediLink()). Con del testo selezionato chiede solo l'indirizzo, senza selezione anche il testo; su un link esistente permette di modificarlo o rimuoverlo.
  • Ricerca: con risolviLink, ciò che si scrive nel campo e non sembra un indirizzo (non inizia con uno schema, /, #, . o www.) diventa una ricerca, 250 ms dopo l'ultima battuta; scegliere un risultato inserisce il link. Una ricerca più recente scarta le risposte di quelle prima, e i risultati con indirizzi non sicuri non compaiono.
  • Indirizzi: www.esempio.it diventa https://www.esempio.it; uno schema non ammesso mostra «Indirizzo non valido».
  • Scrittura: un indirizzo digitato o incollato diventa link da solo.
  • Clic: in modifica un clic posiziona il cursore e Ctrl/⌘+clic emette apriLink; in sola lettura il clic emette apriLink.

Immagini

Con caricaImmagine un'immagine si aggiunge dal pulsante o dal comando Immagine, incollandola o trascinandola nel testo (va dove la si rilascia). caricaImmagine riceve il File e restituisce { src, id }: l'immagine viene inserita come blocco con alt uguale al nome del file e data-id uguale all'id. Più file si caricano uno dopo l'altro.

Durante il caricamento sotto l'editor compare «Caricamento dell'immagine…»; se la promessa fallisce, o src non è un indirizzo ammesso (http, https, blob o relativo), compare un avviso (role="alert") che sparisce alla modifica successiva.

Qui caricaImmagine non carica nulla: restituisce un indirizzo blob: del file, che resta nel browser. risolviLink cerca in un elenco fisso di tre articoli.

Callout e sezioni richiudibili

  • Callout: avvolge i blocchi selezionati in un riquadro colorato, di tipo info (predefinito), success, warn o danger. Il tipo si cambia dalla seconda riga della barra.
  • Sezione richiudibile: avvolge i blocchi in una sezione con titolo. Nell'editor il titolo è un campo (Invio porta all'inizio del contenuto) e la freccia apre o chiude la sezione: in modifica lo stato viene salvato, in sola lettura no. Nel contenuto salvato è un <details> nativo, che si apre anche mostrato senza editor.

Scorciatoie da tastiera

Sono quelle di Tiptap (Mod = Ctrl, ⌘ su macOS):

TastiAzione
Mod+B / Mod+I / Mod+UGrassetto / corsivo / sottolineato
Mod+Shift+SBarrato
Mod+ECodice in linea
Mod+Alt+1 … Mod+Alt+6Titolo 1–6
Mod+Shift+8 / Mod+Shift+7Elenco puntato / numerato
Mod+Shift+BCitazione
Mod+Alt+CBlocco di codice
Shift+InvioA capo nello stesso paragrafo
Mod+Z / Mod+Shift+Z (o Mod+Y)Annulla / ripeti
/Menu dei comandi

Accessibilità

  • L'area di testo è role="textbox" con aria-multiline, aria-label (prop ariaLabel), aria-readonly in sola lettura e aria-invalid oltre maxLength.
  • Le barre sono role="toolbar" con un nome; i pulsanti hanno aria-label e, se si attivano, aria-pressed.
  • Il menu / è un listbox con aria-activedescendant sulla voce selezionata.
  • La freccia della sezione richiudibile ha aria-expanded; il titolo è un campo con etichetta.
  • Il dialogo del link porta il focus sul primo campo; gli indicatori nel margine hanno un nome («Apri il commento»).
  • Gli avvisi di caricamento sono role="status" e role="alert"; il contatore oltre il limite è annunciato (aria-live).

Note

  • v-model ed eco: il valore emesso e rimandato dal parent come modelValue è riconosciuto e non reimposta il contenuto (il cursore non salta). Un valore diverso arrivato da fuori sostituisce il contenuto e annulla una modifica non ancora emessa.
  • Larghezza: la barra va a capo e le tabelle larghe scorrono dentro il proprio contenitore: l'editor non allarga la pagina.
  • Tema scuro: tutte le classi hanno la variante dark:; il blocco di codice è scuro in entrambi i temi.