@meistrari/minuta-nuxt 0.1.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.
Files changed (45) hide show
  1. package/README.md +106 -0
  2. package/dist/module.cjs +5 -0
  3. package/dist/module.d.mts +18 -0
  4. package/dist/module.d.ts +18 -0
  5. package/dist/module.json +9 -0
  6. package/dist/module.mjs +45 -0
  7. package/dist/runtime/components/MinutaAnnotationsGutter.vue +425 -0
  8. package/dist/runtime/components/MinutaChatEditCard.vue +124 -0
  9. package/dist/runtime/components/MinutaChromeCanvas.vue +549 -0
  10. package/dist/runtime/components/MinutaChromeEditor.vue +100 -0
  11. package/dist/runtime/components/MinutaDocumentViewer.vue +90 -0
  12. package/dist/runtime/components/MinutaDocxEditor.vue +1158 -0
  13. package/dist/runtime/components/MinutaEmbed.vue +783 -0
  14. package/dist/runtime/components/MinutaIcon.vue +53 -0
  15. package/dist/runtime/composables/annotationAnchors.d.ts +73 -0
  16. package/dist/runtime/composables/annotationAnchors.js +243 -0
  17. package/dist/runtime/composables/chatEditOps.d.ts +37 -0
  18. package/dist/runtime/composables/chatEditOps.js +61 -0
  19. package/dist/runtime/composables/docxExport.d.ts +24 -0
  20. package/dist/runtime/composables/docxExport.js +1292 -0
  21. package/dist/runtime/composables/editorExtensions.d.ts +56 -0
  22. package/dist/runtime/composables/editorExtensions.js +466 -0
  23. package/dist/runtime/composables/paginationPlugin.d.ts +126 -0
  24. package/dist/runtime/composables/paginationPlugin.js +421 -0
  25. package/dist/runtime/composables/useMinutaApi.d.ts +112 -0
  26. package/dist/runtime/composables/useMinutaApi.js +68 -0
  27. package/dist/runtime/composables/vaultImages.d.ts +12 -0
  28. package/dist/runtime/composables/vaultImages.js +85 -0
  29. package/dist/runtime/composables/versionDiff.d.ts +34 -0
  30. package/dist/runtime/composables/versionDiff.js +62 -0
  31. package/dist/runtime/server/api/minuta/[...path].d.ts +2 -0
  32. package/dist/runtime/server/api/minuta/[...path].js +6 -0
  33. package/dist/runtime/server/api/minuta/index.d.ts +2 -0
  34. package/dist/runtime/server/api/minuta/index.js +3 -0
  35. package/dist/runtime/server/utils/minuta-proxy.d.ts +10 -0
  36. package/dist/runtime/server/utils/minuta-proxy.js +63 -0
  37. package/dist/runtime/utils/footnoteHtml.d.ts +26 -0
  38. package/dist/runtime/utils/footnoteHtml.js +117 -0
  39. package/dist/runtime/utils/pocKeepWithNext.d.ts +44 -0
  40. package/dist/runtime/utils/pocKeepWithNext.js +9 -0
  41. package/dist/runtime/utils/sanitizeRichHtml.d.ts +10 -0
  42. package/dist/runtime/utils/sanitizeRichHtml.js +73 -0
  43. package/dist/types.d.mts +7 -0
  44. package/dist/types.d.ts +7 -0
  45. package/package.json +100 -0
@@ -0,0 +1,100 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import { sanitizeRichHtml } from '../utils/sanitizeRichHtml'
4
+ import MinutaChromeCanvas from './MinutaChromeCanvas.vue'
5
+ import MinutaIcon from './MinutaIcon.vue'
6
+
7
+ /**
8
+ * Editor de cabeçalho/rodapé da folha — canvas estilo Word em tamanho real da
9
+ * faixa (texto/imagem/forma com arrasto, resize e fonte). Port do
10
+ * poc-chrome-editor do defesa.ai com CSS puro.
11
+ */
12
+ const props = withDefaults(defineProps<{
13
+ part: 'header' | 'footer'
14
+ html: string
15
+ scope: 'all' | 'first'
16
+ /** altura real da faixa (margem da página) — vem do consumidor */
17
+ bandHeightPx?: number
18
+ docFont?: string
19
+ showPageNumber?: boolean
20
+ /** mostra o toggle de numeração no rodapé */
21
+ allowPageNumberToggle?: boolean
22
+ }>(), {
23
+ bandHeightPx: 94,
24
+ docFont: 'Arial',
25
+ showPageNumber: true,
26
+ allowPageNumberToggle: false,
27
+ })
28
+
29
+ const emit = defineEmits<{
30
+ apply: [payload: { html: string, scope: 'all' | 'first', showPageNumber?: boolean }]
31
+ close: []
32
+ }>()
33
+
34
+ const localScope = ref<'all' | 'first'>(props.scope)
35
+ const localHtml = ref(sanitizeRichHtml(props.html))
36
+ const localShowPageNumber = ref(props.showPageNumber)
37
+
38
+ const effectiveShowPageNumber = computed(() =>
39
+ props.allowPageNumberToggle ? localShowPageNumber.value : props.showPageNumber)
40
+
41
+ function apply() {
42
+ emit('apply', {
43
+ html: sanitizeRichHtml(localHtml.value),
44
+ scope: localScope.value,
45
+ ...(props.allowPageNumberToggle ? { showPageNumber: localShowPageNumber.value } : {}),
46
+ })
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <div class="minuta-chrome-card">
52
+ <div class="minuta-chrome-card__head">
53
+ <span class="minuta-chrome-card__badge">
54
+ {{ part === 'header' ? 'Cabeçalho das páginas' : 'Rodapé das páginas' }}
55
+ </span>
56
+ <button class="minuta-chrome-card__close" type="button" @click="emit('close')">
57
+ <MinutaIcon name="i-ph-x" />
58
+ </button>
59
+ </div>
60
+
61
+ <MinutaChromeCanvas
62
+ :html="localHtml"
63
+ :width-px="794"
64
+ :height-px="bandHeightPx"
65
+ :doc-font="docFont"
66
+ :ghost="part === 'footer' && effectiveShowPageNumber ? 'Página 1 de N' : ''"
67
+ :placeholder="part === 'header'
68
+ ? 'Monte o cabeçalho: texto, imagem e formas — arraste como no Word'
69
+ : (effectiveShowPageNumber ? 'Monte o rodapé — o nº de página é automático' : 'Monte o rodapé — sem numeração de páginas')"
70
+ class="minuta-chrome-card__canvas"
71
+ @change="(html: string) => localHtml = html"
72
+ />
73
+
74
+ <label v-if="allowPageNumberToggle && part === 'footer'" class="minuta-chrome-card__pagenum">
75
+ <input v-model="localShowPageNumber" type="checkbox">
76
+ Numerar as páginas automaticamente ("Página 1 de N")
77
+ </label>
78
+
79
+ <div class="minuta-chrome-card__scope">
80
+ <span>Aparece em</span>
81
+ <select v-model="localScope">
82
+ <option value="all">
83
+ Todas as páginas
84
+ </option>
85
+ <option value="first">
86
+ Só na primeira página
87
+ </option>
88
+ </select>
89
+ </div>
90
+ <div class="minuta-chrome-card__actions">
91
+ <button class="minuta-chrome-card__apply" type="button" @click="apply">
92
+ Aplicar
93
+ </button>
94
+ </div>
95
+ </div>
96
+ </template>
97
+
98
+ <style scoped>
99
+ .minuta-chrome-card{background:#fff;border:1px solid #ddd6fe;border-radius:12px;box-shadow:0 10px 25px -5px rgba(0,0,0,.15);max-height:calc(100vh - 120px);overflow-y:auto;padding:16px;width:min(846px,calc(100vw - 48px))}.minuta-chrome-card__head{align-items:center;display:flex;justify-content:space-between;margin-bottom:8px}.minuta-chrome-card__badge{background:#ede9fe;border-radius:999px;color:#6d28d9;font-size:12px;font-weight:600;padding:2px 10px}.minuta-chrome-card__close{background:none;border:none;color:#9ca3af;cursor:pointer;display:inline-flex}.minuta-chrome-card__close:hover{color:#1f2937}.minuta-chrome-card__canvas{margin-bottom:10px}.minuta-chrome-card__pagenum{align-items:center;color:#374151;display:flex;font-size:12px;gap:8px;margin-bottom:8px}.minuta-chrome-card__scope{align-items:center;color:#6b7280;display:flex;font-size:12px;gap:8px;margin-bottom:10px}.minuta-chrome-card__scope select{background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#1f2937;font-size:12px;outline:none;padding:4px 8px;width:220px}.minuta-chrome-card__actions{display:flex;justify-content:flex-end}.minuta-chrome-card__apply{background:rgba(0,0,0,.88);border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:12.5px;font-weight:500;padding:5px 14px}
100
+ </style>
@@ -0,0 +1,90 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue'
3
+ import type { MinutaFile } from '../composables/useMinutaApi'
4
+ import { useMinutaApi } from '../composables/useMinutaApi'
5
+
6
+ /**
7
+ * Overlay de visualização dos documentos do caso (imagem/texto/iframe),
8
+ * aberto pelos cards de fato extraído da margem ou pelo botão Documentos.
9
+ */
10
+
11
+ const props = defineProps<{
12
+ files: MinutaFile[]
13
+ initialUrl?: string | null
14
+ /** página inicial (PDF em iframe via #page=N) */
15
+ initialPage?: number | null
16
+ }>()
17
+
18
+ const emit = defineEmits<{ close: [], error: [message: string] }>()
19
+
20
+ const { getDownloadUrl } = useMinutaApi()
21
+
22
+ const selected = ref(props.initialUrl || props.files[0]?.url || '')
23
+ const content = ref<{ src: string, kind: 'image' | 'text' | 'frame', text?: string } | null>(null)
24
+ const loading = ref(false)
25
+
26
+ const currentName = computed(() => props.files.find(f => f.url === selected.value)?.name ?? '')
27
+
28
+ async function load() {
29
+ const file = props.files.find(f => f.url === selected.value)
30
+ if (!file)
31
+ return
32
+ loading.value = true
33
+ content.value = null
34
+ try {
35
+ const src = await getDownloadUrl(file.url)
36
+ if (/\.(png|jpe?g|gif|webp|svg)$/i.test(file.name)) {
37
+ content.value = { src, kind: 'image' }
38
+ }
39
+ else if (/\.(txt|md|json|csv|log)$/i.test(file.name)) {
40
+ const text = await (await fetch(src)).text()
41
+ content.value = { src, kind: 'text', text }
42
+ }
43
+ else {
44
+ const isPdf = /\.pdf$/i.test(file.name)
45
+ const page = props.initialPage && file.url === props.initialUrl ? `#page=${props.initialPage}` : ''
46
+ content.value = { src: isPdf ? src + page : src, kind: 'frame' }
47
+ }
48
+ }
49
+ catch {
50
+ emit('error', 'Não consegui carregar o documento')
51
+ }
52
+ finally {
53
+ loading.value = false
54
+ }
55
+ }
56
+
57
+ watch(selected, load, { immediate: true })
58
+ </script>
59
+
60
+ <template>
61
+ <Teleport to="body">
62
+ <div class="minuta-viewer__overlay" @click.self="emit('close')">
63
+ <div class="minuta-viewer__modal">
64
+ <div class="minuta-viewer__head">
65
+ <select v-if="files.length > 1" v-model="selected">
66
+ <option v-for="file in files" :key="file.url" :value="file.url">
67
+ {{ file.name }}
68
+ </option>
69
+ </select>
70
+ <span v-else>{{ currentName }}</span>
71
+ <button type="button" @click="emit('close')">
72
+ ×
73
+ </button>
74
+ </div>
75
+ <div class="minuta-viewer__body">
76
+ <p v-if="loading" class="minuta-viewer__loading">
77
+ Carregando…
78
+ </p>
79
+ <img v-else-if="content?.kind === 'image'" :src="content.src">
80
+ <pre v-else-if="content?.kind === 'text'">{{ content.text }}</pre>
81
+ <iframe v-else-if="content" :src="content.src" />
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </Teleport>
86
+ </template>
87
+
88
+ <style scoped>
89
+ .minuta-viewer__overlay{align-items:center;background:rgba(0,0,0,.5);display:flex;inset:0;justify-content:center;padding:24px;position:fixed;z-index:100}.minuta-viewer__modal{background:#fff;border-radius:12px;box-shadow:0 20px 25px -5px rgba(0,0,0,.2);display:flex;flex-direction:column;height:100%;max-height:90vh;max-width:960px;overflow:hidden;width:100%}.minuta-viewer__head{align-items:center;border-bottom:1px solid #e5e7eb;color:#111827;display:flex;font-size:14px;font-weight:600;gap:12px;justify-content:space-between;padding:10px 16px}.minuta-viewer__head select{background:#fff;border:.5px solid #d4d9df;border-radius:6px;font-size:13px;max-width:70%;padding:4px 8px}.minuta-viewer__head button{background:none;border:none;color:#6b7280;cursor:pointer;font-size:18px;line-height:1}.minuta-viewer__body{background:#f9fafb;flex:1;min-height:0;overflow-y:auto}.minuta-viewer__loading{color:#6b7280;font-size:13px;padding:24px}.minuta-viewer__body img{height:100%;-o-object-fit:contain;object-fit:contain;width:100%}.minuta-viewer__body pre{color:#1f2937;font-size:13px;line-height:1.6;margin:0;padding:20px;white-space:pre-wrap}.minuta-viewer__body iframe{border:0;height:100%;width:100%}
90
+ </style>