@meistrari/minuta-nuxt 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import type { Minuta, MinutaAnalysis, MinutaChrome, MinutaVersion } from '../com
5
5
  import { useMinutaApi } from '../composables/useMinutaApi'
6
6
  import type { ApplyChatEditResult, MinutaChatEdit } from '../composables/chatEditOps'
7
7
  import { applyChatEditOps, mergeChatAnnotations } from '../composables/chatEditOps'
8
- import { pocPageKey } from '../composables/paginationPlugin'
8
+ import { pocPageKey } from '@meistrari/minuta-editor'
9
9
  import { pocEnterVersionDiff, pocExitVersionDiff } from '../composables/versionDiff'
10
10
  import { refreshVaultImages } from '../composables/vaultImages'
11
11
  import TelaButton from '@meistrari/tela-build/components/tela/button/button.vue'
@@ -219,7 +219,7 @@ async function exportDocx() {
219
219
  await saveNow()
220
220
  // a lib `docx` é o maior pedaço do bundle e só serve no clique:
221
221
  // carregar sob demanda tira ~1 MB do abrir da minuta
222
- const { pocExportDocx } = await import('../composables/docxExport')
222
+ const { pocExportDocx } = await import('@meistrari/minuta-editor/docx')
223
223
  const breaks = pocPageKey.getState(ed.state)?.breaks ?? []
224
224
  const blob = await pocExportDocx(ed, props.margins, {
225
225
  headerText: chromeText.header,
@@ -3,8 +3,8 @@ import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
3
3
  import type { Editor } from '@tiptap/core'
4
4
  import MinutaDocxEditor from './MinutaDocxEditor.vue'
5
5
  import MinutaChromeEditor from './MinutaChromeEditor.vue'
6
- import type { MinutaVariableDef } from '../utils/templateVariables'
7
- import { extractTemplateVariables } from '../utils/templateVariables'
6
+ import type { MinutaVariableDef } from '@meistrari/minuta-editor'
7
+ import { extractTemplateVariables } from '@meistrari/minuta-editor'
8
8
  import type { MinutaChrome } from '../composables/useMinutaApi'
9
9
 
10
10
  /**
@@ -1,5 +1,5 @@
1
- import { extractTemplateVariables, resolveTemplateVariables } from '../utils/templateVariables.js';
2
- export type { MinutaVariableDef } from '../utils/templateVariables.js';
1
+ import { extractTemplateVariables, resolveTemplateVariables } from '@meistrari/minuta-editor';
2
+ export type { MinutaVariableDef } from '@meistrari/minuta-editor';
3
3
  export { extractTemplateVariables, resolveTemplateVariables };
4
4
  export interface MinutaFile {
5
5
  name: string;
@@ -1,5 +1,5 @@
1
1
  import { $fetch } from "ofetch";
2
- import { extractTemplateVariables, resolveTemplateVariables } from "../utils/templateVariables.js";
2
+ import { extractTemplateVariables, resolveTemplateVariables } from "@meistrari/minuta-editor";
3
3
  export { extractTemplateVariables, resolveTemplateVariables };
4
4
  export function useMinutaApi() {
5
5
  const base = "/api/minuta";
@@ -1,9 +1,5 @@
1
1
  import type { Editor } from '@tiptap/core';
2
- /** mesmo escape do lazy-vault-images pro valor do atributo */
3
- export declare function escapeVaultAttr(value: string): string;
4
- export declare function decodeVaultAttr(value: string): string;
5
- /** tag de imagem que o editor insere após o upload */
6
- export declare function vaultImageHtml(src: string, vaultUrl: string): string;
2
+ export { decodeVaultAttr, escapeVaultAttr, vaultImageHtml } from '@meistrari/minuta-editor';
7
3
  /**
8
4
  * Re-resolve todas as imagens vault:// do documento (o src presignado salvo
9
5
  * pode ter expirado). Transação fora do histórico de undo e marcada como
@@ -1,19 +1,9 @@
1
1
  import { PROGRAMMATIC_META } from "./annotationAnchors.js";
2
+ import { decodeVaultAttr } from "@meistrari/minuta-editor";
3
+ export { decodeVaultAttr, escapeVaultAttr, vaultImageHtml } from "@meistrari/minuta-editor";
2
4
  function escapeAttr(value) {
3
5
  return value.replace(/&/g, "&").replace(/"/g, """);
4
6
  }
5
- function escapeRegExp(value) {
6
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7
- }
8
- export function escapeVaultAttr(value) {
9
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
10
- }
11
- export function decodeVaultAttr(value) {
12
- return value.replace(/&quot;/g, '"').replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
13
- }
14
- export function vaultImageHtml(src, vaultUrl) {
15
- return `<img src="${escapeAttr(src)}" data-vault-src="${escapeVaultAttr(vaultUrl)}" style="width: 420px; height: auto; max-width: 100%;">`;
16
- }
17
7
  function swapSrcKeepRef(html, attrValue, freshUrl) {
18
8
  const needle = `data-vault-src="${attrValue}"`;
19
9
  if (!html.includes(needle))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/minuta-nuxt",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Embed do editor de minuta (docx paginado A4) do defesa.ai para apps Nuxt — componente + proxy Nitro, sem segredos no browser",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@fontsource-variable/inter": "5.3.0",
42
+ "@meistrari/minuta-editor": "^1.0.0",
42
43
  "@nuxt/kit": "^3.17.7",
43
44
  "@phosphor-icons/vue": "2.2.1",
44
45
  "@tiptap/core": "2.11.9",
@@ -1,149 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, onMounted, ref, watch } from 'vue'
3
- import type { MinutaVariableDef } from '../utils/templateVariables'
4
-
5
- /**
6
- * Seletor de variável do template — o popover que abre pelo `/` no texto ou
7
- * pelo botão da toolbar.
8
- *
9
- * A busca é por RÓTULO, não por chave: quem monta o template pensa em "número
10
- * do processo", não em `PROCESSO`. A chave aparece ao lado porque é ela que o
11
- * host recebe de volta, e ver as duas juntas é o que ensina o vocabulário.
12
- */
13
-
14
- const props = defineProps<{
15
- variables: MinutaVariableDef[]
16
- /** posição absoluta dentro do container do editor */
17
- top: number
18
- left: number
19
- /** texto digitado depois do `/` (busca ao vivo do gatilho no documento) */
20
- query?: string
21
- }>()
22
-
23
- const emit = defineEmits<{ select: [variable: MinutaVariableDef], close: [] }>()
24
-
25
- const search = ref('')
26
- const activeIndex = ref(0)
27
- const searchInput = ref<HTMLInputElement | null>(null)
28
-
29
- /** o gatilho `/` filtra pelo que está sendo digitado NO DOCUMENTO; o botão, pelo campo */
30
- const term = computed(() => (props.query ?? search.value).trim().toLowerCase())
31
-
32
- const filtered = computed(() => {
33
- if (!term.value)
34
- return props.variables
35
- return props.variables.filter(v =>
36
- v.label.toLowerCase().includes(term.value)
37
- || v.key.toLowerCase().includes(term.value)
38
- || (v.description?.toLowerCase().includes(term.value) ?? false),
39
- )
40
- })
41
-
42
- /** lista achatada com cabeçalhos de grupo — o índice ativo anda só nos itens */
43
- const rows = computed(() => {
44
- const out: Array<{ kind: 'group', label: string } | { kind: 'item', variable: MinutaVariableDef, index: number }> = []
45
- let index = 0
46
- let lastGroup: string | null = null
47
- for (const variable of filtered.value) {
48
- const group = variable.group ?? null
49
- if (group && group !== lastGroup)
50
- out.push({ kind: 'group', label: group })
51
- lastGroup = group
52
- out.push({ kind: 'item', variable, index })
53
- index++
54
- }
55
- return out
56
- })
57
-
58
- watch(filtered, () => {
59
- activeIndex.value = 0
60
- })
61
-
62
- /**
63
- * Aberto pelo BOTÃO (sem `query`), o filtro é o campo daqui e ele precisa do
64
- * foco — quem abriu não estava digitando no documento. Aberto pelo GATILHO `/`,
65
- * o foco fica no editor de propósito: a pessoa continua digitando o texto do
66
- * filtro no próprio parágrafo, e é o pai que encaminha as teclas para cá.
67
- *
68
- * `onMounted`, e não um watcher `immediate`: no watcher o ref do input ainda
69
- * não existe, e o foco caía no editor — daí o que se digitava virar texto do
70
- * documento em vez de busca.
71
- */
72
- onMounted(() => {
73
- if (props.query === undefined)
74
- searchInput.value?.focus()
75
- })
76
-
77
- function move(delta: number) {
78
- const total = filtered.value.length
79
- if (!total)
80
- return
81
- activeIndex.value = (activeIndex.value + delta + total) % total
82
- }
83
-
84
- function choose(index = activeIndex.value) {
85
- const variable = filtered.value[index]
86
- if (variable)
87
- emit('select', variable)
88
- }
89
-
90
- /** o gatilho `/` mantém o foco no editor — o pai delega as teclas para cá */
91
- defineExpose({ move, choose, hasResults: () => filtered.value.length > 0 })
92
- </script>
93
-
94
- <template>
95
- <div
96
- class="poc-varpicker"
97
- :style="{ top: `${top}px`, left: `${left}px` }"
98
- @click.stop
99
- >
100
- <input
101
- v-if="query === undefined"
102
- ref="searchInput"
103
- v-model="search"
104
- class="poc-varpicker__search"
105
- placeholder="Buscar variável…"
106
- spellcheck="false"
107
- @keydown.down.prevent="move(1)"
108
- @keydown.up.prevent="move(-1)"
109
- @keydown.enter.prevent="choose()"
110
- @keydown.esc.prevent="emit('close')"
111
- >
112
- <div v-else class="poc-varpicker__hint">
113
- Variável<span v-if="query">: <b>{{ query }}</b></span>
114
- </div>
115
-
116
- <!-- `mousedown.prevent` só na lista: na raiz ele impediria o clique de
117
- focar o próprio campo de busca. Aqui ele serve para o clique num
118
- item não tirar o foco de onde está (editor ou campo) antes do
119
- `click` — o que fecharia o popover pelo caminho de "clicou fora". -->
120
- <div class="poc-varpicker__list" @mousedown.prevent>
121
- <p v-if="!filtered.length" class="poc-varpicker__empty">
122
- Nenhuma variável encontrada.
123
- </p>
124
- <template v-for="(row, i) in rows" :key="i">
125
- <div v-if="row.kind === 'group'" class="poc-varpicker__group">
126
- {{ row.label }}
127
- </div>
128
- <button
129
- v-else
130
- type="button"
131
- class="poc-varpicker__item"
132
- :class="{ 'poc-varpicker__item--on': row.index === activeIndex }"
133
- @mouseenter="activeIndex = row.index"
134
- @click="choose(row.index)"
135
- >
136
- <span class="poc-varpicker__label">{{ row.variable.label }}</span>
137
- <span class="poc-varpicker__key">{{ row.variable.key }}</span>
138
- <span v-if="row.variable.description" class="poc-varpicker__desc">
139
- {{ row.variable.description }}
140
- </span>
141
- </button>
142
- </template>
143
- </div>
144
- </div>
145
- </template>
146
-
147
- <style scoped>
148
- .poc-varpicker{background:#fff;border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 12px 24px -8px rgba(0,0,0,.25);display:flex;flex-direction:column;font-size:13px;max-height:300px;overflow:hidden;position:absolute;width:320px;z-index:60}.poc-varpicker__search{border:0;border-bottom:1px solid #f3f4f6;font-family:inherit;font-size:13px;outline:none;padding:10px 12px}.poc-varpicker__hint{border-bottom:1px solid #f3f4f6;color:#6b7280;font-size:12px;padding:8px 12px}.poc-varpicker__list{flex:1;min-height:0;overflow-y:auto;padding:4px}.poc-varpicker__empty{color:#6b7280;font-size:12px;margin:0;padding:12px}.poc-varpicker__group{color:#9ca3af;font-size:11px;font-weight:600;letter-spacing:.04em;padding:8px 8px 4px;text-transform:uppercase}.poc-varpicker__item{background:none;border:0;border-radius:6px;cursor:pointer;display:grid;font-family:inherit;gap:2px 8px;grid-template-columns:1fr auto;padding:6px 8px;text-align:left;width:100%}.poc-varpicker__item--on{background:#f3f4f6}.poc-varpicker__label{color:#111827;font-weight:500}.poc-varpicker__key{color:#9ca3af;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.poc-varpicker__desc{color:#6b7280;display:-webkit-box;font-size:11px;grid-column:1/-1;-webkit-line-clamp:2;line-height:1.4;overflow:hidden;-webkit-box-orient:vertical}
149
- </style>
@@ -1,24 +0,0 @@
1
- import type { Editor } from '@tiptap/core';
2
- import type { PocPageBreak } from './paginationPlugin.js';
3
- export interface PocDocxChrome {
4
- headerText?: string;
5
- footerText?: string;
6
- headerLogoUrl?: string | null;
7
- headerPosition?: 'left' | 'center' | 'right';
8
- /** cabeçalho em todas as páginas ou só na primeira */
9
- headerScope?: 'all' | 'first';
10
- /** conteúdo custom do rodapé (o nº de página aparece sempre) */
11
- footerScope?: 'all' | 'first';
12
- /** numeração "Página N de M" no rodapé (default: ligada) */
13
- showPageNumber?: boolean;
14
- }
15
- export interface PocDocxExportOptions {
16
- /** quebras calculadas pelo engine de paginação do preview → paginação 1:1 */
17
- breaks?: PocPageBreak[];
18
- }
19
- export declare function pocExportDocx(editor: Editor, margins: {
20
- top: number;
21
- right: number;
22
- bottom: number;
23
- left: number;
24
- }, chrome?: PocDocxChrome, options?: PocDocxExportOptions): Promise<Blob>;