misskey/packages/frontend/.storybook/generate.tsx

417 lines
15 KiB
TypeScript
Raw Normal View History

/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { existsSync, readFileSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { basename, dirname } from 'node:path/posix';
import { GENERATOR, type State, generate } from 'astring';
2023-03-19 13:22:14 +00:00
import type * as estree from 'estree';
2023-04-01 13:22:07 +00:00
import glob from 'fast-glob';
2023-03-19 13:22:14 +00:00
import { format } from 'prettier';
interface SatisfiesExpression extends estree.BaseExpression {
type: 'SatisfiesExpression';
expression: estree.Expression;
reference: estree.Identifier;
}
const generator = {
...GENERATOR,
SatisfiesExpression(node: SatisfiesExpression, state: State) {
switch (node.expression.type) {
case 'ArrowFunctionExpression': {
state.write('(');
this[node.expression.type](node.expression, state);
state.write(')');
break;
}
default: {
// @ts-ignore
this[node.expression.type](node.expression, state);
break;
}
2023-03-21 02:58:58 +00:00
}
2023-03-21 15:25:17 +00:00
state.write(' satisfies ', node as unknown as estree.Expression);
this[node.reference.type](node.reference, state);
},
2023-03-21 15:48:11 +00:00
};
2023-03-21 15:48:11 +00:00
type SplitCamel<
T extends string,
YC extends string = '',
YN extends readonly string[] = []
> = T extends `${infer XH}${infer XR}`
? XR extends ''
? [...YN, Uncapitalize<`${YC}${XH}`>]
: XH extends Uppercase<XH>
2023-03-21 15:48:11 +00:00
? SplitCamel<XR, Lowercase<XH>, [...YN, YC]>
: SplitCamel<XR, `${YC}${XH}`, YN>
: YN;
// @ts-ignore
type SplitKebab<T extends string> = T extends `${infer XH}-${infer XR}`
? [XH, ...SplitKebab<XR>]
: [T];
2023-03-21 15:48:11 +00:00
type ToKebab<T extends readonly string[]> = T extends readonly [
infer XO extends string
]
? XO
2023-03-21 15:48:11 +00:00
: T extends readonly [
infer XH extends string,
...infer XR extends readonly string[]
]
? `${XH}${XR extends readonly string[] ? `-${ToKebab<XR>}` : ''}`
: '';
// @ts-ignore
2023-03-21 15:48:11 +00:00
type ToPascal<T extends readonly string[]> = T extends readonly [
infer XH extends string,
...infer XR extends readonly string[]
]
? `${Capitalize<XH>}${ToPascal<XR>}`
: '';
2023-03-21 15:48:11 +00:00
function h<T extends estree.Node>(
component: T['type'],
props: Omit<T, 'type'>
): T {
2023-03-19 13:22:14 +00:00
const type = component.replace(/(?:^|-)([a-z])/g, (_, c) => c.toUpperCase());
2023-04-01 08:54:30 +00:00
return Object.assign(props || {}, { type }) as T;
2023-03-19 13:22:14 +00:00
}
declare namespace h.JSX {
type Element = estree.Node;
type IntrinsicElements = {
[T in keyof typeof generator as ToKebab<SplitCamel<Uncapitalize<T>>>]: {
[K in keyof Omit<
Parameters<(typeof generator)[T]>[0],
'type'
>]?: Parameters<(typeof generator)[T]>[0][K];
};
};
}
function toStories(component: string): Promise<string> {
const msw = `${component.slice(0, -'.vue'.length)}.msw`;
const implStories = `${component.slice(0, -'.vue'.length)}.stories.impl`;
2023-03-31 19:20:38 +00:00
const metaStories = `${component.slice(0, -'.vue'.length)}.stories.meta`;
const hasMsw = existsSync(`${msw}.ts`);
const hasImplStories = existsSync(`${implStories}.ts`);
2023-03-31 19:20:38 +00:00
const hasMetaStories = existsSync(`${metaStories}.ts`);
const base = basename(component);
const dir = dirname(component);
2023-04-03 05:49:40 +00:00
const literal =
<literal
value={component
.slice('src/'.length, -'.vue'.length)
.replace(/\./g, '/')}
2023-04-03 05:49:40 +00:00
/> as estree.Literal;
const identifier =
2023-03-21 15:48:11 +00:00
<identifier
name={base
.slice(0, -'.vue'.length)
.replace(/[-.]|^(?=\d)/g, '_')
.replace(/(?<=^[^A-Z_]*$)/, '_')}
2023-04-03 05:49:40 +00:00
/> as estree.Identifier;
const parameters =
<object-expression
properties={[
2023-04-03 05:49:40 +00:00
<property
key={<identifier name='layout' /> as estree.Identifier}
value={<literal value={`${dir}/`.startsWith('src/pages/') ? 'fullscreen' : 'centered'}/> as estree.Literal}
kind={'init' as const}
/> as estree.Property,
2023-03-21 15:48:11 +00:00
...(hasMsw
? [
2023-04-03 05:49:40 +00:00
<property
key={<identifier name='msw' /> as estree.Identifier}
value={<identifier name='msw' /> as estree.Identifier}
kind={'init' as const}
shorthand
/> as estree.Property,
2023-03-21 15:48:11 +00:00
]
: []),
]}
/> as estree.ObjectExpression;
const program =
2023-03-19 13:22:14 +00:00
<program
body={[
2023-04-03 05:49:40 +00:00
<import-declaration
source={<literal value='@storybook/vue3' /> as estree.Literal}
specifiers={[
<import-specifier
local={<identifier name='Meta' /> as estree.Identifier}
imported={<identifier name='Meta' /> as estree.Identifier}
/> as estree.ImportSpecifier,
...(hasImplStories
? []
: [
<import-specifier
local={<identifier name='StoryObj' /> as estree.Identifier}
imported={<identifier name='StoryObj' /> as estree.Identifier}
/> as estree.ImportSpecifier,
]),
]}
/> as estree.ImportDeclaration,
2023-03-21 15:48:11 +00:00
...(hasMsw
? [
2023-04-03 05:49:40 +00:00
<import-declaration
source={<literal value={`./${basename(msw)}`} /> as estree.Literal}
specifiers={[
<import-namespace-specifier
local={<identifier name='msw' /> as estree.Identifier}
/> as estree.ImportNamespaceSpecifier,
]}
/> as estree.ImportDeclaration,
2023-03-21 15:48:11 +00:00
]
: []),
...(hasImplStories
2023-03-20 07:27:40 +00:00
? []
: [
2023-04-03 05:49:40 +00:00
<import-declaration
source={<literal value={`./${base}`} /> as estree.Literal}
specifiers={[
<import-default-specifier local={identifier} /> as estree.ImportDefaultSpecifier,
]}
/> as estree.ImportDeclaration,
2023-03-21 15:48:11 +00:00
]),
2023-03-31 19:20:38 +00:00
...(hasMetaStories
? [
2023-04-03 05:49:40 +00:00
<import-declaration
source={<literal value={`./${basename(metaStories)}`} /> as estree.Literal}
specifiers={[
<import-namespace-specifier
local={<identifier name='storiesMeta' /> as estree.Identifier}
/> as estree.ImportNamespaceSpecifier,
]}
/> as estree.ImportDeclaration,
2023-03-31 19:20:38 +00:00
]
: []),
2023-04-03 05:49:40 +00:00
<variable-declaration
kind={'const' as const}
declarations={[
<variable-declarator
id={<identifier name='meta' /> as estree.Identifier}
init={
<satisfies-expression
expression={
<object-expression
properties={[
<property
key={<identifier name='title' /> as estree.Identifier}
value={literal}
kind={'init' as const}
/> as estree.Property,
<property
key={<identifier name='component' /> as estree.Identifier}
value={identifier}
kind={'init' as const}
/> as estree.Property,
...(hasMetaStories
? [
<spread-element
argument={<identifier name='storiesMeta' /> as estree.Identifier}
/> as estree.SpreadElement,
]
: [])
]}
/> as estree.ObjectExpression
}
2023-04-03 05:49:40 +00:00
reference={<identifier name={`Meta<typeof ${identifier.name}>`} /> as estree.Identifier}
/> as estree.Expression
}
/> as estree.VariableDeclarator,
]}
/> as estree.VariableDeclaration,
2023-03-21 15:48:11 +00:00
...(hasImplStories
? []
: [
2023-04-03 05:49:40 +00:00
<export-named-declaration
declaration={
<variable-declaration
kind={'const' as const}
declarations={[
<variable-declarator
id={<identifier name='Default' /> as estree.Identifier}
init={
<satisfies-expression
expression={
<object-expression
properties={[
<property
key={<identifier name='render' /> as estree.Identifier}
value={
<function-expression
params={[
<identifier name='args' /> as estree.Identifier,
]}
body={
<block-statement
body={[
<return-statement
argument={
<object-expression
properties={[
<property
key={<identifier name='components' /> as estree.Identifier}
value={
<object-expression
properties={[
<property key={identifier} value={identifier} kind={'init' as const} shorthand /> as estree.Property,
]}
/> as estree.ObjectExpression
}
kind={'init' as const}
/> as estree.Property,
<property
key={<identifier name='setup' /> as estree.Identifier}
value={
<function-expression
params={[]}
body={
<block-statement
body={[
<return-statement
argument={
2023-03-21 15:48:11 +00:00
<object-expression
properties={[
2023-04-03 05:49:40 +00:00
<property
key={<identifier name='args' /> as estree.Identifier}
value={<identifier name='args' /> as estree.Identifier}
kind={'init' as const}
shorthand
/> as estree.Property,
]}
/> as estree.ObjectExpression
}
/> as estree.ReturnStatement,
]}
/> as estree.BlockStatement
}
/> as estree.FunctionExpression
}
method
kind={'init' as const}
/> as estree.Property,
<property
key={<identifier name='computed' /> as estree.Identifier}
value={
<object-expression
properties={[
<property
key={<identifier name='props' /> as estree.Identifier}
value={
<function-expression
params={[]}
body={
<block-statement
body={[
<return-statement
argument={
<object-expression
properties={[
<spread-element
argument={
<member-expression
object={<this-expression /> as estree.ThisExpression}
property={<identifier name='args' /> as estree.Identifier}
/> as estree.MemberExpression
2023-03-23 07:44:41 +00:00
}
2023-04-03 05:49:40 +00:00
/> as estree.SpreadElement,
]}
/> as estree.ObjectExpression
}
/> as estree.ReturnStatement,
2023-03-21 15:48:11 +00:00
]}
2023-04-03 05:49:40 +00:00
/> as estree.BlockStatement
}
/> as estree.FunctionExpression
}
method
kind={'init' as const}
/> as estree.Property,
]}
2023-04-03 05:49:40 +00:00
/> as estree.ObjectExpression
}
kind={'init' as const}
/> as estree.Property,
<property
key={<identifier name='template' /> as estree.Identifier}
value={<literal value={`<${identifier.name} v-bind="props" />`} /> as estree.Literal}
kind={'init' as const}
/> as estree.Property,
]}
/> as estree.ObjectExpression
}
2023-04-03 05:49:40 +00:00
/> as estree.ReturnStatement,
]}
/> as estree.BlockStatement
}
/> as estree.FunctionExpression
2023-03-21 15:48:11 +00:00
}
2023-04-03 05:49:40 +00:00
method
kind={'init' as const}
/> as estree.Property,
<property
key={<identifier name='parameters' /> as estree.Identifier}
value={parameters}
kind={'init' as const}
/> as estree.Property,
]}
/> as estree.ObjectExpression
}
reference={<identifier name={`StoryObj<typeof ${identifier.name}>`} /> as estree.Identifier}
/> as estree.Expression
}
/> as estree.VariableDeclarator,
]}
/> as estree.VariableDeclaration
}
/> as estree.ExportNamedDeclaration,
]),
<export-default-declaration
declaration={(<identifier name='meta' />) as estree.Identifier}
/> as estree.ExportDefaultDeclaration,
2023-03-19 13:22:14 +00:00
]}
/> as estree.Program;
2023-03-19 13:22:14 +00:00
return format(
2023-03-21 15:25:17 +00:00
'/* eslint-disable @typescript-eslint/explicit-function-return-type */\n' +
'/* eslint-disable import/no-default-export */\n' +
'/* eslint-disable import/no-duplicates */\n' +
'/* eslint-disable import/order */\n' +
2023-03-21 15:25:17 +00:00
generate(program, { generator }) +
(hasImplStories ? readFileSync(`${implStories}.ts`, 'utf-8') : ''),
2023-03-19 13:22:14 +00:00
{
parser: 'babel-ts',
singleQuote: true,
useTabs: true,
}
);
}
// glob('src/{components,pages,ui,widgets}/**/*.vue')
(async () => {
const globs = await Promise.all([
glob('src/components/global/Mk*.vue'),
glob('src/components/global/RouterView.vue'),
glob('src/components/Mk[A-C]*.vue'),
glob('src/components/MkDigitalClock.vue'),
glob('src/components/MkGalleryPostPreview.vue'),
glob('src/components/MkSignupServerRules.vue'),
glob('src/components/MkUserSetupDialog.vue'),
glob('src/components/MkUserSetupDialog.*.vue'),
glob('src/components/MkInstanceCardMini.vue'),
enhance: 招待機能の改善 (#11195) * refactor(backend): 招待機能を改修 * feat(backend): 招待コードのcreate/delete/listエンドポイントを追加 * add(misskey-js): エンドポイントと型を追加 * change(backend): metaでinvite関連の情報も返すように * add(misskey-js): エンドポイントと型を追加 * add(backend): `/endpoints/invite/limit`を追加 * fix: createdByがnullableではなかったのを修正 * fix: relationが取得できていなかった問題を修正 * fix: パラメータを間違えていたのを修正 * feat(client): 招待ページを実装 * change(client): インスタンスメニューの「招待」押した場合に招待ページに飛ぶように変更 * feat: 招待コードをコピーできるように * change(backend): metaに招待コード発行に関する情報を持たせるのをやめる * feat: ロールごとに招待コードの発行上限数などを設定できるように * change(client): 招待コードをコピーしたときにダイアログを出すように * add: 招待に関する管理者用のエンドポイントを追加 * change(backend): モデレーターであれば作成者以外でも招待コードを削除できるように * change(backend): admin/invite/listはオフセットでページネーションするように * feat(client): 招待コードの管理ページを追加 * feat(client): 招待コードのリストをソートできるように * change: `admin/invite/create`のレスポンスを修正 * fix(client): 有効期限を指定できていなかった問題を修正 * refactor: 必要のない箇所を削除 * perf(backend): use limit() instead of take() * change(client): 作成ボタンを見た目を変更 * refactor: 招待コードの生成部分を共通化し、コード内に"01OI"のいずれかの文字を含まないように * fix(client): paginationの仕様が変わっていたので修正 * change(backend): expiresAtパラメータのnullを許容 * change(client): 有効期限を設けないときは日付の入力欄を非表示に * fix: 自身のポリシーよりもインスタンス側のポリシーが優先表示される問題を修正 * fix: n時間のときに「n時間間」となってしまうのを修正 * fix(backend): ポリシーが途中で変更されたときに作成可能数がマイナス表記になってしまうのを修正 * change(client): 招待コードのユーザー名が不明な理由を表示するように * update: CHANGELOG.md * lint * refactor * refactor * tweak ui * :art: * :art: * add(backend): indexを追加 * change(backend): indexの追加に伴う変更 * change(client): インスタンスメニューの「招待」の場所を変更 * add(frontend): MkInviteCode用のstorybookを追加 * Update misskey-js.api.md * fix(misskey-js): InviteのcreatedByの型が間違っていたのを修正 --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2023-07-15 00:57:58 +00:00
glob('src/components/MkInviteCode.vue'),
feat: このユーザーのノートを検索, クエリに基づく検索の初期値 & ノート検索のUI改善 (#14128) * refactor(frontend): noteSearchAvailableをaccountsに移動 * feat: searchページでのクエリの受取りとtypeによる表示タブの変更 * user検索でsearchの親から受け取った値を基に入力値を初期化 * feat(frontend): ノート検索で親(search)からの情報を基にユーザー情報を取得 * feat(frontend): ユーザーのノートを検索するページに遷移するボタン * feat(frontend): ノート検索にホスト名指定のオプション追加 also :art: * style: ただ照会部分を囲っただけ(可読性確保のために) * refactor: remove unneed import defineProps and withDefaults are compiler micro when using `<script setup>` FYI: https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits:~:text=defineProps%20and%20defineEmits%20are%20compiler%20macros%20only%20usable%20inside%20%3Cscript%20setup%3E.%20They%20do%20not%20need%20to%20be%20imported%2C%20and%20are%20compiled%20away%20when%20%3Cscript%20setup%3E%20is%20processed. * Update CHANGELOG * Fix: ノート検索の初期値が常にホスト指定になってしまう * notesSearchAvailableをaccountに持たせるのをやめる * SDPX-Licence-Identifier * Fix: Vitest fails due to instance.policies being undefined * Add Storybook for search * Fix(storybook): ノート検索が利用できないと出てしまう問題 * storybookでユーザー選択ができないのを修正 * feat: ノート検索で自分を選択可能に & :art: * feat(background): api/metaで検索可能なノートのスコープを参照できるように * globalのノートが検索不可能な場合、検索オプションを表示しないように * Update CHANGELOG.md * config.meilisearch.scopeがstring[]を取ることがあるので修正 * meilisearchを利用かつscopeがlocalの場合、リモートユーザーのメニューで「このユーザーのノートを検索」を出さないように * hostが空文字の時の挙動を修正 * ローカルのみしかノートがインデックスされていない場合、リモートユーザーも選択できなくした
2024-07-30 06:51:08 +00:00
glob('src/pages/search.vue'),
glob('src/pages/user/home.vue'),
]);
const components = globs.flat();
await Promise.all(components.map(async (component) => {
const stories = component.replace(/\.vue$/, '.stories.ts');
await writeFile(stories, await toStories(component));
}))
})();