diff --git a/CHANGELOG.md b/CHANGELOG.md index c118e7e5d..c0eaf721c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように - フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように - 引用対象を「もっと見る」で展開した場合、「閉じる」で畳めるように +- プロフィールURLをコピーできるボタンを追加 #11190 - Fix: サーバーメトリクスが90度傾いている - Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正 - Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 @@ -34,12 +35,14 @@ - Fix: ページ遷移でスクロール位置が保持されない問題を修正 - Fix: フォルダーのページネーションが機能しない #11180 - Fix: 長い文章を投稿する際、プレビューが画面からはみ出る問題を修正 +- Fix: システムフォント設定が正しく反映されない問題を修正 ### Server - JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました - nsfwjs のモデルロードを排他することで、重複ロードによってメモリ使用量が増加しないように - 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用) - 全体的なDBクエリのパフォーマンスを向上 +- featuredノートのsignedGet回数を減らしました ## 13.13.2 diff --git a/gulpfile.js b/gulpfile.mjs similarity index 88% rename from gulpfile.js rename to gulpfile.mjs index 6507aad60..9556eb795 100644 --- a/gulpfile.js +++ b/gulpfile.mjs @@ -2,14 +2,14 @@ * Gulp tasks */ -const fs = require('fs'); -const gulp = require('gulp'); -const replace = require('gulp-replace'); -const terser = require('gulp-terser'); -const cssnano = require('gulp-cssnano'); +import * as fs from 'node:fs'; +import gulp from 'gulp'; +import replace from 'gulp-replace'; +import terser from 'gulp-terser'; +import cssnano from 'gulp-cssnano'; -const locales = require('./locales'); -const meta = require('./package.json'); +import locales from './locales/index.js'; +import meta from './package.json' assert { type: "json" }; gulp.task('copy:backend:views', () => gulp.src('./packages/backend/src/server/web/views/**/*').pipe(gulp.dest('./packages/backend/built/server/web/views')) diff --git a/locales/generateDTS.js b/locales/generateDTS.js index 5949aee7c..bc9827632 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -1,6 +1,6 @@ -const fs = require('fs'); -const yaml = require('js-yaml'); -const ts = require('typescript'); +import * as fs from 'node:fs'; +import * as yaml from 'js-yaml'; +import * as ts from 'typescript'; function createMembers(record) { return Object.entries(record) @@ -14,7 +14,7 @@ function createMembers(record) { )); } -module.exports = function generateDTS() { +export default function generateDTS() { const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); const members = createMembers(locale); const elements = [ diff --git a/locales/index.d.ts b/locales/index.d.ts index dacab0cca..42dddebbf 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -59,6 +59,7 @@ export interface Locale { "copyNoteId": string; "copyFileId": string; "copyFolderId": string; + "copyProfileUrl": string; "searchUser": string; "reply": string; "loadMore": string; diff --git a/locales/index.js b/locales/index.js index 2248bb6ac..7801f1275 100644 --- a/locales/index.js +++ b/locales/index.js @@ -2,8 +2,8 @@ * Languages Loader */ -const fs = require('fs'); -const yaml = require('js-yaml'); +import * as fs from 'node:fs'; +import * as yaml from 'js-yaml'; const merge = (...args) => args.reduce((a, c) => ({ ...a, @@ -51,9 +51,9 @@ const primaries = { // 何故か文字列にバックスペース文字が混入することがあり、YAMLが壊れるので取り除く const clean = (text) => text.replace(new RegExp(String.fromCodePoint(0x08), 'g'), ''); -const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(`${__dirname}/${c}.yml`, 'utf-8'))) || {}, a), {}); +const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, import.meta.url), 'utf-8'))) || {}, a), {}); -module.exports = Object.entries(locales) +export default Object.entries(locales) .reduce((a, [k ,v]) => (a[k] = (() => { const [lang] = k.split('-'); switch (k) { diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 29f157b84..16014da51 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -56,6 +56,7 @@ copyUserId: "ユーザーIDをコピー" copyNoteId: "ノートIDをコピー" copyFileId: "ファイルIDをコピー" copyFolderId: "フォルダーIDをコピー" +copyProfileUrl: "プロフィールURLをコピー" searchUser: "ユーザーを検索" reply: "返信" loadMore: "もっと見る" diff --git a/locales/package.json b/locales/package.json new file mode 100644 index 000000000..bedb411a9 --- /dev/null +++ b/locales/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 35865a819..d3359ef90 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -177,7 +177,7 @@ export class ApNoteService { // リプライ const reply: Note | null = note.inReplyTo - ? await this.resolveNote(note.inReplyTo, resolver) + ? await this.resolveNote(note.inReplyTo, { resolver }) .then(x => { if (x == null) { this.logger.warn('Specified inReplyTo, but not found'); @@ -293,9 +293,8 @@ export class ApNoteService { * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ @bindThis - public async resolveNote(value: string | IObject, resolver?: Resolver): Promise { - const uri = typeof value === 'string' ? value : value.id; - if (uri == null) throw new Error('missing uri'); + public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise { + const uri = getApId(value); // ブロックしていたら中断 const meta = await this.metaService.fetch(); @@ -318,7 +317,8 @@ export class ApNoteService { // リモートサーバーからフェッチしてきて登録 // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 - return await this.createNote(uri, resolver, true); + const createFrom = options.sentFrom?.origin === new URL(uri).origin ? value : uri; + return await this.createNote(createFrom, options.resolver, true); } finally { unlock(); } diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 3b5975662..86c2a8271 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -637,7 +637,10 @@ export class ApPersonService implements OnModuleInit { const featuredNotes = await Promise.all(items .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも .slice(0, 5) - .map(item => limit(() => this.apNoteService.resolveNote(item, _resolver)))); + .map(item => limit(() => this.apNoteService.resolveNote(item, { + resolver: _resolver, + sentFrom: new URL(user.uri), + })))); await this.db.transaction(async transactionalEntityManager => { await transactionalEntityManager.delete(UserNotePining, { userId: user.id }); diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index fd726b1fc..481179441 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -18,7 +18,8 @@ type MockResponse = { }; export class MockResolver extends Resolver { - private _rs = new Map(); + #responseMap = new Map(); + #remoteGetTrials: string[] = []; constructor(loggerService: LoggerService) { super( @@ -38,22 +39,28 @@ export class MockResolver extends Resolver { ); } - public _register(uri: string, content: string | Record, type = 'application/activity+json') { - this._rs.set(uri, { + public register(uri: string, content: string | Record, type = 'application/activity+json'): void { + this.#responseMap.set(uri, { type, content: typeof content === 'string' ? content : JSON.stringify(content), }); } - public clear() { - this._rs.clear(); + public clear(): void { + this.#responseMap.clear(); + this.#remoteGetTrials.length = 0; + } + + public remoteGetTrials(): string[] { + return this.#remoteGetTrials; } @bindThis public async resolve(value: string | IObject): Promise { if (typeof value !== 'string') return value; - const r = this._rs.get(value); + this.#remoteGetTrials.push(value); + const r = this.#responseMap.get(value); if (!r) { throw new Error('Not registered for mock'); diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index a95f0bfa5..c677980d4 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -18,9 +18,12 @@ import { MockResolver } from '../misc/mock-resolver.js'; const host = 'https://host1.test'; -function createRandomActor(): IActor & { id: string } { +type NonTransientIActor = IActor & { id: string }; +type NonTransientIPost = IPost & { id: string }; + +function createRandomActor({ actorHost = host } = {}): NonTransientIActor { const preferredUsername = secureRndstr(8); - const actorId = `${host}/users/${preferredUsername.toLowerCase()}`; + const actorId = `${actorHost}/users/${preferredUsername.toLowerCase()}`; return { '@context': 'https://www.w3.org/ns/activitystreams', @@ -32,26 +35,48 @@ function createRandomActor(): IActor & { id: string } { }; } -function createRandomCreateActivity(actor: IActor, length: number): ICreate[] { +function createRandomNote(actor: NonTransientIActor): NonTransientIPost { + const id = secureRndstr(8); + const noteId = `${new URL(actor.id).origin}/notes/${id}`; + + return { + id: noteId, + type: 'Note', + attributedTo: actor.id, + content: 'test test foo', + }; +} + +function createRandomNotes(actor: NonTransientIActor, length: number): NonTransientIPost[] { + return new Array(length).fill(null).map(() => createRandomNote(actor)); +} + +function createRandomFeaturedCollection(actor: NonTransientIActor, length: number): ICollection { + const items = createRandomNotes(actor, length); + + return { + '@context': 'https://www.w3.org/ns/activitystreams', + type: 'Collection', + id: actor.outbox as string, + totalItems: items.length, + items, + }; +} + +function createRandomCreateActivity(actor: NonTransientIActor, length: number): ICreate[] { return new Array(length).fill(null).map((): ICreate => { - const id = secureRndstr(8); - const noteId = `${host}/notes/${id}`; + const note = reateRandomNote(actor); return { type: 'Create', - id: `${noteId}/activity`, + id: `${note.id}/activity`, actor, - object: { - id: noteId, - type: 'Note', - attributedTo: actor.id, - content: 'test test foo', - } satisfies IPost, + object: note, }; }); } -function createRandomNonPagedOutbox(actor: IActor, length: number): IOrderedCollection { +function createRandomNonPagedOutbox(actor: NonTransientIActor, length: number): IOrderedCollection { const orderedItems = createRandomCreateActivity(actor, length); return { @@ -63,7 +88,7 @@ function createRandomNonPagedOutbox(actor: IActor, length: number): IOrderedColl }; } -function createRandomOutboxPage(actor: IActor, id: string, length: number): IOrderedCollectionPage { +function createRandomOutboxPage(actor: NonTransientIActor, id: string, length: number): IOrderedCollectionPage { const orderedItems = createRandomCreateActivity(actor, length); return { @@ -75,14 +100,14 @@ function createRandomOutboxPage(actor: IActor, id: string, length: number): IOrd }; } -function createRandomPagedOutbox(actor: IActor): IOrderedCollection { +function createRandomPagedOutbox(actor: NonTransientIActor): IOrderedCollection { return { '@context': 'https://www.w3.org/ns/activitystreams', type: 'OrderedCollection', id: actor.outbox as string, totalItems: 10, first: `${actor.outbox}?first`, - }; + }; } describe('ActivityPub', () => { @@ -126,7 +151,7 @@ describe('ActivityPub', () => { }; test('Minimum Actor', async () => { - resolver._register(actor.id, actor); + resolver.register(actor.id, actor); const user = await personService.createPerson(actor.id, resolver); @@ -136,8 +161,8 @@ describe('ActivityPub', () => { }); test('Minimum Note', async () => { - resolver._register(actor.id, actor); - resolver._register(post.id, post); + resolver.register(actor.id, actor); + resolver.register(post.id, post); const note = await noteService.createNote(post.id, resolver, true); @@ -154,7 +179,7 @@ describe('ActivityPub', () => { name: secureRndstr(129), }; - resolver._register(actor.id, actor); + resolver.register(actor.id, actor); const user = await personService.createPerson(actor.id, resolver); @@ -167,7 +192,7 @@ describe('ActivityPub', () => { name: '', }; - resolver._register(actor.id, actor); + resolver.register(actor.id, actor); const user = await personService.createPerson(actor.id, resolver); @@ -184,13 +209,70 @@ describe('ActivityPub', () => { }); }); + describe('Featured', () => { + test('Fetch featured notes from IActor', async () => { + const actor = createRandomActor(); + actor.featured = `${actor.id}/collections/featured`; + + const featured = createRandomFeaturedCollection(actor, 5); + + resolver.register(actor.id, actor); + resolver.register(actor.featured, featured); + + await personService.createPerson(actor.id, resolver); + + // All notes in `featured` are same-origin, no need to fetch notes again + assert.deepStrictEqual(resolver.remoteGetTrials(), [actor.id, actor.featured]); + + // Created notes without resolving anything + for (const item of featured.items as IPost[]) { + const note = await noteService.fetchNote(item); + assert.ok(note); + assert.strictEqual(note.text, 'test test foo'); + assert.strictEqual(note.uri, item.id); + } + }); + + test('Fetch featured notes from IActor pointing to another remote server', async () => { + const actor1 = createRandomActor(); + actor1.featured = `${actor1.id}/collections/featured`; + const actor2 = createRandomActor({ actorHost: 'https://host2.test' }); + + const actor2Note = createRandomNote(actor2); + const featured = createRandomFeaturedCollection(actor1, 0); + (featured.items as IPost[]).push({ + ...actor2Note, + content: 'test test bar', // fraud! + }); + + resolver.register(actor1.id, actor1); + resolver.register(actor1.featured, featured); + resolver.register(actor2.id, actor2); + resolver.register(actor2Note.id, actor2Note); + + await personService.createPerson(actor1.id, resolver); + + // actor2Note is from a different server and needs to be fetched again + assert.deepStrictEqual( + resolver.remoteGetTrials(), + [actor1.id, actor1.featured, actor2Note.id, actor2.id], + ); + + const note = await noteService.fetchNote(actor2Note.id); + assert.ok(note); + + // Reflects the original content instead of the fraud + assert.strictEqual(note.text, 'test test foo'); + assert.strictEqual(note.uri, actor2Note.id); + }); + describe('Outbox', () => { test('Fetch non-paged outbox from IActor', async () => { const actor = createRandomActor(); const outbox = createRandomNonPagedOutbox(actor, 10); - resolver._register(actor.id, actor); - resolver._register(actor.outbox as string, outbox); + resolver.register(actor.id, actor); + resolver.register(actor.outbox as string, outbox); await personService.createPerson(actor.id, resolver); @@ -207,9 +289,9 @@ describe('ActivityPub', () => { const outbox = createRandomPagedOutbox(actor); const page = createRandomOutboxPage(actor, outbox.id!, 10); - resolver._register(actor.id, actor); - resolver._register(actor.outbox as string, outbox); - resolver._register(outbox.first as string, page); + resolver.register(actor.id, actor); + resolver.register(actor.outbox as string, outbox); + resolver.register(outbox.first as string, page); await personService.createPerson(actor.id, resolver); @@ -221,18 +303,18 @@ describe('ActivityPub', () => { } }); - test('Fetch only the first 100 items', async () => { + test('Fetch only the first 20 items', async () => { const actor = createRandomActor(); const outbox = createRandomNonPagedOutbox(actor, 200); - resolver._register(actor.id, actor); - resolver._register(actor.outbox as string, outbox); + resolver.register(actor.id, actor); + resolver.register(actor.outbox as string, outbox); await personService.createPerson(actor.id, resolver); const items = outbox.orderedItems as ICreate[]; assert.ok(await noteService.fetchNote(items[19].object)); assert.ok(!await noteService.fetchNote(items[20].object)); - }); + }); }); }); diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts index fc0f0c286..a1275132b 100644 --- a/packages/frontend/.storybook/changes.ts +++ b/packages/frontend/.storybook/changes.ts @@ -1,7 +1,10 @@ import fs from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; import path from 'node:path'; import micromatch from 'micromatch'; -import main from './main'; +import main from './main.js'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); interface Stats { readonly modules: readonly { @@ -13,8 +16,8 @@ interface Stats { }[]; } -fs.readFile( - path.resolve(__dirname, '../storybook-static/preview-stats.json') +await fs.readFile( + new URL('../storybook-static/preview-stats.json', import.meta.url) ).then((buffer) => { const stats: Stats = JSON.parse(buffer.toString()); const keys = new Set(stats.modules.map((stat) => stat.id)); diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts index 1d0ce5ab6..b64979980 100644 --- a/packages/frontend/.storybook/main.ts +++ b/packages/frontend/.storybook/main.ts @@ -1,7 +1,11 @@ import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; import type { StorybookConfig } from '@storybook/vue3-vite'; import { type Plugin, mergeConfig } from 'vite'; import turbosnap from 'vite-plugin-turbosnap'; + +const dirname = fileURLToPath(new URL('.', import.meta.url)); + const config = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ @@ -9,7 +13,7 @@ const config = { '@storybook/addon-interactions', '@storybook/addon-links', '@storybook/addon-storysource', - resolve(__dirname, '../node_modules/storybook-addon-misskey-theme'), + resolve(dirname, '../node_modules/storybook-addon-misskey-theme'), ], framework: { name: '@storybook/vue3-vite', @@ -28,7 +32,8 @@ const config = { } return mergeConfig(config, { plugins: [ - turbosnap({ + // XXX: https://github.com/IanVS/vite-plugin-turbosnap/issues/8 + (turbosnap as any as typeof turbosnap['default'])({ rootDir: config.root ?? process.cwd(), }), ], diff --git a/packages/frontend/.storybook/package.json b/packages/frontend/.storybook/package.json new file mode 100644 index 000000000..bedb411a9 --- /dev/null +++ b/packages/frontend/.storybook/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/frontend/.storybook/preload-locale.ts b/packages/frontend/.storybook/preload-locale.ts index a54164742..636931967 100644 --- a/packages/frontend/.storybook/preload-locale.ts +++ b/packages/frontend/.storybook/preload-locale.ts @@ -1,9 +1,8 @@ import { writeFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; -import * as locales from '../../../locales'; +import * as locales from '../../../locales/index.js'; -writeFile( - resolve(__dirname, 'locale.ts'), +await writeFile( + new URL('locale.ts', import.meta.url), `export default ${JSON.stringify(locales['ja-JP'], undefined, 2)} as const;`, 'utf8', ) diff --git a/packages/frontend/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts index 1ff8f71ec..42fbeff73 100644 --- a/packages/frontend/.storybook/preload-theme.ts +++ b/packages/frontend/.storybook/preload-theme.ts @@ -1,6 +1,5 @@ import { readFile, writeFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; -import * as JSON5 from 'json5'; +import JSON5 from 'json5'; const keys = [ '_dark', @@ -26,9 +25,9 @@ const keys = [ 'd-u0', ] -Promise.all(keys.map((key) => readFile(resolve(__dirname, `../src/themes/${key}.json5`), 'utf8'))).then((sources) => { +await Promise.all(keys.map((key) => readFile(new URL(`../src/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => { writeFile( - resolve(__dirname, './themes.ts'), + new URL('./themes.ts', import.meta.url), `export default ${JSON.stringify( Object.fromEntries(sources.map((source, i) => [keys[i], JSON5.parse(source)])), undefined, diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts index e887acaa2..67c81c666 100644 --- a/packages/frontend/.storybook/preview.ts +++ b/packages/frontend/.storybook/preview.ts @@ -3,10 +3,10 @@ import { FORCE_REMOUNT } from '@storybook/core-events'; import { type Preview, setup } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; import { initialize, mswDecorator } from 'msw-storybook-addon'; -import { userDetailed } from './fakes'; -import locale from './locale'; -import { commonHandlers, onUnhandledRequest } from './mocks'; -import themes from './themes'; +import { userDetailed } from './fakes.js'; +import locale from './locale.js'; +import { commonHandlers, onUnhandledRequest } from './mocks.js'; +import themes from './themes.js'; import '../src/style.scss'; const appInitialized = Symbol(); diff --git a/packages/frontend/.storybook/tsconfig.json b/packages/frontend/.storybook/tsconfig.json index 2db2f1eab..02465f5af 100644 --- a/packages/frontend/.storybook/tsconfig.json +++ b/packages/frontend/.storybook/tsconfig.json @@ -1,5 +1,7 @@ { "compilerOptions": { + "target": "es2022", + "module": "Node16", "strict": true, "allowUnusedLabels": false, "allowUnreachableCode": false, diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts index c884ed76c..636a6543d 100644 --- a/packages/frontend/src/scripts/get-user-menu.ts +++ b/packages/frontend/src/scripts/get-user-menu.ts @@ -2,13 +2,14 @@ import { defineAsyncComponent } from 'vue'; import * as misskey from 'misskey-js'; import { i18n } from '@/i18n'; import copyToClipboard from '@/scripts/copy-to-clipboard'; -import { host } from '@/config'; +import { host, url } from '@/config'; import * as os from '@/os'; import { defaultStore, userActions } from '@/store'; import { $i, iAmModerator } from '@/account'; import { mainRouter } from '@/router'; import { Router } from '@/nirax'; import { rolesCache, userListsCache } from '@/cache'; +import { toUnicode } from 'punycode'; export function getUserMenu(user: misskey.entities.UserDetailed, router: Router = mainRouter) { const meId = $i ? $i.id : null; @@ -137,6 +138,13 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router action: () => { copyToClipboard(`${user.host ?? host}/@${user.username}.atom`); }, + }, { + icon: 'ti ti-share', + text: i18n.ts.copyProfileUrl, + action: () => { + const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}` + copyToClipboard(`${url}/${canonical}`); + }, }, { icon: 'ti ti-mail', text: i18n.ts.sendMessage, diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index b4b80a4bb..bd74db7c8 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -72,7 +72,7 @@ html { } &.useSystemFont { - font-family: 'Hiragino Maru Gothic Pro', sans-serif; + font-family: system-ui; } } diff --git a/packages/sw/.eslintrc.js b/packages/sw/.eslintrc.cjs similarity index 100% rename from packages/sw/.eslintrc.js rename to packages/sw/.eslintrc.cjs diff --git a/packages/sw/build.js b/packages/sw/build.js index ad16fb949..e92619764 100644 --- a/packages/sw/build.js +++ b/packages/sw/build.js @@ -1,10 +1,13 @@ // @ts-check -const esbuild = require('esbuild'); -const locales = require('../../locales'); -const meta = require('../../package.json'); +import { fileURLToPath } from 'node:url'; +import * as esbuild from 'esbuild'; +import locales from '../../locales/index.js'; +import meta from '../../package.json' assert { type: "json" }; const watch = process.argv[2]?.includes('watch'); +const __dirname = fileURLToPath(new URL('.', import.meta.url)) + console.log('Starting SW building...'); /** @type {esbuild.BuildOptions} */ diff --git a/packages/sw/package.json b/packages/sw/package.json index a2bead3dd..a60f8230c 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -19,5 +19,6 @@ "eslint": "8.44.0", "eslint-plugin-import": "2.27.5", "typescript": "5.1.6" - } + }, + "type": "module" }