Guide

Comandi e blocchi del modulo

Come un modulo aggiunge i propri comandi / a WikiEditor e disegna i propri blocchi (indice, viste incorporate) con lo slot blocco-personalizzato.

Il modello

Il modulo non tocca lo schema dell'editor. Ha due strumenti:

  1. Comandi / (prop comandi): voci del menu che, scelte, eseguono una funzione del modulo.
  2. Blocchi personalizzati: un tipo di blocco generico che l'editor conserva (tipo + dati) e che il modulo disegna con lo slot blocco-personalizzato.

Un comando come /Kanban Elementi li usa insieme: inserisce un blocco kanban con i suoi dati, e lo slot lo mostra come una vista Kanban vera.

ComandoPowerbox

interface ComandoPowerbox {
  id: string
  etichetta: string
  descrizione?: string   // seconda riga della voce
  icona?: string         // classe PrimeIcons, es. 'pi pi-objects-column' (default 'pi pi-bolt')
  parole?: string[]      // altre parole che trovano il comando
  inserisci: (contesto: ContestoComando) => void | Promise<void>
}

I comandi del modulo seguono quelli dell'editor nel menu. Il filtro cerca ogni parola digitata in etichetta, descrizione e parole, senza badare a maiuscole e accenti: /bach trova un comando con parole: ['bacheca'].

Quando inserisci viene chiamato, il / e la ricerca sono già stati tolti dal testo e il cursore è dove stavano. inserisci può essere asincrono (per esempio per aprire un dialogo di scelta): un errore viene scritto in console con l'id del comando e non interrompe l'editor.

ContestoComando

inserisci riceve gli strumenti per modificare il contenuto senza conoscere Tiptap:

StrumentoDescrizione
inserisciBlocco(tipo, dati?)Inserisce un blocco personalizzato al cursore
inserisciLink({ etichetta, href })Inserisce un link; un indirizzo non sicuro viene ignorato, www.… diventa https://www.…
chiediLink()Apre il dialogo del link, con la ricerca di risolviLink
editorIstanza Tiptap (Editor di @tiptap/core), per ciò che gli strumenti non coprono

Esempio: i comandi del modulo Knowledge

import type { ComandoPowerbox } from '@pzeta/vue-wiki'

export function comandiKnowledge(idarticolo: number): ComandoPowerbox[] {
  const vista = (id: string, etichetta: string, icona: string, parole: string[] = []) => ({
    id,
    etichetta,
    icona,
    parole,
    inserisci: ({ inserisciBlocco }) => inserisciBlocco(id, { idarticolo }),
  }) satisfies ComandoPowerbox

  return [
    vista('indice', 'Indice', 'pi pi-list-check', ['figli']),
    vista('kanban', 'Kanban Elementi', 'pi pi-objects-column', ['bacheca']),
    vista('schede', 'Schede Elementi', 'pi pi-th-large'),
    vista('elenco', 'Elenco Elementi', 'pi pi-list'),
    vista('calendario', 'Calendario Elementi', 'pi pi-calendar'),
    {
      id: 'articolo',
      etichetta: 'Articolo',
      descrizione: 'Link a un altro articolo',
      icona: 'pi pi-link',
      inserisci: ({ chiediLink }) => chiediLink(),
    },
  ]
}

/Articolo riusa il dialogo del link: con risolviLink che cerca gli articoli, l'utente scrive un pezzo del titolo e sceglie dai risultati.

/Sezione Richiudibile e i callout non vanno aggiunti: sono comandi dell'editor. Per /Appunti basta una citazione o un callout, salvo che il modulo voglia un blocco suo.

Blocchi personalizzati

Un blocco personalizzato è un atomo: si seleziona, si sposta trascinandolo e si cancella come un'unità, ma il suo contenuto non si modifica con il cursore. Nel contenuto salvato è:

<div data-blocco="kanban" data-dati="{&quot;idarticolo&quot;:12}" class="my-3"></div>

L'editor conserva tipo e dati (un oggetto JSON; dati illeggibili diventano {}) senza interpretarli.

Lo slot blocco-personalizzato

Lo slot riceve BloccoPersonalizzatoProps:

PropTipoDescrizione
tipostringTipo del blocco (data-blocco)
datiRecord<string, unknown>Dati del blocco (data-dati)
readonlybooleanLo stato di sola lettura dell'editor
aggiorna(dati) => voidSostituisce i dati del blocco (per esempio dopo una scelta dell'utente)
elimina() => voidToglie il blocco dal contenuto

In sola lettura aggiorna ed elimina non fanno nulla: il contenuto non cambia.

<WikiEditor v-model="contenuto" :comandi="comandi">
  <template #blocco-personalizzato="{ tipo, dati, readonly, aggiorna }">
    <IndiceFigli v-if="tipo === 'indice'" :id-articolo="Number(dati.idarticolo)" />
    <VistaKanban
      v-else-if="tipo === 'kanban'"
      :id-articolo="Number(dati.idarticolo)"
      :filtri="dati.filtri"
      :readonly="readonly"
      @update:filtri="(filtri) => aggiorna({ ...dati, filtri })"
    />
  </template>
</WikiEditor>

Lo slot viene reso nel contesto dell'applicazione: router, provide/inject e componenti registrati globalmente funzionano come altrove. Un blocco selezionato ha un bordo evidenziato.

Senza slot, o per un tipo che lo slot non gestisce, il blocco resta nel contenuto e l'editor mostra un segnaposto («Blocco «kanban»»): il contenuto non si perde.

Prova

Due comandi del modulo, /Indice e /Kanban Elementi (trovato anche da /bacheca), e uno slot che disegna i loro blocchi con dati fissi. Il contenuto parte con un indice già inserito.

Inserire un blocco da codice

Fuori dal menu /, il metodo esposto inserisciBlocco fa lo stesso:

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

const wiki = ref<InstanceType<typeof WikiEditor> | null>(null)
const aggiungiIndice = () => wiki.value?.inserisciBlocco('indice', { idarticolo: 12 })
</script>

<template>
  <WikiEditor ref="wiki" v-model="contenuto" />
</template>

Mostrare i blocchi fuori dall'editor

Un <div data-blocco> mostrato con v-html resta vuoto: chi lo disegna è lo slot. Per una pagina in lettura che contiene blocchi del modulo conviene quindi <WikiEditor readonly> con lo stesso slot, invece di v-html. Vedi Contenuto HTML.