diff --git a/packages/ui-default/pages/contest.page.ts b/packages/ui-default/pages/contest.page.ts index 5ba31dd7a..112fb4866 100644 --- a/packages/ui-default/pages/contest.page.ts +++ b/packages/ui-default/pages/contest.page.ts @@ -1,7 +1,7 @@ import { formatSeconds } from '@hydrooj/utils/lib/common'; import NProgress from 'nprogress'; import { NamedPage } from 'vj/misc/Page'; -import { tpl } from 'vj/utils'; +import { addSpeculationRules, tpl } from 'vj/utils'; const contestTimer = $(tpl``); contestTimer.appendTo(document.body); @@ -21,4 +21,15 @@ export default new NamedPage(['contest_detail', 'contest_problemlist', 'contest_ NProgress.start(); updateProgress(); setInterval(updateProgress, 1000); + + addSpeculationRules({ + prerender: [{ + 'where': { + 'or': [ + { 'href_matches': '/p/*' }, + { 'href_matches': '/d/*/p/*' }, + ], + }, + }], + }); }); diff --git a/packages/ui-default/pages/problem_main.page.ts b/packages/ui-default/pages/problem_main.page.ts index 249cd5b37..fc23662fc 100644 --- a/packages/ui-default/pages/problem_main.page.ts +++ b/packages/ui-default/pages/problem_main.page.ts @@ -9,7 +9,7 @@ import Notification from 'vj/components/notification'; import { downloadProblemSet } from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; import { - delay, i18n, pjax, request, tpl, + addSpeculationRules, delay, i18n, pjax, request, tpl, } from 'vj/utils'; const list = []; @@ -343,6 +343,17 @@ const page = new NamedPage(['problem_main'], () => { }); $(document).on('vjContentNew', (e) => processElement(e.target)); processElement(document); + + addSpeculationRules({ + prerender: [{ + 'where': { + 'or': [ + { 'href_matches': '/p/*' }, + { 'href_matches': '/d/*/p/*' }, + ], + }, + }], + }); }); export default page; diff --git a/packages/ui-default/utils/base.ts b/packages/ui-default/utils/base.ts index c2b9e3169..00ff79f55 100644 --- a/packages/ui-default/utils/base.ts +++ b/packages/ui-default/utils/base.ts @@ -2,7 +2,6 @@ import $ from 'jquery'; import _ from 'lodash'; import React from 'react'; import ReactDOM from 'react-dom/client'; -import DOMServer from 'react-dom/server'; export function substitute(str: string, obj: any) { return str.replace(/\{([^{}]+)\}/g, (match, key) => { @@ -38,12 +37,13 @@ export function tpl(node: React.ReactNode, reactive?: export function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]): string; export function tpl(pieces: TemplateStringsArray | React.ReactNode, ...substitutions: Substitution[] | boolean[]) { if (React.isValidElement(pieces)) { - if (substitutions[0]) { - const div = document.createElement('div'); - ReactDOM.createRoot(div).render(pieces); - return div; - } - return DOMServer.renderToStaticMarkup(pieces); + const div = document.createElement('div'); + const root = ReactDOM.createRoot(div); + root.render(pieces); + if (substitutions[0]) return div; + const html = div.innerHTML; + root.unmount(); + return html; } let result = pieces[0]; for (let i = 0; i < substitutions.length; ++i) { @@ -81,6 +81,15 @@ export const zIndexManager = { }, }; +export function addSpeculationRules(rules) { + if (HTMLScriptElement.supports?.('speculationrules')) { + const specScript = document.createElement('script'); + specScript.type = 'speculationrules'; + specScript.textContent = JSON.stringify(rules); + document.body.append(specScript); + } +} + export const request = { ajax(options: Record) { const stack = new Error().stack;