@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.
- package/dist/module.json +1 -1
- package/dist/runtime/components/MinutaChromeCanvas.vue +1 -1
- package/dist/runtime/components/MinutaChromeEditor.vue +1 -1
- package/dist/runtime/components/MinutaDocxEditor.vue +64 -1302
- package/dist/runtime/components/MinutaEmbed.vue +2 -2
- package/dist/runtime/components/MinutaTemplateEditor.vue +2 -2
- package/dist/runtime/composables/useMinutaApi.d.ts +2 -2
- package/dist/runtime/composables/useMinutaApi.js +1 -1
- package/dist/runtime/composables/vaultImages.d.ts +1 -5
- package/dist/runtime/composables/vaultImages.js +2 -12
- package/package.json +2 -1
- package/dist/runtime/components/MinutaVariablePicker.vue +0 -149
- package/dist/runtime/composables/docxExport.d.ts +0 -24
- package/dist/runtime/composables/docxExport.js +0 -1304
- package/dist/runtime/composables/editorExtensions.d.ts +0 -93
- package/dist/runtime/composables/editorExtensions.js +0 -548
- package/dist/runtime/composables/paginationPlugin.d.ts +0 -139
- package/dist/runtime/composables/paginationPlugin.js +0 -505
- package/dist/runtime/utils/footnoteHtml.d.ts +0 -26
- package/dist/runtime/utils/footnoteHtml.js +0 -117
- package/dist/runtime/utils/pocKeepWithNext.d.ts +0 -44
- package/dist/runtime/utils/pocKeepWithNext.js +0 -9
- package/dist/runtime/utils/sanitizeRichHtml.d.ts +0 -10
- package/dist/runtime/utils/sanitizeRichHtml.js +0 -73
- package/dist/runtime/utils/templateVariables.d.ts +0 -49
- package/dist/runtime/utils/templateVariables.js +0 -36
|
@@ -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 '
|
|
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('
|
|
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 '
|
|
7
|
-
import { extractTemplateVariables } from '
|
|
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 '
|
|
2
|
-
export type { MinutaVariableDef } from '
|
|
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 "
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
10
|
-
}
|
|
11
|
-
export function decodeVaultAttr(value) {
|
|
12
|
-
return value.replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/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.
|
|
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>;
|