admin/home: display #N as link in updates' text
Massimo Melina committed
Jul 22, 2023 at 13:38 UTC
60c1ab61eedaf75f3e5320ce9370ea902319a112
2 files changed
+29
-23
admin/src/HomePage.ts
+9
-3
@@ -16,8 +16,8 @@ import {
16
wikiLink,
17
with_
18
} from './misc'
19
-import { BrowserUpdated as UpdateIcon, CheckCircle, Error, Info, Launch, Warning } from '@mui/icons-material'
20
-import md from './md'
19
+import { BrowserUpdated as UpdateIcon, CheckCircle, Error, Info, Launch, OpenInNew, Warning } from '@mui/icons-material'
20
+import md, { replaceStringToReact } from './md'
21
import { state, useSnapState } from './state'
22
import { alertDialog, confirmDialog, toast } from './dialog'
23
import { isCertError, isKeyError, makeCertAndSave } from './OptionsPage'
@@ -122,11 +122,17 @@ export default function HomePage() {
122
...!x.isNewer && { color: 'warning', variant: 'outlined' },
123
onClick: () => update(x.tag_name)
124
}, prefix("Install ", x.name, x.isNewer ? '' : " (older)")),
125
- h(Box, { mt: 1 }, md(x.body, { linkTarget: '_blank' }))
125
+ h(Box, { mt: 1 }, md(x.body, { onText, linkTarget: '_blank' }))
126
)),
127
)),
128
))
129
)
130
+
131
+ function onText(s: string) {
132
+ return replaceStringToReact(s, /(?<=^|\W)#(\d+)\b/g, m => // link issues
133
+ h(Link, { href: REPO_URL + 'issues/' + m[1], target: '_blank' },
134
+ h(OpenInNew, { sx: { verticalAlign: 'bottom' } }) ))
135
+ }
136
}
137
138
async function update(tag?: string) {
admin/src/md.ts
+20
-20
@@ -1,34 +1,34 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
-import { createElement as h, Fragment } from 'react'
3
+import { createElement as h, Fragment, ReactNode } from 'react'
4
import { Link } from '@mui/material'
5
6
+const TAGS = {
7
+ '`': 'code',
8
+ '*': 'b',
9
+ '/': 'i',
10
+ '_': 'u',
11
+}
12
// markdown inspired syntax to transform text into react elements: * for bold, / for italic, _ for underline, ` for code
7
-export default function md(text: string | TemplateStringsArray, { linkTarget='' }={}) {
13
+type OnText = (s: string) => ReactNode
14
+export default function md(text: string | TemplateStringsArray, { linkTarget='', onText=(x=>x) as OnText }={}) {
15
if (typeof text !== 'string')
16
text = text[0]
10
- const re = /([`*/_])(.+)\1|(\n)|\[(.+)\]\((.+)\)/g
17
+ return replaceStringToReact(text, /([`*/_])(.+)\1|(\n)|\[(.+)\]\((.+)\)/g, m =>
18
+ m[4] ? h(Link, { href: m[5], target: linkTarget }, onText(m[4]))
19
+ : m[3] ? h('br')
20
+ : h((TAGS as any)[ m[1] ], {}, onText(m[2])),
21
+ onText)
22
+}
23
+
24
+export function replaceStringToReact(text: string, re: RegExp, cb: (match: RegExpExecArray) => ReactNode, onText=(x=>x) as OnText ) {
25
const res = []
26
let last = 0
27
let match
28
while (match = re.exec(text)) { //eslint-disable-line no-cond-assign
15
- res.push( text.slice(last, match.index) )
16
- if (match[4])
17
- res.push(h(Link, { href: match[5], target: linkTarget }, match[4]))
18
- else if (match[3])
19
- res.push(h('br'))
20
- else {
21
- const tag = ({
22
- '`': 'code',
23
- '*': 'b',
24
- '/': 'i',
25
- '_': 'u',
26
- })[ match[1] ]
27
- if (!tag)
28
- throw Error("should never happen")
29
- res.push( h(tag,{}, match[2]) )
30
- }
29
+ res.push(onText(text.slice(last, match.index)))
30
+ res.push(cb(match))
31
last = match.index + match[0].length
32
}
33
- return h(Fragment, {}, ...res, text.slice(last, Infinity))
33
+ return h(Fragment, {}, ...res, onText(text.slice(last, Infinity)))
34
}