@finema/finework-layer 2.0.63 → 2.0.65

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.65](https://gitlab.finema.co/finema/internal/finework/compare/2.0.64...2.0.65) (2026-09-15)
4
+
5
+ ### Features
6
+
7
+ * add resizable panel to manual viewer with improved PDF navigation ([dc37e28](https://gitlab.finema.co/finema/internal/finework/commit/dc37e28790a2e16168ed1e1264d621efb17cfe56))
8
+
9
+ ## [2.0.64](https://gitlab.finema.co/finema/internal/finework/compare/2.0.63...2.0.64) (2026-09-15)
10
+
11
+ ### Bug Fixes
12
+
13
+ * fetch PDF as blob to bypass X-Frame-Options restrictions and add loading states ([4584b67](https://gitlab.finema.co/finema/internal/finework/commit/4584b67b555b19a3d2f10a25f1d89f9340dec134))
14
+
3
15
  ## [2.0.63](https://gitlab.finema.co/finema/internal/finework/compare/2.0.62...2.0.63) (2026-09-15)
4
16
 
5
17
  ## [2.0.62](https://gitlab.finema.co/finema/internal/finework/compare/2.0.61...2.0.62) (2026-09-15)
@@ -4,12 +4,22 @@
4
4
  :portal="true"
5
5
  :modal="false"
6
6
  :dismissible="false"
7
- :ui="{ content: 'w-full max-w-[560px]' }"
7
+ :ui="{ content: 'w-full' }"
8
+ :content="{
9
+ style: {
10
+ width: `${panelWidth}px`,
11
+ maxWidth: '100vw',
12
+ },
13
+ }"
8
14
  side="right"
9
15
  @update:open="(val) => emit('update', val)"
10
16
  >
11
17
  <template #content>
12
- <div class="flex h-full flex-col">
18
+ <div class="relative flex h-full flex-col">
19
+ <div
20
+ class="hover:bg-primary-300/60 active:bg-primary-400/70 absolute inset-y-0 left-0 z-10 w-1.5 -translate-x-1/2 cursor-col-resize touch-none"
21
+ @mousedown="startResize"
22
+ />
13
23
  <!-- Header -->
14
24
  <div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
15
25
  <div class="flex min-w-0 items-center gap-3">
@@ -246,9 +256,9 @@
246
256
  class="relative flex-1 overflow-hidden bg-gray-600"
247
257
  >
248
258
  <iframe
249
- v-if="current?.file"
250
- :key="current.file.url"
251
- :src="`${current.file.url}#view=FitH`"
259
+ v-if="viewerBlobUrl"
260
+ :key="viewerBlobUrl"
261
+ :src="`${viewerBlobUrl}#view=FitH&navpanes=0`"
252
262
  title="User manual"
253
263
  class="size-full border-0"
254
264
  />
@@ -256,7 +266,7 @@
256
266
  v-else
257
267
  class="flex h-full items-center justify-center bg-white text-sm text-gray-500"
258
268
  >
259
- ไม่พบไฟล์คู่มือ
269
+ {{ viewerError ? 'ไม่สามารถโหลดไฟล์ได้' : (current?.file ? 'กำลังโหลด...' : 'ไม่พบไฟล์คู่มือ') }}
260
270
  </div>
261
271
  </div>
262
272
  </div>
@@ -333,6 +343,76 @@ const current = computed<IManual | null>(
333
343
  () => props.manuals.find((m) => m.id === selectedId.value) ?? null,
334
344
  )
335
345
 
346
+ // The API sets X-Frame-Options/CSP frame-ancestors that block embedding the
347
+ // PDF URL directly in an <iframe> across subdomains. Fetching the bytes and
348
+ // rendering via a blob: URL sidesteps that, since it isn't a framed request.
349
+ const viewerBlobUrl = ref<string | null>(null)
350
+ const viewerError = ref(false)
351
+
352
+ const revokeViewerBlobUrl = () => {
353
+ if (viewerBlobUrl.value) {
354
+ URL.revokeObjectURL(viewerBlobUrl.value)
355
+ viewerBlobUrl.value = null
356
+ }
357
+ }
358
+
359
+ watch(() => current.value?.file?.url, async (url) => {
360
+ revokeViewerBlobUrl()
361
+ viewerError.value = false
362
+
363
+ if (!url) return
364
+
365
+ try {
366
+ const res = await fetch(url)
367
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
368
+
369
+ const blob = await res.blob()
370
+
371
+ viewerBlobUrl.value = URL.createObjectURL(blob)
372
+ } catch {
373
+ viewerError.value = true
374
+ }
375
+ }, {
376
+ immediate: true,
377
+ })
378
+
379
+ onBeforeUnmount(revokeViewerBlobUrl)
380
+
381
+ const DEFAULT_WIDTH = 560
382
+ const MIN_WIDTH = 400
383
+ const MAX_WIDTH = 1100
384
+
385
+ const panelWidth = ref(DEFAULT_WIDTH)
386
+
387
+ const startResize = (e: MouseEvent) => {
388
+ e.preventDefault()
389
+
390
+ const startX = e.clientX
391
+ const startWidth = panelWidth.value
392
+ const prevCursor = document.body.style.cursor
393
+ const prevUserSelect = document.body.style.userSelect
394
+
395
+ document.body.style.cursor = 'col-resize'
396
+ document.body.style.userSelect = 'none'
397
+
398
+ const onMove = (moveEvent: MouseEvent) => {
399
+ const delta = startX - moveEvent.clientX
400
+ const maxWidth = Math.min(MAX_WIDTH, window.innerWidth)
401
+
402
+ panelWidth.value = Math.min(Math.max(startWidth + delta, MIN_WIDTH), maxWidth)
403
+ }
404
+
405
+ const onUp = () => {
406
+ document.body.style.cursor = prevCursor
407
+ document.body.style.userSelect = prevUserSelect
408
+ window.removeEventListener('mousemove', onMove)
409
+ window.removeEventListener('mouseup', onUp)
410
+ }
411
+
412
+ window.addEventListener('mousemove', onMove)
413
+ window.addEventListener('mouseup', onUp)
414
+ }
415
+
336
416
  const canManage = (app: string) =>
337
417
  auth.isSuperAdmin.value || auth.hasPermission(app as UserModule, Permission.SUPER)
338
418
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="fixed right-7 bottom-7 z-40 md:bottom-8.5 print:hidden">
2
+ <div class="fixed right-6 bottom-5.5 z-40 md:bottom-7 print:hidden">
3
3
  <!-- click-away scrim -->
4
4
  <div
5
5
  v-if="isDialOpen"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@finema/finework-layer",
3
3
  "type": "module",
4
- "version": "2.0.63",
4
+ "version": "2.0.65",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground -o",