Skip to content

Commit

Permalink
feat: plugins should support displaying detailed information in README
Browse files Browse the repository at this point in the history
  • Loading branch information
rxliuli committed Oct 14, 2024
1 parent b0417d7 commit 62bd840
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
import { dbApi, initDB } from '$lib/api/db'
import { convStore } from '$lib/stores/converstation'
import { initPluginSystem, destoryPluginSystem } from '$lib/plugins/command'
import { pluginStore } from '$lib/plugins/store'
import Plugin from './routes/plugins/[id]/page.svelte'
const routes = {
'/': Home,
'/conversation/:id': Chat,
'/plugins/:id': Plugin,
'/plugins': Plugins,
'/settings': Settings,
'*': NotFound,
Expand Down
5 changes: 5 additions & 0 deletions src/lib/plugins/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface PluginManifest {
name: string
version: string
description?: string
author?: string
homepage?: string
repository?: string
lastUpdated?: string
icons?: Record<string, string>
configuration?: SettingSchema
}

Expand Down
6 changes: 3 additions & 3 deletions src/routes/page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { push } from 'svelte-spa-router'
import { toast } from 'svelte-sonner'
import { onMount } from 'svelte'
import { pluginStore } from '$lib/plugins/store'
import { installedPlugins, pluginStore } from '$lib/plugins/store'
let loading = false
let pending = false
Expand All @@ -31,8 +31,8 @@
push(`/conversation/${id}`)
}
onMount(() => {
if ($pluginStore.plugins.length === 0) {
onMount(async () => {
if ((await installedPlugins.getValue()).length === 0) {
toast.info('Please install a plugin', {
action: {
label: 'Install Plugin',
Expand Down
134 changes: 134 additions & 0 deletions src/routes/plugins/[id]/page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<script lang="ts">
import { type PluginManifest } from '$lib/plugins/store'
import InstallButton from '../components/InstallButton.svelte'
import {
activePluginFromLocal,
installPluginForRemote,
loadRemotePlugins,
} from '$lib/plugins/command'
import { toast } from 'svelte-sonner'
import { onMount } from 'svelte'
import { gfmFromMarkdown } from 'mdast-util-gfm'
import { newlineToBreak } from 'mdast-util-newline-to-break'
import { gfm } from 'micromark-extension-gfm'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { toHast } from 'mdast-util-to-hast'
import { toHtml } from 'hast-util-to-html'
import { createHighlighter } from 'shiki'
import type { Code } from 'mdast'
import type { Element } from 'hast'
export let params: { id: string } = { id: '' }
let plugin: PluginManifest
let readmeHtml: string = ''
onMount(async () => {
const plugins = await loadRemotePlugins()
const findPlugin = plugins.find((it) => it.id === params.id)
if (findPlugin) {
plugin = findPlugin
}
const r = await fetch(
`https://raw.githubusercontent.com/novachat/plugins/refs/heads/main/plugins/${plugin.id}/README.md`,
)
if (!r.ok) {
return
}
const root = fromMarkdown(await r.text(), {
extensions: [gfm()],
mdastExtensions: [gfmFromMarkdown(), { transforms: [newlineToBreak] }],
})
const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark'],
langs: ['typescript', 'javascript', 'json'],
})
const hast = toHast(root, {
handlers: {
code: (state, node, parent) => {
const code = node as Code
const lang = code.lang
const value = code.value
const hast = highlighter.codeToHast(value, {
lang: lang!,
themes: {
light: 'github-light',
dark: 'github-dark',
},
})
return [hast.children[0] as Element]
},
},
})
readmeHtml = toHtml(hast)
})
async function onInstallPlugin(manifest: PluginManifest) {
try {
await installPluginForRemote(manifest)
await activePluginFromLocal(manifest.id)
} catch (err) {
console.error(err)
toast.error('Failed to install plugin')
}
}
</script>

<div class="container p-2 md:p-4 max-w-3xl">
{#if plugin}
<div class="p-5 bg-gray-900 text-white">
<div class="flex items-start mb-5">
{#if plugin.icons}
<img
src={plugin.icons['128']}
alt={plugin.name}
class="w-16 h-16 mr-5"
/>
{:else}
<div
class="w-16 h-16 mr-5 bg-gray-800 rounded-full flex items-center justify-center"
>
<span class="text-2xl font-bold">{plugin.name[0]}</span>
</div>
{/if}
<div>
<h1 class="text-2xl font-bold mb-2">{plugin.name}</h1>
<p class="text-gray-300 mb-4">{plugin.description}</p>
<InstallButton onClick={() => onInstallPlugin(plugin)}>
Install
</InstallButton>
</div>
</div>
<div class="grid grid-cols-[auto_1fr] gap-x-4">
<span class="text-sm text-gray-400">Author</span>
<span>{plugin.author}</span>
<span class="text-sm text-gray-400">Version</span>
<span>{plugin.version}</span>
<span class="text-sm text-gray-400">Updated</span>
<span
>{(plugin.lastUpdated
? new Date(plugin.lastUpdated)
: new Date()
).toLocaleDateString()}</span
>
{#if plugin.homepage}
<a
href={plugin.homepage}
target="_blank"
rel="noopener noreferrer"
class="text-blue-400 hover:underline">Website</a
>
<span />
{/if}
</div>
<hr class="my-4" />
<div
class="prose dark:prose-invert max-sm:prose-sm max-w-none [&_p]:break-words [&_p]:break-all"
>
{@html readmeHtml}
</div>
</div>
{:else}
<div class="p-5 bg-gray-900 text-white">Plugin not found</div>
{/if}
</div>
9 changes: 8 additions & 1 deletion src/routes/plugins/components/InstallButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@
}
</script>

<Button size="sm" on:click={() => handleClick()} disabled={loading}>
<Button
size="sm"
on:click={(ev) => {
ev.preventDefault()
handleClick()
}}
disabled={loading}
>
{#if loading}
<Loader2Icon class="mr-1 w-4 h-4 animate-spin" />
{/if}
Expand Down
12 changes: 8 additions & 4 deletions src/routes/plugins/page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
type PluginManifest,
} from '$lib/plugins/store'
import { fileSelector } from '$lib/utils/fileSelector'
import { uniq, uniqBy } from 'lodash-es'
import { uniqBy } from 'lodash-es'
import { BlocksIcon, Loader2Icon } from 'lucide-svelte'
import { toast } from 'svelte-sonner'
import InstallButton from './components/InstallButton.svelte'
Expand Down Expand Up @@ -85,8 +85,9 @@
{:then _}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{#each plugins as plugin}
<div
<a
class="bg-gray-100 dark:bg-gray-800 rounded-2xl border border-gray-100 dark:border-gray-800 p-4 flex flex-col justify-between"
href={`#/plugins/${plugin.manifest.id}`}
>
<BlocksIcon class="w-4 h-4" />
<h3 class="text-lg font-semibold">{plugin.manifest.name}</h3>
Expand All @@ -104,7 +105,10 @@
{/if}
<Button
size="sm"
on:click={() => onUninstallPlugin(plugin.manifest.id)}
on:click={(ev) => {
ev.preventDefault()
onUninstallPlugin(plugin.manifest.id)
}}
>
Uninstall
</Button>
Expand All @@ -116,7 +120,7 @@
</InstallButton>
{/if}
</div>
</div>
</a>
{/each}
</div>
{:catch somError}
Expand Down

0 comments on commit 62bd840

Please sign in to comment.