diff --git a/docs/development.md b/docs/development.md index e1cccf8..af16bee 100644 --- a/docs/development.md +++ b/docs/development.md @@ -90,7 +90,7 @@ Default font resources are checked in; ordinary builds require no download or co python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip ``` -The script generates WOFF2 files and `maple-font.css` while retaining the upstream license. Code blocks use highlight.js with fixed Atom One Dark; theme changes must not reintroduce previous highlighters or color schemes. +The script generates WOFF2 files and `maple-font.css` while retaining the upstream license; `--css-only` regenerates declarations without rebuilding font binaries. Fonts use swap rendering, so downloads never hide the page. After first paint, regular Maple chunks (about 6.72 MB) load in batches of two; other weights load on demand. PWA installation skips font files and caches public build fonts after use. Code blocks use highlight.js with fixed Atom One Dark; theme changes must not reintroduce previous highlighters or color schemes. Plugin SDK types are in [common/plugin-api/index.d.ts](../web/src/common/plugin-api/index.d.ts); loading and lifecycle code lives in `common/plugins/`. Plugins run only on the frontend, and installers must trust their code. Namespaces, disabling, and resource versioning do not constitute a permission sandbox. Editor-extension failures preserve document contents and disable the relevant extensions on the current page. diff --git a/scripts/prepare-maple-font.py b/scripts/prepare-maple-font.py index 380bd57..6f4d997 100644 --- a/scripts/prepare-maple-font.py +++ b/scripts/prepare-maple-font.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Rebuild local fonts from the specified v7.9 ZIP: python3 scripts/prepare-maple-font.py /path/to/MapleMonoNormalNL-NF-CN.zip. +Use --css-only to regenerate declarations from checked-in chunks. Only font regeneration requires fonttools[woff]; application builds use the checked-in WOFF2 files. Source: https://github.com/subframe7536/maple-font/releases/tag/v7.9 """ @@ -8,31 +9,37 @@ from pathlib import Path import sys import zipfile -from fontTools import subset -from fontTools.ttLib import TTFont - root = Path(__file__).resolve().parents[1] / 'web/src/common/appearance' output = root / 'fonts/maple' output.mkdir(exist_ok=True) family = 'Maple Mono Normal NL NF CN' +if sys.argv[1:] != ['--css-only']: + from fontTools import subset + from fontTools.ttLib import TTFont + with zipfile.ZipFile(sys.argv[1]) as archive: + (output / 'LICENSE.txt').write_bytes(archive.read('LICENSE.txt')) + for variant, weight, style in [('Regular', 400, 'normal'), ('Bold', 700, 'normal'), ('Italic', 400, 'italic'), ('BoldItalic', 700, 'italic')]: + data = archive.read('MapleMonoNormalNL-NF-CN-' + variant + '.ttf') + source = TTFont(io.BytesIO(data)) + points = sorted(source.getBestCmap()) + # Split by Unicode range so Chinese and Nerd Font glyphs need not download alongside Latin text. + for block in sorted({cp // 4096 for cp in points}): + unicodes = [cp for cp in points if cp // 4096 == block] + font = TTFont(io.BytesIO(data), recalcTimestamp=False) + options = subset.Options() + options.flavor = 'woff2' + subsetter = subset.Subsetter(options=options) + subsetter.populate(unicodes=unicodes) + subsetter.subset(font) + font.flavor = 'woff2' + name = f'{variant.lower()}-{block:02x}.woff2' + font.save(output / name) css = ['/* Maple Mono v7.9; generated by scripts/prepare-maple-font.py. See fonts/maple/LICENSE.txt for licensing. */'] -with zipfile.ZipFile(sys.argv[1]) as archive: - (output / 'LICENSE.txt').write_bytes(archive.read('LICENSE.txt')) - for variant, weight, style in [('Regular', 400, 'normal'), ('Bold', 700, 'normal'), ('Italic', 400, 'italic'), ('BoldItalic', 700, 'italic')]: - data = archive.read('MapleMonoNormalNL-NF-CN-' + variant + '.ttf') - source = TTFont(io.BytesIO(data)) - points = sorted(source.getBestCmap()) - # Split by Unicode range so Chinese and Nerd Font glyphs need not download alongside Latin text. - for block in sorted({cp // 4096 for cp in points}): - unicodes = [cp for cp in points if cp // 4096 == block] - font = TTFont(io.BytesIO(data), recalcTimestamp=False) - options = subset.Options() - options.flavor = 'woff2' - subsetter = subset.Subsetter(options=options) - subsetter.populate(unicodes=unicodes) - subsetter.subset(font) - font.flavor = 'woff2' - name = f'{variant.lower()}-{block:02x}.woff2' - font.save(output / name) - css.append(f"@font-face {{ font-family: '{family}'; font-style: {style}; font-weight: {weight}; font-display: block; src: url('./fonts/maple/{name}') format('woff2'); unicode-range: U+{block * 4096:X}-{(block + 1) * 4096 - 1:X}; }}") +for variant, weight, style in [('regular', 400, 'normal'), ('bold', 700, 'normal'), ('italic', 400, 'italic'), ('bolditalic', 700, 'italic')]: + files = sorted(output.glob(f'{variant}-*.woff2')) + if not files: + raise ValueError(f'Missing checked-in font chunks: {variant}') + for file in files: + block = int(file.stem.split('-')[1], 16) + css.append(f"@font-face {{ font-family: '{family}'; font-style: {style}; font-weight: {weight}; font-display: swap; src: url('./fonts/maple/{file.name}') format('woff2'); unicode-range: U+{block * 4096:X}-{(block + 1) * 4096 - 1:X}; }}") (root / 'maple-font.css').write_text('\n'.join(css) + '\n') diff --git a/web/demo.html b/web/demo.html index 6d91a8d..4bf9043 100644 --- a/web/demo.html +++ b/web/demo.html @@ -1,5 +1,5 @@ - + diff --git a/web/index.html b/web/index.html index 03cf99c..b3f8ead 100644 --- a/web/index.html +++ b/web/index.html @@ -1,5 +1,5 @@ - + diff --git a/web/src/common/appearance/base.css b/web/src/common/appearance/base.css index 1ccf691..39611f2 100644 --- a/web/src/common/appearance/base.css +++ b/web/src/common/appearance/base.css @@ -628,20 +628,18 @@ textarea { } } -/* opacity preserves layout and font requests; hide fallback fonts while waiting, including menus teleported to body. */ -:root:is([data-rm-fonts='loading'], [data-rm-fonts='error']) body > :not(.rm-font-error) { - opacity: 0; - pointer-events: none; -} .rm-font-error { position: fixed; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 24px; + z-index: 10000; + right: 16px; + bottom: 16px; + max-width: min(360px, calc(100vw - 64px)); + padding: 16px; + border: 1px solid var(--rm-divider); + border-radius: 8px; + background: var(--rm-bg); color: var(--rm-text); + box-shadow: var(--rm-shadow); font: 16px/1.5 system-ui, sans-serif; diff --git a/web/src/common/appearance/font-loading.test.ts b/web/src/common/appearance/font-loading.test.ts index e50e816..881348b 100644 --- a/web/src/common/appearance/font-loading.test.ts +++ b/web/src/common/appearance/font-loading.test.ts @@ -2,96 +2,66 @@ import { afterEach, beforeEach, expect, it, vi } from 'vitest' import { watchFontLoading } from './font-loading' +function face(overrides = {}) { + return { family: 'Maple Mono Normal NL NF CN', weight: '400', style: 'normal', status: 'unloaded', load: vi.fn().mockResolvedValue(undefined), ...overrides } +} class Fonts extends EventTarget { - status = 'loading' - faces: { status: string }[] = [] - resolve!: () => void - ready = this.pending() - pending() { - return new Promise((resolve) => { - this.resolve = resolve - }) - } - + faces = [face()]; [Symbol.iterator]() { return this.faces[Symbol.iterator]() } - - finish() { - this.status = 'loaded'; this.resolve() - } - - load() { - this.status = 'loading'; this.ready = this.pending(); this.dispatchEvent(new Event('loading')) - } } let fonts: Fonts let frame: FrameRequestCallback const descriptor = Object.getOwnPropertyDescriptor(document, 'fonts') beforeEach(() => { + vi.useFakeTimers() document.body.innerHTML = '
正文
' fonts = new Fonts() Object.defineProperty(document, 'fonts', { configurable: true, value: fonts }) vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => { frame = callback; return 1 }) - watchFontLoading() }) afterEach(() => { - vi.unstubAllGlobals() - delete document.documentElement.dataset.rmFonts + vi.useRealTimers(); vi.unstubAllGlobals() document.body.innerHTML = '' if (descriptor) Object.defineProperty(document, 'fonts', descriptor) else Reflect.deleteProperty(document, 'fonts') }) - -it('挂载后的字体就绪前保持隐藏,等待结束才显示', async () => { - expect(document.documentElement.dataset.rmFonts).toBe('loading') +it('首屏与后续字体加载均不隐藏或禁用应用,预下载不阻塞首屏', async () => { + watchFontLoading() + expect(fonts.faces[0]!.load).not.toHaveBeenCalled() + fonts.dispatchEvent(new Event('loading')) + expect(document.documentElement.dataset.rmFonts).toBeUndefined() + expect(getComputedStyle(document.getElementById('app')!).pointerEvents).not.toBe('none') frame(0) - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('loading') - fonts.finish() - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('ready') + expect(fonts.faces[0]!.load).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(0) + expect(fonts.faces[0]!.load).toHaveBeenCalledOnce() }) - -it('字体已在缓存中时直接显示,不依赖再次触发 loading 事件', async () => { - fonts.finish() - frame(0) - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('ready') -}) - -it('切换笔记或字形需要新字体时重新等待,不用旧一轮完成信号提前显示', async () => { - frame(0) - fonts.finish() - fonts.load() - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('loading') - fonts.finish() - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('ready') -}) - -it('字体失败显示可刷新提示,之后 ready 完成和其他字体加载不能显示备用字体', async () => { - frame(0) - fonts.dispatchEvent(new Event('loadingerror')) - fonts.dispatchEvent(new Event('loadingerror')) - fonts.finish() - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('error') +it('只预下载常规Maple,每批两个,单片失败仍继续且不重复启动', async () => { + let finish!: () => void + fonts.faces = [face({ family: '\'Maple Mono Normal NL NF CN\'', load: vi.fn(() => new Promise((resolve) => { + finish = resolve + })) }), face({ load: vi.fn().mockRejectedValue(new Error('offline')) }), face(), face({ weight: '700' }), face({ style: 'italic' }), face({ family: 'Other font' })] + watchFontLoading(); frame(0) + await vi.advanceTimersByTimeAsync(0) + expect(fonts.faces[0]!.load).toHaveBeenCalledOnce() + expect(fonts.faces[1]!.load).toHaveBeenCalledOnce() + expect(fonts.faces[2]!.load).not.toHaveBeenCalled() + finish(); await vi.advanceTimersByTimeAsync(0) + expect(fonts.faces[2]!.load).toHaveBeenCalledOnce() + for (const font of fonts.faces.slice(3)) expect(font.load).not.toHaveBeenCalled() + watchFontLoading(); await vi.advanceTimersByTimeAsync(0) + expect(fonts.faces[0]!.load).toHaveBeenCalledOnce() expect(document.querySelectorAll('[role="alert"]')).toHaveLength(1) - expect(document.querySelector('[role="alert"]')!.textContent).toContain('Fonts could not be loaded') +}) +it('字体失败只提示一次,保留页面和刷新重试入口', () => { + watchFontLoading() + fonts.dispatchEvent(new Event('loadingerror')); fonts.dispatchEvent(new Event('loadingerror')) + expect(document.querySelectorAll('[role="alert"]')).toHaveLength(1) + expect(document.querySelector('[role="alert"]')!.textContent).toContain('Using fallback fonts') expect(document.querySelector('[role="alert"] button')!.textContent).toBe('Refresh page') - fonts.load() - expect(document.documentElement.dataset.rmFonts).toBe('error') -}) - -it('开始监听前已有字体失败,也不能被 resolved ready 当作成功', async () => { - fonts.faces = [{ status: 'error' }] - fonts.finish() - frame(0) - await Promise.resolve() - expect(document.documentElement.dataset.rmFonts).toBe('error') - expect(document.querySelector('[role="alert"]')).not.toBeNull() + expect(document.documentElement.dataset.rmFonts).toBeUndefined() }) diff --git a/web/src/common/appearance/font-loading.ts b/web/src/common/appearance/font-loading.ts index a4c430b..35dfdf1 100644 --- a/web/src/common/appearance/font-loading.ts +++ b/web/src/common/appearance/font-loading.ts @@ -1,13 +1,16 @@ import { t as translate } from '@@/i18n/runtime' -// Use the font set requested by actual browser layout, covering initial render, note switches, and later glyphs. -export function watchFontLoading() { - const root = document.documentElement - const fonts = document.fonts - root.dataset.rmFonts = 'loading' +const started = new WeakSet() + +// Fonts may finish after interaction starts; never hide or disable the application. +export function watchFontLoading() { + const fonts = document.fonts + if (started.has(fonts)) return + started.add(fonts) + let reported = false function failed() { - if (root.dataset.rmFonts === 'error') return - root.dataset.rmFonts = 'error' + if (reported) return + reported = true const panel = document.createElement('div') panel.className = 'rm-font-error' panel.setAttribute('role', 'alert') @@ -19,24 +22,20 @@ export function watchFontLoading() { panel.append(message, retry) document.body.append(panel) } - - function ready() { - if (root.dataset.rmFonts === 'error') return - // fonts.ready also resolves after download failure; failure must not count as fonts being ready. - if ([...fonts].some(font => font.status === 'error')) failed() - else if (fonts.status === 'loaded') root.dataset.rmFonts = 'ready' - } - - fonts.addEventListener('loading', () => { - if (root.dataset.rmFonts === 'error') return - root.dataset.rmFonts = 'loading' - void fonts.ready.then(ready) - }) fonts.addEventListener('loadingerror', failed) - // Trigger layout after mounting so the wait covers fonts used by the current text. + async function preload() { + const regular = [...fonts].filter(face => face.family.replace(/['"]/g, '') === 'Maple Mono Normal NL NF CN' && face.style === 'normal' && face.weight === '400') + for (let i = 0; i < regular.length; i += 2) { + const results = await Promise.allSettled(regular.slice(i, i + 2).map(face => face.load())) + if (results.some(result => result.status === 'rejected')) failed() + } + } + // Yield a paint before scheduling background downloads, including in the standalone demo. requestAnimationFrame(() => { - document.body.getBoundingClientRect() - void fonts.ready.then(ready) + setTimeout(() => { + if ([...fonts].some(face => face.status === 'error')) failed() + void preload() + }, 0) }) } diff --git a/web/src/common/appearance/fonts.css b/web/src/common/appearance/fonts.css index aa69da5..29746ca 100644 --- a/web/src/common/appearance/fonts.css +++ b/web/src/common/appearance/fonts.css @@ -3,7 +3,7 @@ font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; - font-display: block; + font-display: swap; src: url(./fonts/6b5e37b52d26eeac.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } @@ -12,7 +12,7 @@ font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; - font-display: block; + font-display: swap; src: url(./fonts/eb83d8d40af4411f.woff2) format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } @@ -21,7 +21,7 @@ font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; - font-display: block; + font-display: swap; src: url(./fonts/99e400330f36ae77.woff2) format('woff2'); unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; } @@ -30,7 +30,7 @@ font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; - font-display: block; + font-display: swap; src: url(./fonts/c4064400db3750ce.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, @@ -41,7 +41,7 @@ font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; - font-display: block; + font-display: swap; src: url(./fonts/b19c2080652a3c2b.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, @@ -52,7 +52,7 @@ font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 600; - font-display: block; + font-display: swap; src: url(./fonts/cc27b30471250057.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, @@ -63,7 +63,7 @@ font-family: 'Manrope'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/842459da81f909f2.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } @@ -72,7 +72,7 @@ font-family: 'Manrope'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/9b2d027172a3481c.woff2) format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } @@ -81,7 +81,7 @@ font-family: 'Manrope'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/ed7393f42d27db3d.woff2) format('woff2'); unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF; } @@ -90,7 +90,7 @@ font-family: 'Manrope'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/1f3ea86e40d947e2.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, @@ -101,7 +101,7 @@ font-family: 'Manrope'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/ee75508933e6fd45.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, @@ -112,7 +112,7 @@ font-family: 'Manrope'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/4872f2a6dc9a8250.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, @@ -122,7 +122,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/7b3ddaeea40a0fe5.woff2) format('woff2'); unicode-range: U+1f1e9-1f1f5, U+1f1f7-1f1ff, U+1f21a, U+1f232, U+1f234-1f237, U+1f250-1f251, U+1f300, U+1f302-1f308, U+1f30a-1f311, @@ -132,7 +132,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/9af011928c669ba6.woff2) format('woff2'); unicode-range: U+fee3, U+fef3, U+ff03-ff04, U+ff07, U+ff0a, U+ff17-ff19, U+ff1c-ff1d, U+ff20-ff3a, U+ff3c, U+ff3e-ff5b, U+ff5d, @@ -144,7 +144,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/5f6c87a981ef2def.woff2) format('woff2'); unicode-range: U+f0a7, U+f0b2, U+f0b7, U+f0c9, U+f0d8, U+f0da, U+f0dc-f0dd, U+f0e0, U+f0e6, U+f0eb, U+f0fc, U+f101, U+f104-f105, @@ -159,7 +159,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/8953b4246f39fce9.woff2) format('woff2'); unicode-range: U+9f83, U+9f85-9f8d, U+9f90-9f91, U+9f94-9f96, U+9f98, U+9f9b-9f9c, U+9f9e, U+9fa0, U+9fa2, U+9ff0-9fff, U+a001, @@ -175,7 +175,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/e55b19a3912a75d1.woff2) format('woff2'); unicode-range: U+9e30-9e33, U+9e35-9e3b, U+9e3e, U+9e40-9e44, U+9e46-9e4e, U+9e51, U+9e53, U+9e55-9e58, U+9e5a-9e5c, U+9e5e-9e63, @@ -190,7 +190,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/935e2cf08e92673e.woff2) format('woff2'); unicode-range: U+9c82-9c83, U+9c85-9c8c, U+9c8e-9c92, U+9c94-9c9b, U+9c9e-9ca3, U+9ca5-9ca7, U+9ca9, U+9cab, U+9cad-9cae, @@ -204,7 +204,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b5ae0757f6ecaa47.woff2) format('woff2'); unicode-range: U+9a80, U+9a83, U+9a85, U+9a88-9a8a, U+9a8d-9a8e, U+9a90, U+9a92-9a93, U+9a95-9a96, U+9a98-9a99, U+9a9b-9aa2, @@ -220,7 +220,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/4e47f619496a388f.woff2) format('woff2'); unicode-range: U+98dd, U+98e1-98e2, U+98e7-98ea, U+98ec, U+98ee-98ef, U+98f2, U+98f4, U+98fc-98fe, U+9903, U+9905, U+9908, U+990a, @@ -236,7 +236,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/3bc0783456e12fcc.woff2) format('woff2'); unicode-range: U+975b-975c, U+9763, U+9765-9766, U+976c-976d, U+9773, U+9776, U+977a, U+977c, U+9784-9785, U+978e-978f, @@ -252,7 +252,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/4168a698bef8858e.woff2) format('woff2'); unicode-range: U+95c4-95ca, U+95cc-95cd, U+95d4-95d6, U+95d8, U+95e1-95e2, U+95e9, U+95f0-95f1, U+95f3, U+95f6, U+95fc, @@ -268,7 +268,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/7a0b612893faf498.woff2) format('woff2'); unicode-range: U+94f5, U+94f7, U+94f9, U+94fb-94fd, U+94ff, U+9503-9504, U+9506-9507, U+9509-950a, U+950d-950f, U+9511-9518, @@ -280,7 +280,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/95d37f56ac503320.woff2) format('woff2'); unicode-range: U+941c-942b, U+942d-942e, U+9432-9433, U+9435, U+9438, U+943a, U+943e, U+9444, U+944a, U+9451-9452, U+945a, @@ -291,7 +291,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/7e74d2ffc02d78d0.woff2) format('woff2'); unicode-range: U+92ec-92ed, U+92f0, U+92f3, U+92f8, U+92fc, U+9304, U+9306, U+9310, U+9312, U+9315, U+9318, U+931a, U+931e, @@ -304,7 +304,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/70a0301ffa863394.woff2) format('woff2'); unicode-range: U+9163-9164, U+9169, U+9170, U+9172, U+9174, U+9179-917a, U+917d-917e, U+9182-9183, U+9185, U+918c-918d, @@ -320,7 +320,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f65e41fe796f55cb.woff2) format('woff2'); unicode-range: U+9004, U+900b, U+9011, U+9015-9016, U+901e, U+9021, U+9026, U+902d, U+902f, U+9031, U+9035-9036, U+9039-903a, @@ -336,7 +336,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/1a993e7ab856f9c8.woff2) format('woff2'); unicode-range: U+8e41-8e42, U+8e47, U+8e49-8e4b, U+8e50-8e53, U+8e59-8e5a, U+8e5f-8e60, U+8e64, U+8e69, U+8e6c, U+8e70, U+8e74, @@ -352,7 +352,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/50a18da9b4927dbb.woff2) format('woff2'); unicode-range: U+8cbd, U+8cbf-8cc4, U+8cc7-8cc8, U+8cca, U+8ccd, U+8cd1, U+8cd3, U+8cdb-8cdc, U+8cde, U+8ce0, U+8ce2-8ce4, @@ -368,7 +368,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/a806fd74a8216e5f.woff2) format('woff2'); unicode-range: U+8b80, U+8b83, U+8b8a, U+8b8c, U+8b90, U+8b93, U+8b99-8b9a, U+8ba0, U+8ba3, U+8ba5-8ba7, U+8baa-8bac, U+8bb4-8bb5, @@ -383,7 +383,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/78bd32786a1b31ff.woff2) format('woff2'); unicode-range: U+8a15-8a18, U+8a1a-8a1b, U+8a1d, U+8a1f, U+8a22-8a23, U+8a25, U+8a2b, U+8a2d, U+8a31, U+8a33-8a34, U+8a36-8a38, @@ -399,7 +399,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f44e78d8ce83767d.woff2) format('woff2'); unicode-range: U+8882, U+8884-8886, U+8888, U+888f, U+8892-8893, U+889b, U+88a2, U+88a4, U+88a6, U+88a8, U+88aa, U+88ae, U+88b1, @@ -415,7 +415,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/63e1e25c88076e71.woff2) format('woff2'); unicode-range: U+86f4, U+86f8-86f9, U+86fb, U+86fe, U+8703, U+8706-870a, U+870d, U+8711-8713, U+871a, U+871e, U+8722-8723, U+8725, @@ -431,7 +431,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/badb08d4ed31aab3.woff2) format('woff2'); unicode-range: U+8548, U+854e, U+8553, U+8556-8557, U+8559, U+855e, U+8561, U+8564-8565, U+8568-856a, U+856d, U+856f-8570, U+8572, @@ -447,7 +447,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/9d0550296822bc88.woff2) format('woff2'); unicode-range: U+83c5, U+83c8-83c9, U+83cb, U+83d1, U+83d3-83d6, U+83d8, U+83db, U+83dd, U+83df, U+83e1, U+83e5, U+83ea-83eb, @@ -464,7 +464,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/ea65d613de6262b7.woff2) format('woff2'); unicode-range: U+82bc, U+82be, U+82c0-82c2, U+82c4-82c8, U+82ca-82cc, U+82ce, U+82d0, U+82d2-82d3, U+82d5-82d6, U+82d8-82d9, @@ -479,7 +479,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f624e6edcc42557f.woff2) format('woff2'); unicode-range: U+8166-8169, U+816b, U+816d, U+8171, U+8173-8174, U+8178, U+817c-817d, U+8182, U+8188, U+8191, U+8198-819b, U+81a0, @@ -495,7 +495,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/af97bfb909af51ae.woff2) format('woff2'); unicode-range: U+8016, U+8018-8019, U+801c, U+801e, U+8026-802a, U+8031, U+8034-8035, U+8037, U+8043, U+804b, U+804d, U+8052, @@ -510,7 +510,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/84a684f5b97032b6.woff2) format('woff2'); unicode-range: U+7ef0-7ef2, U+7ef6, U+7efa-7efb, U+7efe, U+7f01-7f04, U+7f08, U+7f0a-7f12, U+7f17, U+7f19, U+7f1b-7f1c, U+7f1f, @@ -525,7 +525,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/4af13061394e55b3.woff2) format('woff2'); unicode-range: U+7dd2, U+7dd4, U+7dd6-7dd8, U+7dda-7de0, U+7de2-7de6, U+7de8-7ded, U+7def, U+7df1-7df5, U+7df7, U+7df9, @@ -540,7 +540,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/a3739966a326e377.woff2) format('woff2'); unicode-range: U+7ce8, U+7cec, U+7cf0, U+7cf5-7cf9, U+7cfc, U+7cfe, U+7d00, U+7d04-7d0b, U+7d0d, U+7d10-7d14, U+7d17-7d19, @@ -554,7 +554,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/6c7df51c2d3405f8.woff2) format('woff2'); unicode-range: U+7bd3-7bd4, U+7bd9-7bda, U+7bdd, U+7be0-7be1, U+7be4-7be6, U+7be9-7bea, U+7bef, U+7bf4, U+7bf6, U+7bfc, U+7bfe, @@ -568,7 +568,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/a8901603a8f60b5c.woff2) format('woff2'); unicode-range: U+7ae6, U+7af4-7af7, U+7afa-7afb, U+7afd-7b0a, U+7b0c, U+7b0e-7b0f, U+7b13, U+7b15-7b16, U+7b18-7b19, U+7b1e-7b20, @@ -581,7 +581,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/62d567322492159f.woff2) format('woff2'); unicode-range: U+798b-798e, U+7992, U+7994-7995, U+7997-7998, U+799a-799c, U+799f, U+79a3-79a6, U+79a8-79ac, U+79ae-79b1, @@ -597,7 +597,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/4ca36ec0898d9f4b.woff2) format('woff2'); unicode-range: U+784c, U+784e-7854, U+7856-7857, U+7859-785a, U+7865, U+7869-786a, U+786d, U+786f, U+7876-7877, U+787c, @@ -613,7 +613,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/dc00089f866f9369.woff2) format('woff2'); unicode-range: U+7722, U+7726, U+7728, U+772b-7730, U+7732-7736, U+7739-773a, U+773d-773f, U+7743, U+7746-7747, U+774c-774f, @@ -628,7 +628,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b14a1ed0396f00cf.woff2) format('woff2'); unicode-range: U+7613-7619, U+761b-761d, U+761f-7622, U+7625, U+7627-762a, U+762e-7630, U+7632-7635, U+7638-763a, U+763c-763d, @@ -643,7 +643,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/beed123d60cc6155.woff2) format('woff2'); unicode-range: U+750d, U+750f, U+7511, U+7513, U+7515, U+7517, U+7519, U+7521-7527, U+752a, U+752c-752d, U+752f, U+7534, U+7536, @@ -658,7 +658,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/9fcc77355f5a0d52.woff2) format('woff2'); unicode-range: U+73f0, U+73f2, U+73f4-73f5, U+73f7, U+73f9-73fa, U+73fc-73fd, U+73ff-7402, U+7404, U+7407-7408, U+740a-740f, @@ -673,7 +673,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/500793f7237d7337.woff2) format('woff2'); unicode-range: U+72e6, U+72e8, U+72ef-72f0, U+72f2-72f4, U+72f6-72f7, U+72f9-72fb, U+72fd, U+7300-7304, U+7307, U+730a-730c, @@ -688,7 +688,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/c4c44fadfc63ac54.woff2) format('woff2'); unicode-range: U+71a8, U+71af, U+71b1-71bc, U+71be, U+71c1-71c2, U+71c4, U+71c8-71cb, U+71ce-71d0, U+71d2, U+71d4, U+71d9-71da, @@ -703,7 +703,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/cf9cd646bc0d0f19.woff2) format('woff2'); unicode-range: U+700b, U+700d, U+7015, U+7018, U+701b, U+701d-701f, U+7023, U+7026-7028, U+702c, U+702e-7030, U+7035, U+7037, @@ -720,7 +720,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/620f5f28321be052.woff2) format('woff2'); unicode-range: U+6ed9, U+6edb, U+6edd, U+6edf-6ee0, U+6ee2, U+6ee6, U+6eea, U+6eec, U+6eee-6eef, U+6ef2-6ef3, U+6ef7-6efa, U+6efe, @@ -735,7 +735,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/658210c7f683a557.woff2) format('woff2'); unicode-range: U+6dc3, U+6dc5-6dc6, U+6dc9, U+6dcc, U+6dcf, U+6dd2-6dd3, U+6dd6, U+6dd9-6dde, U+6de0, U+6de4, U+6de6, U+6de8-6dea, @@ -750,7 +750,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/795b87224e8dcbc4.woff2) format('woff2'); unicode-range: U+6cb1-6cb2, U+6cb4-6cb5, U+6cb7, U+6cba, U+6cbc-6cbd, U+6cc1-6cc3, U+6cc5-6cc7, U+6cd0-6cd4, U+6cd6-6cd7, @@ -765,7 +765,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/e9d83a6fe76066e1.woff2) format('woff2'); unicode-range: U+6b83-6b86, U+6b89, U+6b8d, U+6b91-6b93, U+6b95, U+6b97-6b98, U+6b9a-6b9b, U+6b9e, U+6ba1-6ba4, U+6ba9-6baa, @@ -780,7 +780,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/df78bcc0618fdcb2.woff2) format('woff2'); unicode-range: U+69fe-6a01, U+6a06, U+6a09, U+6a0b, U+6a11, U+6a13, U+6a17-6a19, U+6a1b, U+6a1e, U+6a23, U+6a28-6a29, U+6a2b, @@ -796,7 +796,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/cf0b566ed87b1702.woff2) format('woff2'); unicode-range: U+68d3, U+68d7, U+68dd, U+68df, U+68e1, U+68e3-68e4, U+68e6-68ed, U+68ef-68f0, U+68f2, U+68f4, U+68f6-68f7, U+68f9, @@ -811,7 +811,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b4bf867aff4a6bde.woff2) format('woff2'); unicode-range: U+678b-678d, U+678f, U+6792-6793, U+6796, U+6798, U+679e-67a1, U+67a5, U+67a7-67a9, U+67ac-67ad, U+67b0-67b1, @@ -827,7 +827,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/23f09a3fd29c803a.woff2) format('woff2'); unicode-range: U+6631, U+6633-6634, U+6636, U+663a-663b, U+663d, U+6641, U+6644-6645, U+6649, U+664c, U+664f, U+6654, U+6659, @@ -843,7 +843,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/e110453047bfe034.woff2) format('woff2'); unicode-range: U+64f1-64f2, U+64f4, U+64f7-64f8, U+64fa, U+64fc, U+64fe-64ff, U+6503, U+6509, U+650f, U+6514, U+6518, U+651c-651e, @@ -859,7 +859,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b7668e18fe9cce6c.woff2) format('woff2'); unicode-range: U+63b8-63bc, U+63be, U+63c0, U+63c3-63c4, U+63c6, U+63c8, U+63cd-63ce, U+63d1, U+63d6, U+63da-63db, U+63de, U+63e0, @@ -874,7 +874,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/2306716a58a58a05.woff2) format('woff2'); unicode-range: U+622c, U+622e-6230, U+6232, U+6238, U+623b, U+623d-623e, U+6243, U+6246, U+6248-6249, U+624c, U+6255, U+6259, @@ -890,7 +890,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b37a954f47c08e8e.woff2) format('woff2'); unicode-range: U+60ed-60ee, U+60f0-60f1, U+60f4, U+60f6, U+60fa, U+6100, U+6106, U+610d-610e, U+6112, U+6114-6115, U+6119, U+611c, @@ -905,7 +905,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/41d1d19f7bebcf26.woff2) format('woff2'); unicode-range: U+5fc4, U+5fc9, U+5fcb, U+5fce-5fd6, U+5fda-5fde, U+5fe1-5fe2, U+5fe4-5fe5, U+5fea, U+5fed-5fee, U+5ff1-5ff3, @@ -920,7 +920,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/3c66c61b08f9dc28.woff2) format('woff2'); unicode-range: U+5e98, U+5e9b, U+5e9d, U+5ea0-5ea5, U+5ea8, U+5eab, U+5eaf, U+5eb3, U+5eb5-5eb6, U+5eb9, U+5ebe, U+5ec1-5ec3, @@ -935,7 +935,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/8a51ce32a2d6a2b0.woff2) format('woff2'); unicode-range: U+5d26-5d27, U+5d2e-5d34, U+5d3c-5d3e, U+5d41-5d44, U+5d46-5d48, U+5d4a-5d4b, U+5d4e, U+5d50, U+5d52, U+5d55-5d58, @@ -950,7 +950,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/ffa6ae4b9eb5ffdc.woff2) format('woff2'); unicode-range: U+5bec, U+5bee-5bf0, U+5bf2-5bf3, U+5bf5-5bf6, U+5bfe, U+5c02-5c03, U+5c05, U+5c07-5c09, U+5c0b-5c0c, U+5c0e, @@ -965,7 +965,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/96088f438d29f54b.woff2) format('woff2'); unicode-range: U+5aa0, U+5aa3-5aa4, U+5aaa, U+5aae-5aaf, U+5ab1-5ab2, U+5ab4-5ab5, U+5ab7-5aba, U+5abd-5abf, U+5ac3-5ac4, @@ -980,7 +980,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/e0d8fdb665a16cd2.woff2) format('woff2'); unicode-range: U+596a, U+596c-596e, U+5977, U+597b-597c, U+5981, U+598f, U+5997-5998, U+599a, U+599c-599d, U+59a0-59a1, @@ -995,7 +995,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/20f6ba8c0c269b1a.woff2) format('woff2'); unicode-range: U+5820, U+5822-5823, U+5825-5826, U+582c, U+582f, U+5831, U+583a, U+583d, U+583f-5842, U+5844-5846, U+5848, U+584a, @@ -1011,7 +1011,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/847818b9c19663fd.woff2) format('woff2'); unicode-range: U+56f9, U+56fc, U+56ff-5700, U+5703-5704, U+5709-570a, U+570c-570d, U+570f, U+5712-5713, U+5718-5719, U+571c, @@ -1026,7 +1026,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/a7c81fbdd8cc5eb5.woff2) format('woff2'); unicode-range: U+55f5-55f7, U+55fb, U+55fe, U+5600-5601, U+5605-5606, U+5608, U+560c-560d, U+560f, U+5614, U+5616-5617, U+561a, @@ -1040,7 +1040,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/23acd90334da40b5.woff2) format('woff2'); unicode-range: U+550f, U+5511-5514, U+5516-5517, U+5519, U+551b, U+551d-551e, U+5520, U+5522-5523, U+5526-5527, U+552a-552c, @@ -1054,7 +1054,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/aebf42c37a635878.woff2) format('woff2'); unicode-range: U+53e7-53e9, U+53f1, U+53f4-53f5, U+53fa-5400, U+5402, U+5405-5407, U+540b, U+540f, U+5412, U+5414, U+5416, @@ -1069,7 +1069,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/c08b4b4d95cfb2ea.woff2) format('woff2'); unicode-range: U+5289, U+528b, U+528d, U+528f, U+5291-5293, U+529a, U+52a2, U+52a6-52a7, U+52ac-52ad, U+52af, U+52b4-52b5, U+52b9, @@ -1085,7 +1085,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/5b4fc0b27a8a1464.woff2) format('woff2'); unicode-range: U+5104, U+5106-5107, U+5109-510b, U+510d, U+510f-5110, U+5113, U+5115, U+5117-5118, U+511a-511c, U+511e-511f, @@ -1101,7 +1101,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/14213d7fbd6ab158.woff2) format('woff2'); unicode-range: U+4fd1, U+4fd3, U+4fda-4fdc, U+4fdf-4fe0, U+4fe2-4fe4, U+4fe6, U+4fe8, U+4feb-4fed, U+4ff3, U+4ff5-4ff6, U+4ff8, @@ -1116,7 +1116,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/3ff1abbbffc86b31.woff2) format('woff2'); unicode-range: U+4ea3, U+4ea5, U+4eb0-4eb1, U+4eb3-4eb6, U+4eb8-4eb9, U+4ebb-4ebe, U+4ec2-4ec4, U+4ec8-4ec9, U+4ecc, U+4ecf-4ed0, @@ -1131,7 +1131,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/5e6011e3010d50bd.woff2) format('woff2'); unicode-range: U+3129, U+3131, U+3134, U+3137, U+3139, U+3141-3142, U+3145, U+3147-3148, U+314b, U+314d-314e, U+315c, U+3160-3161, @@ -1147,7 +1147,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/65a11622c84af2c7.woff2) format('woff2'); unicode-range: U+279f-27a2, U+27a4-27a5, U+27a8, U+27b0, U+27b2-27b3, U+27b9, U+27e8-27e9, U+27f6, U+2800, U+28ec, U+2913, @@ -1162,7 +1162,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/6d4e9520e29f6d02.woff2) format('woff2'); unicode-range: U+2651-2655, U+2658, U+265a-265b, U+265d-265e, U+2660-266d, U+266f, U+267b, U+2688, U+2693-2696, U+2698-2699, @@ -1175,7 +1175,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/497141c2daba02b0.woff2) format('woff2'); unicode-range: U+2550-2551, U+2554, U+2557, U+255a-255b, U+255d, U+255f-2560, U+2562-2563, U+2565-2567, U+2569-256a, U+256c-2572, @@ -1187,7 +1187,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/eb4969d421026bd0.woff2) format('woff2'); unicode-range: U+23f0, U+23f3, U+2445, U+2449, U+2465-2471, U+2474-249b, U+24b8, U+24c2, U+24c7, U+24c9, U+24d0, U+24d2, U+24d4, @@ -1198,7 +1198,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/48c235f25cd265a1.woff2) format('woff2'); unicode-range: U+207c-2083, U+208c-208e, U+2092, U+20a6, U+20a8-20ad, U+20af, U+20b1, U+20b4-20b5, U+20b8-20ba, U+20bd, U+20db, @@ -1213,7 +1213,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/0a8cf734a0b6b153.woff2) format('woff2'); unicode-range: U+1d34-1d35, U+1d38-1d3a, U+1d3c, U+1d3f-1d40, U+1d49, U+1d4e-1d4f, U+1d52, U+1d55, U+1d5b, U+1d5e, U+1d9c, U+1da0, @@ -1227,7 +1227,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b42723fe53599b90.woff2) format('woff2'); unicode-range: U+2ae-2b3, U+2b5-2bf, U+2c2-2c3, U+2c6-2d1, U+2d8-2da, U+2dc, U+2e1-2e3, U+2e5, U+2eb, U+2ee-2f0, U+2f2-2f7, @@ -1239,7 +1239,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/05a7d600a249b837.woff2) format('woff2'); unicode-range: U+176-17f, U+192, U+194, U+19a-19b, U+19d, U+1a0-1a1, U+1a3-1a4, U+1aa, U+1ac-1ad, U+1af-1bf, U+1d2, U+1d4, U+1d6, @@ -1252,7 +1252,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b73283458ef26d55.woff2) format('woff2'); unicode-range: U+a1-a4, U+a6-a8, U+aa, U+ac, U+af, U+b1, U+b3-b6, U+b8-ba, U+bc-d6, U+d8-de, U+e6, U+eb, U+ee-f0, U+f5, U+f7-f8, @@ -1264,7 +1264,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/2944f60aea487c2a.woff2) format('woff2'); unicode-range: U+221a, U+2264, U+2464, U+25a0, U+3008, U+4e10, U+512a, U+5152, U+5201, U+5241, U+5340, U+5352, U+549a, U+54b2, @@ -1286,7 +1286,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/b140f9d60ba877f5.woff2) format('woff2'); unicode-range: U+161, U+926, U+928, U+939, U+93f-940, U+94d, U+e17, U+e22, U+e44, U+25c7, U+25ce, U+2764, U+3009, U+3016-3017, @@ -1307,7 +1307,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/3bf9956fe5c594f4.woff2) format('woff2'); unicode-range: U+10c, U+627-629, U+639, U+644, U+64a, U+203b, U+2265, U+2463, U+2573, U+25b2, U+3448-3449, U+4e1e, U+4e5e, U+4f3a, @@ -1329,7 +1329,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/47d3dc9f8403f684.woff2) format('woff2'); unicode-range: U+60, U+631, U+2606, U+3014-3015, U+309c, U+33a1, U+4e52, U+4ec6, U+4f86, U+4f8d, U+4fde, U+4fef, U+500b, U+502a, @@ -1351,7 +1351,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/4a7ea73dc15f2b63.woff2) format('woff2'); unicode-range: U+2003, U+2193, U+2462, U+4e19, U+4e2b, U+4e36, U+4ea8, U+4ed1, U+4ed7, U+4f51, U+4f63, U+4f83, U+50e7, U+5112, @@ -1373,7 +1373,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f0721a3949f84d42.woff2) format('woff2'); unicode-range: U+5e, U+2190, U+250a, U+25bc, U+25cf, U+4e56, U+4ea9, U+4f3d, U+4f6c, U+4f88, U+4fa8, U+4fcf, U+5029, U+5188, @@ -1395,7 +1395,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/71139ee70c1b1fce.woff2) format('woff2'); unicode-range: U+b0, U+2191, U+2460-2461, U+25c6, U+300e-300f, U+4e1b, U+4e7e, U+4ed5, U+4ef2, U+4f10, U+4f1e, U+4f50, U+4fa6, @@ -1417,7 +1417,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/75c0090760a68ac5.woff2) format('woff2'); unicode-range: U+200b, U+2103, U+4e18, U+4e27-4e28, U+4e38, U+4e59, U+4e8f, U+4ead, U+4ec7, U+4fe9, U+503a, U+5085, U+5146, U+51af, @@ -1439,7 +1439,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/8eae1f2d9b73ca52.woff2) format('woff2'); unicode-range: U+24, U+4e08, U+4e43, U+4e4f, U+4ef0, U+4f2a, U+507f, U+50ac, U+50bb, U+5151, U+51bb, U+51f6, U+51fd, U+5272, @@ -1461,7 +1461,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/a263283eb6ff7679.woff2) format('woff2'); unicode-range: U+a5, U+2192, U+2605, U+4e11, U+4e22, U+4e32, U+4f0d, U+4f0f, U+4f69, U+4ff1, U+50b2, U+5154, U+51dd, U+51f0, @@ -1483,7 +1483,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/09c325c9da420816.woff2) format('woff2'); unicode-range: U+300c-300d, U+4e54, U+4e58, U+4e95, U+4ec1, U+4f2f, U+4f38, U+4fa3, U+4fca, U+503e, U+5141, U+5144, U+517c, U+51cc, @@ -1505,7 +1505,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/053609e6b57c594f.woff2) format('woff2'); unicode-range: U+4e4c, U+4e88, U+4ea1, U+4ea6, U+4ed3-4ed4, U+4eff, U+4f30, U+4fa7, U+4fc4, U+4fd7, U+500d, U+504f, U+5076-5077, @@ -1527,7 +1527,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/5c92b2016607caeb.woff2) format('woff2'); unicode-range: U+23, U+3d, U+4e01, U+4e39, U+4e73, U+4ecd, U+4ed9, U+4eea, U+4f0a, U+4f1f, U+4f5b, U+4fa0, U+4fc3, U+501f, U+50a8, @@ -1548,7 +1548,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f192aec3fa1f17f4.woff2) format('woff2'); unicode-range: U+26, U+3c, U+d7, U+4e4e, U+4e61, U+4e71, U+4ebf, U+4f26, U+5012, U+51ac, U+51b0, U+51b2, U+51b7, U+5218, U+521a, @@ -1569,7 +1569,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f6255320a8ecc997.woff2) format('woff2'); unicode-range: U+7e, U+2026, U+4e03, U+4e25, U+4e30, U+4e34, U+4e45, U+4e5d, U+4e89, U+4eae, U+4ed8, U+4f11, U+4f19, U+4f24, @@ -1591,7 +1591,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/5849283072442ca2.woff2) format('woff2'); unicode-range: U+25, U+4e14, U+4e1d, U+4e3d, U+4e49, U+4e60, U+4e9a, U+4eb2, U+4ec5, U+4efd, U+4f3c, U+4f4f, U+4f8b, U+4fbf, @@ -1613,7 +1613,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/04eaf1a999368419.woff2) format('woff2'); unicode-range: U+2b, U+40, U+3000, U+300a-300b, U+4e16, U+4e66, U+4e70, U+4e91-4e92, U+4e94, U+4e9b, U+4ec0, U+4eca, U+4f01, @@ -1635,7 +1635,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/2ef4d8c6c25aaa84.woff2) format('woff2'); unicode-range: U+4e, U+201c-201d, U+3010-3011, U+4e07, U+4e1c, U+4e24, U+4e3e, U+4e48, U+4e50, U+4e5f, U+4e8b-4e8c, U+4ea4, @@ -1657,7 +1657,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/f63d970a7b2b5321.woff2) format('woff2'); unicode-range: U+d, U+3e, U+5f, U+7c, U+a0, U+a9, U+4e09-4e0b, U+4e0d-4e0e, U+4e13, U+4e1a, U+4e2a, U+4e3a-4e3b, U+4e4b, U+4e86, @@ -1678,7 +1678,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/ca6307f70603bdbf.woff2) format('woff2'); unicode-range: U+20-22, U+27-2a, U+2c-3b, U+3f, U+41-4d, U+4f-5d, U+61-7b, U+7d, U+ab, U+ae, U+b2, U+b7, U+bb, U+df-e5, U+e7-ea, @@ -1694,7 +1694,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/89c8b72714b2fb05.woff2) format('woff2'); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } @@ -1703,7 +1703,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/38475678e2d17cfd.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, @@ -1714,7 +1714,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/59aba4ce37e0d3de.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, @@ -1725,7 +1725,7 @@ font-family: 'Noto Serif SC'; font-style: normal; font-weight: 400 700; - font-display: block; + font-display: swap; src: url(./fonts/d8c61f0c834f348a.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, diff --git a/web/src/common/appearance/fonts.ts b/web/src/common/appearance/fonts.ts index 5aa3c9a..42e04c3 100644 --- a/web/src/common/appearance/fonts.ts +++ b/web/src/common/appearance/fonts.ts @@ -14,7 +14,7 @@ export function applyFontCatalog(fonts: CustomFont[]) { style = document.createElement('style'); style.id = 'rm-custom-fonts'; document.head.append(style) } // Names are display-only; CSS identifiers and resource paths use only server-generated content hashes. - const css = fonts.map(font => `@font-face { font-family: '${font.id}'; src: url('${fontResourceRoot()}${font.id}/file'); font-display: block; }`).join('\n') + const css = fonts.map(font => `@font-face { font-family: '${font.id}'; src: url('${fontResourceRoot()}${font.id}/file'); font-display: swap; }`).join('\n') if (style.textContent !== css) style.textContent = css } diff --git a/web/src/common/appearance/maple-font.css b/web/src/common/appearance/maple-font.css index 2c544d5..de3c167 100644 --- a/web/src/common/appearance/maple-font.css +++ b/web/src/common/appearance/maple-font.css @@ -1,61 +1,61 @@ /* Maple Mono v7.9; generated by scripts/prepare-maple-font.py. See fonts/maple/LICENSE.txt for licensing. */ -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-00.woff2') format('woff2'); unicode-range: U+0-FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: block; src: url('./fonts/maple/regular-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-00.woff2') format('woff2'); unicode-range: U+0-FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: block; src: url('./fonts/maple/bold-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-00.woff2') format('woff2'); unicode-range: U+0-FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: block; src: url('./fonts/maple/italic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-00.woff2') format('woff2'); unicode-range: U+0-FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } -@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: block; src: url('./fonts/maple/bolditalic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-00.woff2') format('woff2'); unicode-range: U+0-FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 400; font-display: swap; src: url('./fonts/maple/regular-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-00.woff2') format('woff2'); unicode-range: U+0-FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: normal; font-weight: 700; font-display: swap; src: url('./fonts/maple/bold-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-00.woff2') format('woff2'); unicode-range: U+0-FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 400; font-display: swap; src: url('./fonts/maple/italic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-00.woff2') format('woff2'); unicode-range: U+0-FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-01.woff2') format('woff2'); unicode-range: U+1000-1FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-02.woff2') format('woff2'); unicode-range: U+2000-2FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-03.woff2') format('woff2'); unicode-range: U+3000-3FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-04.woff2') format('woff2'); unicode-range: U+4000-4FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-05.woff2') format('woff2'); unicode-range: U+5000-5FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-06.woff2') format('woff2'); unicode-range: U+6000-6FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-07.woff2') format('woff2'); unicode-range: U+7000-7FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-08.woff2') format('woff2'); unicode-range: U+8000-8FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-09.woff2') format('woff2'); unicode-range: U+9000-9FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-0e.woff2') format('woff2'); unicode-range: U+E000-EFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-0f.woff2') format('woff2'); unicode-range: U+F000-FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-1f.woff2') format('woff2'); unicode-range: U+1F000-1FFFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-f0.woff2') format('woff2'); unicode-range: U+F0000-F0FFF; } +@font-face { font-family: 'Maple Mono Normal NL NF CN'; font-style: italic; font-weight: 700; font-display: swap; src: url('./fonts/maple/bolditalic-f1.woff2') format('woff2'); unicode-range: U+F1000-F1FFF; } diff --git a/web/src/common/i18n/en.json b/web/src/common/i18n/en.json index d18db92..3cd05ad 100644 --- a/web/src/common/i18n/en.json +++ b/web/src/common/i18n/en.json @@ -1,7 +1,7 @@ { "demo_notice": "Editor preview only. Changes are not saved; refreshing restores the sample.", "retry_b8784c": "Retry", - "fonts_could_not_be_loaded_42a574": "Fonts could not be loaded. Check your connection and refresh the page.", + "fonts_could_not_be_loaded_42a574": "Some fonts could not be loaded. Using fallback fonts; refresh to try again.", "refresh_page_9366e1": "Refresh page", "invalid_font_catalog_b577df": "Invalid font catalog", "manrope_sans_serif_a8923c": "Manrope · Sans serif", diff --git a/web/src/common/i18n/zh-CN.json b/web/src/common/i18n/zh-CN.json index 6f324cc..325836e 100644 --- a/web/src/common/i18n/zh-CN.json +++ b/web/src/common/i18n/zh-CN.json @@ -1,7 +1,7 @@ { "demo_notice": "仅供编辑器预览。修改不会保存,刷新后恢复示例。", "retry_b8784c": "重试", - "fonts_could_not_be_loaded_42a574": "字体加载失败,请检查网络后刷新页面。", + "fonts_could_not_be_loaded_42a574": "部分字体加载失败,正在使用备用字体,可刷新页面重试。", "refresh_page_9366e1": "刷新页面", "invalid_font_catalog_b577df": "字体列表格式无效", "manrope_sans_serif_a8923c": "Manrope · 清晰无衬线", diff --git a/web/src/main.ts b/web/src/main.ts index 2755a17..5f0d586 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -13,7 +13,6 @@ import '@@/assets/styles/app.css' import '@@/assets/styles/element-plus.scss' import 'virtual:uno.css' -watchFontLoading() const app = createApp(App) app.use(i18n).use(pinia).use(router) const user = useUserStore(pinia) @@ -28,5 +27,5 @@ watch(() => user.sessionVersion, () => { }) // Mount a retryable error interface even if identity restoration temporarily fails. void router.isReady().catch(() => {}).finally(() => { - app.mount('#app'); void registerPwa() + app.mount('#app'); watchFontLoading(); void registerPwa() }) diff --git a/web/src/pages/workspace/components/EditorPane.vue b/web/src/pages/workspace/components/EditorPane.vue index 3f33212..cd9d584 100644 --- a/web/src/pages/workspace/components/EditorPane.vue +++ b/web/src/pages/workspace/components/EditorPane.vue @@ -74,6 +74,9 @@ watch(() => [ws.active, tab.value?.status, ws.view, ws.images], () => {

{{ translate('workspace.select_or_create_a_note_669f82') }}

+
+

{{ translate('workspace.loading_4927a5') }}

+

{{ translate('workspace.could_not_open_this_note_634236') }}

diff --git a/web/src/service-worker.js b/web/src/service-worker.js index 5d12cbd..55ec087 100644 --- a/web/src/service-worker.js +++ b/web/src/service-worker.js @@ -1,13 +1,15 @@ const CACHE = 'rawmark-shell-__VERSION__' const FILES = __PRECACHE__ const paths = new Set(FILES) +const isFont = path => /\.(?:woff2?|ttf|otf)$/.test(path) +const shell = FILES.filter(path => !isFont(path)) globalThis.addEventListener('install', (event) => { event.waitUntil((async () => { const cache = await caches.open(CACHE) - // Download in batches instead of requesting hundreds of syntax packages and fonts concurrently. - for (let i = 0; i < FILES.length; i += 16) { - await cache.addAll(FILES.slice(i, i + 16).map(url => new Request(url, { cache: 'reload' }))) + // Download in batches instead of requesting hundreds of syntax packages concurrently. + for (let i = 0; i < shell.length; i += 16) { + await cache.addAll(shell.slice(i, i + 16).map(url => new Request(url, { cache: 'reload' }))) } })()) }) @@ -67,6 +69,11 @@ globalThis.addEventListener('fetch', (event) => { if (previous) return previous } } - return fetch(request) + const response = await fetch(request) + // Cache public build fonts only after use; private resource tickets never reach this branch. + if (paths.has(url.pathname) && isFont(url.pathname) && response.ok) { + event.waitUntil(cache.put(url.pathname, response.clone())) + } + return response })()) }) diff --git a/web/src/service-worker.test.ts b/web/src/service-worker.test.ts new file mode 100644 index 0000000..c2b9e37 --- /dev/null +++ b/web/src/service-worker.test.ts @@ -0,0 +1,53 @@ +import { readFileSync } from 'node:fs' +import { runInNewContext } from 'node:vm' +import { expect, it, vi } from 'vitest' + +function worker() { + const handlers: Record) => void, respondWith: (promise: Promise) => void }) => void> = {} + const cache = { addAll: vi.fn().mockResolvedValue(undefined), match: vi.fn<() => Promise>().mockResolvedValue(undefined), put: vi.fn().mockResolvedValue(undefined) } + const fetch = vi.fn().mockResolvedValue(new Response('font')) + const source = readFileSync(new URL('./service-worker.js', import.meta.url), 'utf8').replace('__VERSION__', 'test').replace('__PRECACHE__', JSON.stringify(['/index.html', '/assets/main.js', '/assets/regular.woff2', '/assets/bold.woff2'])) + runInNewContext(source, { URL, Request: class extends Request { + constructor(url: string, options?: RequestInit) { + super(new URL(url, 'https://test.invalid'), options) + } + }, fetch, caches: { open: async () => cache, keys: async () => [] }, globalThis: { location: { origin: 'https://test.invalid' }, addEventListener: (name: string, handler: (event: { request: Request, waitUntil: (promise: Promise) => void, respondWith: (promise: Promise) => void }) => void) => { + handlers[name] = handler + } } }) + return { handlers, cache, fetch } +} +it('安装缓存外壳但不预下载字体', async () => { + const { handlers, cache } = worker() + let done!: Promise + handlers.install!({ request: new Request('https://test.invalid'), respondWith: vi.fn(), waitUntil: (promise: Promise) => { + done = promise + } }) + await done + expect(cache.addAll.mock.calls.flat(2).map((request: Request) => request.url)).toEqual(['https://test.invalid/index.html', 'https://test.invalid/assets/main.js']) +}) +it('使用后缓存公开构建字体,缓存命中不再请求网络', async () => { + const { handlers, cache, fetch } = worker() + const pending: Promise[] = [] + let response!: Promise + const event = { request: new Request('https://test.invalid/assets/bold.woff2'), respondWith: (promise: Promise) => { + response = promise + }, waitUntil: (promise: Promise) => pending.push(promise) } + handlers.fetch!(event); expect((await response).status).toBe(200); await Promise.all(pending) + expect(cache.put).toHaveBeenCalledOnce() + cache.match.mockResolvedValue(new Response('cached')) + handlers.fetch!(event); expect(await (await response).text()).toBe('cached') + expect(fetch).toHaveBeenCalledOnce() +}) +it('不缓存失败响应,不拦截私有字体票据', async () => { + const { handlers, cache, fetch } = worker() + fetch.mockResolvedValue(new Response('missing', { status: 404 })) + let response!: Promise + const respondWith = vi.fn((promise: Promise) => { + response = promise + }) + handlers.fetch!({ request: new Request('https://test.invalid/assets/regular.woff2'), respondWith, waitUntil: vi.fn() }) + expect((await response).status).toBe(404); expect(cache.put).not.toHaveBeenCalled() + respondWith.mockClear() + handlers.fetch!({ request: new Request('https://test.invalid/api/v1/resources/ticket/fonts/custom/file'), respondWith, waitUntil: vi.fn() }) + expect(respondWith).not.toHaveBeenCalled() +})