admin/plugin/updates: new "change log" column #897
Massimo Melina committed
Feb 24, 2025 at 13:49 UTC
8dd4144eea1bcda43dab3e5efc12f59fbcbac83b
4 files changed
+37
-5
admin/src/InstalledPlugins.ts
+28
-2
@@ -2,7 +2,7 @@
2
3
import { apiCall, useApiEx, useApiList } from './api'
4
import { createElement as h, Fragment, useEffect } from 'react'
5
-import { Box, Breakpoint, Link, Paper, useTheme } from '@mui/material'
5
+import { Box, Breakpoint, Link, Paper, Table, TableCell, TableRow, useTheme } from '@mui/material'
6
import { DataTable, DataTableColumn } from './DataTable'
7
import {
8
Clear, Delete, Error as ErrorIcon, FormatPaint as ThemeIcon, ListAlt, PlayCircle, Settings, StopCircle, Upgrade
@@ -20,6 +20,7 @@ import { PLUGIN_ERRORS } from './PluginsPage'
20
import { Btn, Flex, hTooltip, IconBtn, iconTooltip, usePauseButton } from './mui'
21
import VfsPathField from './VfsPathField'
22
23
+// updates=true will show the "check updates" version of the page
24
export default function InstalledPlugins({ updates }: { updates?: true }) {
25
const { list, error, setList, initializing } = useApiList(updates ? 'get_plugin_updates' : 'get_plugins')
26
useEffect(() => {
@@ -37,6 +38,7 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
38
fillFlex: true,
39
initializing,
40
disableColumnSelector: true,
41
+ getRowHeight: updates && (({ model }) => model.changelog ? 'auto' as const : 50),
42
noRows: updates && `No updates available. Only plugins available on "search online" are checked.`,
43
columns: [
44
{
@@ -46,12 +48,13 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
48
minWidth: 150,
49
renderCell: renderName,
50
valueGetter({ row }) { return row.repo || row.id },
49
- mergeRender: { description: { fontSize: 'x-small' } }
51
+ mergeRender: { [updates ? 'changelog' : 'description']: { fontSize: 'x-small' } }
52
},
53
{
54
field: 'version',
55
width: 70,
56
hideUnder: 'sm',
57
+ mergeRender: { installedVersion: { fontSize: 'x-small' } }
58
},
59
themeField,
60
{
@@ -59,6 +62,29 @@ export default function InstalledPlugins({ updates }: { updates?: true }) {
62
flex: 1,
63
hideUnder: 'sm',
64
},
65
+ {
66
+ field: 'installedVersion',
67
+ hidden: true,
68
+ renderCell: ({ value }) => value && `Yours ${value}`
69
+ },
70
+ {
71
+ field: 'changelog',
72
+ headerName: "Change log",
73
+ flex: 2,
74
+ hideUnder: 'sm',
75
+ hidden: !updates,
76
+ sx: { flexDirection: 'column', alignItems: 'flex-start' },
77
+ renderCell({ value, row }) {
78
+ if (!Array.isArray(value)) return null
79
+ return h(Table, { sx: { td: { p: 0 } } },
80
+ _.uniq(_.sortBy(value, 'version').filter(x => _.isString(x.message) && x.message && x.version > row.installedVersion))
81
+ .map((x, i) => h(TableRow, { key: i },
82
+ h(TableCell, {}, "• ", x.version, ': '),
83
+ h(TableCell, {}, md(x.message))
84
+ ))
85
+ )
86
+ }
87
+ }
88
],
89
footerSide: () => !updates && pauseButton,
90
actions: ({ row, id }) => updates ? [
dev-plugins.md
+5
-2
@@ -93,8 +93,10 @@ All the following properties are optional unless otherwise specified.
93
Note that in this example we are pointing to a github repo just for clarity. You are not supposed to use this
94
complicated object form to link github, use the string form.
95
Plugins with custom repos are not included in search results, but the update feature will still work.
96
+- `changelog: { version: number, message: string }[]` the UI will show only entries with version greater than currently installed.
97
+ You can use `md` syntax for the message.
98
97
-WARNING: All the properties above are a bit special and must go in `exports` only (thus, not returned in `init`) and the syntax
99
+**WARNING:** All the properties above are a bit special and must go in `exports` only (thus, not returned in `init`) and the syntax
100
used must be strictly JSON (thus, no single quotes, only double quotes for strings and objects), and must fit one line.
101
102
- `init: (api: object) => void | object | function` described in the previous section. If an object is returned,
@@ -780,7 +782,8 @@ If you want to override a text regardless of the language, use the special langu
782
## API version history
783
784
- 12.0 (v0.57.0)
783
- - backend event: finalizingLogin, httpsServerOptions
785
+ - backend event: finalizingLogin, httpsServerOptions
786
+ - exports.changelog
787
- 11.6 (v0.56.0)
788
- api.setError
789
- frontend events: afterBreadcrumbs, afterFolderStats, afterFilter
src/api.plugins.ts
+1
@@ -53,6 +53,7 @@ const apis: ApiHandlers = {
53
if (online.version === disk.version) return // different, not just newer ones, in case a version was retired
54
list.add(Object.assign(online, {
55
id: disk.id, // id is installation-dependant, and online cannot know
56
+ installedVersion: disk.version,
57
repo: serialize(disk).repo, // show the user the current repo we are getting this update from, not a possibly-changed future one
58
downgrade: online.version! < disk.version,
59
downloading: _.isString(online.repo) && downloading[online.repo],
src/plugins.ts
+3
-1
@@ -371,6 +371,7 @@ export interface CommonPluginInterface {
371
depend?: Depend
372
isTheme?: boolean | 'light' | 'dark'
373
preview?: string | string[]
374
+ changelog?: unknown
375
}
376
export interface AvailablePlugin extends CommonPluginInterface {
377
branch?: string
@@ -640,9 +641,10 @@ export function parsePluginSource(id: string, source: string) {
641
pl.apiRequired = tryJson(/exports.apiRequired *= *([ \d.,[\]]+)/.exec(source)?.[1]) ?? undefined
642
pl.isTheme = tryJson(/exports.isTheme *= *(true|false|"light"|"dark")/.exec(source)?.[1]) ?? (id.endsWith('-theme') || undefined)
643
pl.preview = tryJson(/exports.preview *= *(.+)/.exec(source)?.[1]) ?? undefined
643
- pl.depend = tryJson(/exports.depend *= *(\[.*])/m.exec(source)?.[1])?.filter((x: any) =>
644
+ pl.depend = tryJson(/exports.depend *= *(\[[\s\S]*?])/m.exec(source)?.[1])?.filter((x: any) =>
645
typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
646
|| console.warn("plugin dependency discarded", x) )
647
+ pl.changelog = tryJson(/exports.changelog *= *(\[[\s\S]*?])/m.exec(source)?.[1])
648
if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(_.isFinite))) // validate [from,to] form
649
pl.apiRequired = undefined
650
calculateBadApi(pl)