kunkun/apps/desktop/src/routes/app/extension/clipboard/content-preview.svelte
Huakun Shen e4d1441d73
Feature: support npm extension publish (#62)
* feat: npm package registry API

* refactor: move package registry files

* refactor: move jsr and npm api to a new package

* ci: add verify-package-export

* test: implement tests for npm package validation as kunkun extension

* chore: add missing dep for package-registry pkg

* feat: make provenance an optional input for npm validation function

* ci: add verify-package-export as dev dep to 2 packages that uses it

* feat: add rekor log API, and return commit from jsr & npm package in validation function

* feat: return github repo info from validation function of jsr and npm

* feat: extend ExtPublishMetadata to include optional GitHub repository details

* fix: eslint for ui package

* refactor: format desktop

* fix: eslint errors in desktop

* format: all code

* ci: add lint to CI

* feat: add more info to validation function returned from package-registry npm jsr

* pnpm lock

* feat: add 2 more variables to supabase extension metadata model

* format

* feat: add provenance card

* feat: add workflow path to ExtPublishMetadata and jsr/npm validation

* update provenance

* feat: make store extension and provenance more responsive

* chore: add globals to ui package

* fix: remove unnecessary any to fix eslint

* fix: svg sanitize

* chore: add @typescript-eslint/eslint-plugin to ui package to fix eslint

* fix: update eslint dep to fix error

* fix: try fixing eslint

* fix: update eslint configuration for improved compatibility

* chore: add globals package and update README for Discord invite

* fix: update eslint rules and upgrade typescript-eslint dependency

- Disabled additional eslint rules to resolve errors:
  - @typescript-eslint/no-unused-expressions
  - svelte/no-inner-declarations
- Upgraded typescript-eslint from version 8.19.1 to 8.20.0 for improved compatibility.

* update pnpm lock

---------

Co-authored-by: Huakun Shen <huaukun.shen@huakunshen.com>
2025-01-16 06:00:07 -05:00

105 lines
3.2 KiB
Svelte

<script lang="ts">
import { cn } from "@/utils"
import { db } from "@kksh/api/commands"
import type { ExtData } from "@kksh/api/models"
import { Resizable, Separator } from "@kksh/svelte5"
import { convertFileSrc } from "@tauri-apps/api/core"
import DOMPurify from "dompurify"
function formatDate(date: Date) {
const now = new Date()
const isToday = date.toDateString() === now.toDateString()
const options: Intl.DateTimeFormatOptions = {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true
} as const
const timeString = date.toLocaleTimeString("en-US", options)
if (isToday) {
return `Today at ${timeString}`
} else {
const dateOptions: Intl.DateTimeFormatOptions = {
month: "short",
day: "numeric",
year: "numeric"
} as const
const dateString = date.toLocaleDateString("en-US", dateOptions)
return `${dateString} at ${timeString}`
}
}
let { highlighted }: { highlighted: ExtData } = $props()
let imgSrc = $state<string>("")
let txtData = $state<string>("")
let createTime = $state<Date>()
let imgRef = $state<HTMLImageElement>()
$effect(() => {
;(async () => {
if (highlighted.dataType === "Image") {
const dbRecord = await db.getExtensionDataById(highlighted.dataId, []) // do not load "data" field
imgSrc = await convertFileSrc(`/?id=${highlighted.dataId}`, "cbimg")
createTime = dbRecord?.createdAt
} else {
const dbRecord = await db.getExtensionDataById(highlighted.dataId) // do not load "data" field
txtData = dbRecord?.data || ""
createTime = dbRecord?.createdAt
}
})()
})
</script>
<Resizable.PaneGroup direction="vertical">
<Resizable.Pane defaultSize={50} class="px-2 py-1">
<div
class={cn({
hidden: highlighted.dataType !== "Image",
"h-full": highlighted.dataType === "Image",
"flex justify-center": highlighted.dataType === "Image"
})}
>
<img src={imgSrc} alt="" class="h-full w-auto object-contain" bind:this={imgRef} />
</div>
{#if highlighted.dataType === "Image"}{:else if highlighted.dataType === "Text"}
<div class="text-sm">{txtData}</div>
{:else if highlighted.dataType === "Html"}
<div class="">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html DOMPurify.sanitize(txtData)}
</div>
{:else}
<div class="text-sm">No preview available</div>
{/if}
<!-- </div> -->
</Resizable.Pane>
<Resizable.Handle withHandle />
<Resizable.Pane defaultSize={50} class="space-y-1 px-4 pt-2">
<h2 class="font-mono font-bold">Information</h2>
{#if createTime}
{@render row("Copied At", formatDate(createTime))}
{/if}
<Separator />
{@render row("Content Type", highlighted.dataType || "")}
{#if highlighted.dataType === "Image"}
{#if imgRef}
<Separator />
{@render row("Dimension", `${imgRef.naturalWidth}x${imgRef.naturalHeight}`)}
{/if}
{:else}
<Separator />
{@render row("Character Count", txtData.length.toString())}
<Separator />
{@render row("Word Count", txtData.split(/\s+/).length.toString())}
{/if}
</Resizable.Pane>
</Resizable.PaneGroup>
{#snippet row(label: string, value: string)}
<div class="flex justify-between">
<span class="text-sm font-semibold">{label}</span>
<span class="text-sm">{value}</span>
</div>
{/snippet}