* 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 * 🎨 * 🎨 * 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>
		
			
				
	
	
		
			415 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			415 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
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';
 | 
						|
import type * as estree from 'estree';
 | 
						|
import glob from 'fast-glob';
 | 
						|
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;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		state.write(' satisfies ', node as unknown as estree.Expression);
 | 
						|
		this[node.reference.type](node.reference, state);
 | 
						|
	},
 | 
						|
};
 | 
						|
 | 
						|
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>
 | 
						|
		? 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];
 | 
						|
 | 
						|
type ToKebab<T extends readonly string[]> = T extends readonly [
 | 
						|
	infer XO extends string
 | 
						|
]
 | 
						|
	? XO
 | 
						|
	: T extends readonly [
 | 
						|
			infer XH extends string,
 | 
						|
			...infer XR extends readonly string[]
 | 
						|
	  ]
 | 
						|
	? `${XH}${XR extends readonly string[] ? `-${ToKebab<XR>}` : ''}`
 | 
						|
	: '';
 | 
						|
 | 
						|
// @ts-ignore
 | 
						|
type ToPascal<T extends readonly string[]> = T extends readonly [
 | 
						|
	infer XH extends string,
 | 
						|
	...infer XR extends readonly string[]
 | 
						|
]
 | 
						|
	? `${Capitalize<XH>}${ToPascal<XR>}`
 | 
						|
	: '';
 | 
						|
 | 
						|
function h<T extends estree.Node>(
 | 
						|
	component: T['type'],
 | 
						|
	props: Omit<T, 'type'>
 | 
						|
): T {
 | 
						|
	const type = component.replace(/(?:^|-)([a-z])/g, (_, c) => c.toUpperCase());
 | 
						|
	return Object.assign(props || {}, { type }) as T;
 | 
						|
}
 | 
						|
 | 
						|
declare global {
 | 
						|
	namespace JSX {
 | 
						|
		type Element = estree.Node;
 | 
						|
		type ElementClass = never;
 | 
						|
		type ElementAttributesProperty = never;
 | 
						|
		type ElementChildrenAttribute = never;
 | 
						|
		type IntrinsicAttributes = never;
 | 
						|
		type IntrinsicClassAttributes<T> = never;
 | 
						|
		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`;
 | 
						|
	const metaStories = `${component.slice(0, -'.vue'.length)}.stories.meta`;
 | 
						|
	const hasMsw = existsSync(`${msw}.ts`);
 | 
						|
	const hasImplStories = existsSync(`${implStories}.ts`);
 | 
						|
	const hasMetaStories = existsSync(`${metaStories}.ts`);
 | 
						|
	const base = basename(component);
 | 
						|
	const dir = dirname(component);
 | 
						|
	const literal =
 | 
						|
		<literal
 | 
						|
			value={component
 | 
						|
				.slice('src/'.length, -'.vue'.length)
 | 
						|
				.replace(/\./g, '/')}
 | 
						|
		/> as estree.Literal;
 | 
						|
	const identifier =
 | 
						|
		<identifier
 | 
						|
			name={base
 | 
						|
				.slice(0, -'.vue'.length)
 | 
						|
				.replace(/[-.]|^(?=\d)/g, '_')
 | 
						|
				.replace(/(?<=^[^A-Z_]*$)/, '_')}
 | 
						|
		/> as estree.Identifier;
 | 
						|
	const parameters =
 | 
						|
		<object-expression
 | 
						|
			properties={[
 | 
						|
				<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,
 | 
						|
				...(hasMsw
 | 
						|
					? [
 | 
						|
							<property
 | 
						|
								key={<identifier name='msw' /> as estree.Identifier}
 | 
						|
								value={<identifier name='msw' /> as estree.Identifier}
 | 
						|
								kind={'init' as const}
 | 
						|
								shorthand
 | 
						|
							/> as estree.Property,
 | 
						|
					  ]
 | 
						|
					: []),
 | 
						|
			]}
 | 
						|
		/> as estree.ObjectExpression;
 | 
						|
	const program =
 | 
						|
		<program
 | 
						|
			body={[
 | 
						|
				<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,
 | 
						|
				...(hasMsw
 | 
						|
					? [
 | 
						|
							<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,
 | 
						|
					  ]
 | 
						|
					: []),
 | 
						|
				...(hasImplStories
 | 
						|
					? []
 | 
						|
					: [
 | 
						|
							<import-declaration
 | 
						|
								source={<literal value={`./${base}`} /> as estree.Literal}
 | 
						|
								specifiers={[
 | 
						|
									<import-default-specifier local={identifier} /> as estree.ImportDefaultSpecifier,
 | 
						|
								]}
 | 
						|
							/> as estree.ImportDeclaration,
 | 
						|
					  ]),
 | 
						|
				...(hasMetaStories
 | 
						|
					? [
 | 
						|
							<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,
 | 
						|
						]
 | 
						|
					: []),
 | 
						|
				<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
 | 
						|
									}
 | 
						|
									reference={<identifier name={`Meta<typeof ${identifier.name}>`} /> as estree.Identifier}
 | 
						|
								/> as estree.Expression
 | 
						|
							}
 | 
						|
						/> as estree.VariableDeclarator,
 | 
						|
					]}
 | 
						|
				/> as estree.VariableDeclaration,
 | 
						|
				...(hasImplStories
 | 
						|
					? []
 | 
						|
					: [
 | 
						|
							<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={
 | 
						|
																																			<object-expression
 | 
						|
																																				properties={[
 | 
						|
																																					<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
 | 
						|
																																										}
 | 
						|
																																									/> as estree.SpreadElement,
 | 
						|
																																								]}
 | 
						|
																																							/> as estree.ObjectExpression
 | 
						|
																																						}
 | 
						|
																																					/> as estree.ReturnStatement,
 | 
						|
																																				]}
 | 
						|
																																			/> as estree.BlockStatement
 | 
						|
																																		}
 | 
						|
																																	/> as estree.FunctionExpression
 | 
						|
																																}
 | 
						|
																																method
 | 
						|
																																kind={'init' as const}
 | 
						|
																															/> as estree.Property,
 | 
						|
																														]}
 | 
						|
																													/> 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
 | 
						|
																								}
 | 
						|
																							/> as estree.ReturnStatement,
 | 
						|
																						]}
 | 
						|
																					/> as estree.BlockStatement
 | 
						|
																				}
 | 
						|
																			/> as estree.FunctionExpression
 | 
						|
																		}
 | 
						|
																		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,
 | 
						|
			]}
 | 
						|
		/> as estree.Program;
 | 
						|
	return format(
 | 
						|
		'/* eslint-disable @typescript-eslint/explicit-function-return-type */\n' +
 | 
						|
			'/* eslint-disable import/no-default-export */\n' +
 | 
						|
			'/* eslint-disable import/no-duplicates */\n' +
 | 
						|
			generate(program, { generator }) +
 | 
						|
			(hasImplStories ? readFileSync(`${implStories}.ts`, 'utf-8') : ''),
 | 
						|
		{
 | 
						|
			parser: 'babel-ts',
 | 
						|
			singleQuote: true,
 | 
						|
			useTabs: true,
 | 
						|
		}
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
// glob('src/{components,pages,ui,widgets}/**/*.vue')
 | 
						|
(async () => {
 | 
						|
	const globs = await Promise.all([
 | 
						|
		glob('src/components/global/*.vue'),
 | 
						|
		glob('src/components/Mk{A,B}*.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/MkInviteCode.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));
 | 
						|
	}))
 | 
						|
})();
 |