Public Access
feat(hint): targets, automatic hints and formatting
Target fields on the admin hints page. The dashboard shows the four automatic hints after admin hints when their condition is true, using data Core already serves. HintText renders **bold** and safe links (app path or https) as React elements, never HTML, in hints and news. Auto-close without tick hides for this session, X for 7 days, tick + X for good.
This commit is contained in:
1 parent
ae904d946a
commit
8598024bce
14 files changed
+578
-53
No files matched your search
@@ -0,0 +1,42 @@
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
// **bold** and [text](url) only. No nesting: the link text is plain.
|
||||
const TOKEN = /\*\*([^*\n]+)\*\*|\[([^\]\n]+)\]\(([^)\s]+)\)/g;
|
||||
// An app path (not //host, and no backslash: browsers read /\host as //host), or https. Case-sensitive on purpose.
|
||||
const APP_PATH = /^\/(?![/\\])[^\s\\]*$/;
|
||||
const HTTPS = /^https:\/\/[^\s\\]+$/;
|
||||
|
||||
/**
|
||||
* @returns {{to: string} | {href: string} | null} null: the url is not safe, so the link stays plain text
|
||||
*/
|
||||
export const safeLink = (url) => {
|
||||
if (APP_PATH.test(url)) return {to: url};
|
||||
if (HTTPS.test(url)) return {href: url};
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hint and news text with **bold** and [text](url). Only React elements, never HTML: React escapes every string.
|
||||
*/
|
||||
export default function HintText({text}) {
|
||||
const parts = [];
|
||||
let last = 0;
|
||||
for (const match of text.matchAll(TOKEN)) {
|
||||
const [whole, bold, label, url] = match;
|
||||
if (match.index > last) parts.push(text.slice(last, match.index));
|
||||
last = match.index + whole.length;
|
||||
const link = url && safeLink(url);
|
||||
if (bold !== undefined) {
|
||||
parts.push(<strong key={match.index}>{bold}</strong>);
|
||||
} else if (link?.to) {
|
||||
parts.push(<Link key={match.index} to={link.to} className="underline">{label}</Link>);
|
||||
} else if (link) {
|
||||
parts.push(<a key={match.index} href={link.href} target="_blank" rel="noopener noreferrer"
|
||||
className="underline">{label}</a>);
|
||||
} else {
|
||||
parts.push(whole);
|
||||
}
|
||||
}
|
||||
parts.push(text.slice(last));
|
||||
return <>{parts}</>;
|
||||
}
|
||||
Reference in new issue
Block a user