mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 13:09:51 +01:00
Compare commits
7
Commits
4e586253c0
...
d88f7ad539
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d88f7ad539 | ||
|
|
b5d61cebb3 | ||
|
|
551108e047 | ||
|
|
5feec9bf1e | ||
|
|
313691d3e9 | ||
|
|
950c99d3d7 | ||
|
|
80cc74a945 |
No files matched your search
@@ -118,9 +118,6 @@
|
||||
},
|
||||
|
||||
"api": {
|
||||
"json": "JSON methods",
|
||||
"pb": "Protocol Buffer methods",
|
||||
"media": "Media methods",
|
||||
"no_docs": "This method has no documentation.",
|
||||
"test": "Test",
|
||||
"view_full": "View full types"
|
||||
|
||||
@@ -44,7 +44,12 @@ for (const line of bodyOriginal.split("\n")) {
|
||||
|
||||
.gutter {
|
||||
width: var(--space-2);
|
||||
background: linear-gradient(transparent, var(--gray-6) var(--space-4));
|
||||
background: linear-gradient(
|
||||
transparent,
|
||||
var(--gray-6) var(--space-4),
|
||||
var(--gray-6) calc(100% - var(--space-4)),
|
||||
transparent
|
||||
);
|
||||
|
||||
margin: 0 var(--space-4);
|
||||
}
|
||||
@@ -65,9 +70,6 @@ for (const line of bodyOriginal.split("\n")) {
|
||||
<div class="content">
|
||||
<MarkdownCustom {body} />
|
||||
|
||||
<!-- {"see" in macros && <ApiMacroSee {locale} value={macros.see} />}
|
||||
{"type" in macros && <ApiMacroType {locale} value={macros.type} />} -->
|
||||
|
||||
{
|
||||
Object.entries(macroComponents).map(([macro, Component]) => {
|
||||
if (!(macro in macros)) return null;
|
||||
|
||||
@@ -10,8 +10,4 @@ interface Props {
|
||||
const { apis, locale } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
apis
|
||||
.sort((a, b) => a.id.localeCompare(b.id))
|
||||
.map((api) => <ApiEntry {locale} {api} />)
|
||||
}
|
||||
{apis.map((api) => <ApiEntry {locale} {api} />)}
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { existsSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { readFile } from "fs/promises";
|
||||
import { Link } from "./Link";
|
||||
import { api } from "../core/api/dynamic";
|
||||
@@ -7,6 +7,23 @@ import HighlightedCodeBlock from "./HighlightedCodeBlock.astro";
|
||||
import { Text } from "./Text";
|
||||
import { ArrowBottomRightIcon } from "@radix-ui/react-icons";
|
||||
import { Code } from "./Code";
|
||||
import {
|
||||
createProgram,
|
||||
forEachChild,
|
||||
isArrayTypeNode,
|
||||
isEnumDeclaration,
|
||||
isExpressionWithTypeArguments,
|
||||
isInterfaceDeclaration,
|
||||
isMethodSignature,
|
||||
isPropertySignature,
|
||||
isTypeAliasDeclaration,
|
||||
isTypeNode,
|
||||
isTypeReferenceNode,
|
||||
type InterfaceDeclaration,
|
||||
type Node,
|
||||
type Symbol,
|
||||
type TypeNode,
|
||||
} from "typescript";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
@@ -16,62 +33,102 @@ interface Props {
|
||||
const { value, locale } = Astro.props;
|
||||
const strings = await api.strings(locale);
|
||||
|
||||
const lang = "ts";
|
||||
const roots = ["src/protos", "../closed/protos"];
|
||||
|
||||
const lang = "proto";
|
||||
const importPattern = /import "(.+)";/gm;
|
||||
const messagePattern = /message .+ {\s*\n(.+\s*\n)+}/gm;
|
||||
const [route, name] = value.split(".");
|
||||
const path = resolve(`${roots[0]}/${route}.ts`);
|
||||
|
||||
const explored = new Set<string>();
|
||||
let messages: string[] = [];
|
||||
const program = createProgram([path], {});
|
||||
const source = program.getSourceFile(path);
|
||||
|
||||
await pullMessages(`${roots[0]}/${value}.proto`);
|
||||
if (source === undefined) {
|
||||
throw new Error(`Could not find ${path}`);
|
||||
}
|
||||
|
||||
async function pullMessages(path: string) {
|
||||
if (explored.has(path)) return;
|
||||
const checker = program.getTypeChecker();
|
||||
const collected = new Map<string, string>();
|
||||
const visited = new Set<Symbol>();
|
||||
let declaration: InterfaceDeclaration | undefined;
|
||||
|
||||
explored.add(path);
|
||||
forEachChild(source, (node) => {
|
||||
if (isInterfaceDeclaration(node) && node.name.text === name) {
|
||||
declaration = node;
|
||||
}
|
||||
});
|
||||
|
||||
const file = await readFile(path);
|
||||
const content = file.toString();
|
||||
const imports = content.matchAll(importPattern);
|
||||
if (declaration === undefined) {
|
||||
throw new Error(`Could not find ${name} in ${path}`);
|
||||
}
|
||||
|
||||
for (const _import of imports) {
|
||||
for (const root of roots) {
|
||||
const path = `${root}/${_import[1]}`;
|
||||
function collectNodes(node: Node) {
|
||||
if (isInterfaceDeclaration(node)) {
|
||||
const symbol = checker.getSymbolAtLocation(node.name);
|
||||
|
||||
if (existsSync(path)) {
|
||||
await pullMessages(path);
|
||||
break;
|
||||
}
|
||||
if (symbol) {
|
||||
const text = node.getText();
|
||||
|
||||
collected.set(node.name.text, text);
|
||||
|
||||
node.members.forEach((member) => {
|
||||
if (isPropertySignature(member) && member.type) {
|
||||
collectTypes(member.type);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const matchedMessages = content.matchAll(messagePattern);
|
||||
forEachChild(node, collectNodes);
|
||||
}
|
||||
|
||||
for (const message of matchedMessages) {
|
||||
messages = [message[0], ...messages];
|
||||
function collectTypes(node: TypeNode) {
|
||||
if (isArrayTypeNode(node)) {
|
||||
collectTypes(node.elementType);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTypeReferenceNode(node)) {
|
||||
node.typeArguments?.forEach(collectTypes);
|
||||
}
|
||||
|
||||
const type = checker.getTypeAtLocation(node);
|
||||
const symbol = type.getSymbol();
|
||||
|
||||
if (!symbol || visited.has(symbol)) return;
|
||||
|
||||
visited.add(symbol);
|
||||
|
||||
const declarations = symbol.getDeclarations();
|
||||
|
||||
if (!declarations) return;
|
||||
|
||||
for (const declaration of declarations) {
|
||||
if (isInterfaceDeclaration(declaration)) {
|
||||
collected.set(declaration.name.text, declaration.getText());
|
||||
|
||||
declaration.members.forEach((member) => {
|
||||
if (isPropertySignature(member) && member.type) {
|
||||
collectTypes(member.type);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
forEachChild(declaration, (child) => {
|
||||
if (isTypeNode(child)) collectTypes(child);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const content = messages.join("\n\n");
|
||||
collectNodes(declaration);
|
||||
|
||||
const indexPattern = /^( +).+\.(.+ = \d+;)$/m;
|
||||
let content = "";
|
||||
|
||||
let stripped = "";
|
||||
|
||||
for (const line of content.split("\n")) {
|
||||
const matches = line.match(indexPattern);
|
||||
|
||||
if (matches === null) {
|
||||
stripped += `${line}\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
stripped += `${matches[1]}${matches[2]}\n`;
|
||||
for (const [, definition] of collected) {
|
||||
content += `${definition}\n\n`;
|
||||
}
|
||||
|
||||
stripped = stripped.replaceAll(" ", " ");
|
||||
content = content.replaceAll(/^export\s*/gm, "");
|
||||
content = content.trim();
|
||||
---
|
||||
|
||||
<style>
|
||||
@@ -136,7 +193,7 @@ stripped = stripped.replaceAll(" ", " ");
|
||||
</div>
|
||||
|
||||
<div class="types">
|
||||
<HighlightedCodeBlock {lang} content={stripped} />
|
||||
<HighlightedCodeBlock {lang} content={content} />
|
||||
|
||||
<div class="overlay">
|
||||
<Link underline="always" href="#" className="view-full">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import type { ComponentProps, FunctionComponent } from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import { classNames } from "../../core/ui/classNames";
|
||||
import { Code } from "../Code";
|
||||
import { Text } from "../Text";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
@@ -13,7 +14,8 @@ export function MarkdownCustom({ body, className, ...props }: MarkdownCustom) {
|
||||
<div className={classNames(className, styles.markdown)} {...props}>
|
||||
<Markdown
|
||||
components={{
|
||||
p: Text,
|
||||
p: Text as FunctionComponent<ComponentProps<"p">>,
|
||||
code: Code,
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Strings } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import type { Avatar } from "../../protos/avatar";
|
||||
import type { AvatarList } from "../../protos/avatar_list";
|
||||
import type { Avatars } from "../../protos/avatars";
|
||||
import type { Background } from "../../protos/background";
|
||||
import type { Backgrounds } from "../../protos/backgrounds";
|
||||
@@ -130,6 +131,16 @@ export abstract class AbstractAPI {
|
||||
return this._avatarsCache;
|
||||
}
|
||||
|
||||
private _avatarListCache: AvatarList | undefined;
|
||||
protected abstract _avatarList(): Promise<AvatarList>;
|
||||
async avatarList() {
|
||||
if (this._avatarListCache === undefined) {
|
||||
this._avatarListCache = await this._avatarList();
|
||||
}
|
||||
|
||||
return this._avatarListCache;
|
||||
}
|
||||
|
||||
private _backgroundCache: Record<string, Background> = {};
|
||||
protected abstract _background(id: string): Promise<Background>;
|
||||
async background(id: string) {
|
||||
|
||||
@@ -193,6 +193,13 @@ export class ServerAPI extends AbstractAPI {
|
||||
return { avatars };
|
||||
}
|
||||
|
||||
protected async _avatarList() {
|
||||
const avatars = await this.avatars();
|
||||
const list = Object.keys(avatars.avatars);
|
||||
|
||||
return { list };
|
||||
}
|
||||
|
||||
protected async _avatar(id: string) {
|
||||
const avatar = this.metadata.item(`ProfileAvatarEntity.${id}`);
|
||||
const name = avatar.name;
|
||||
|
||||
@@ -3,11 +3,9 @@ import "@radix-ui/colors/amber-dark.css";
|
||||
import { getCollection } from "astro:content";
|
||||
import ApiGroup from "../../../components/ApiGroup.astro";
|
||||
import ColorScale from "../../../components/ColorScale.astro";
|
||||
import { Heading } from "../../../components/Heading";
|
||||
import { Section } from "../../../components/Section";
|
||||
import SyntaxStyles from "../../../components/SyntaxStyles.astro";
|
||||
import ToolBanner from "../../../components/ToolBanner.astro";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { tools } from "../../../core/tools/tools";
|
||||
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||
|
||||
@@ -15,7 +13,6 @@ export { getStaticPaths } from "../index.astro";
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const collection = await getCollection("api");
|
||||
const strings = await api.strings(locale);
|
||||
|
||||
const paths: {
|
||||
path: string;
|
||||
@@ -32,28 +29,11 @@ paths.sort((a, b) => a.path.localeCompare(b.path));
|
||||
<ToolBanner {locale} tool={tools.api} slot="pre-app" />
|
||||
|
||||
<Section>
|
||||
<Heading size="2">{strings.api.json}</Heading>
|
||||
<ApiGroup
|
||||
{locale}
|
||||
apis={collection.filter((api) => api.id.endsWith(".json"))}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Heading size="2">{strings.api.pb}</Heading>
|
||||
<ApiGroup
|
||||
{locale}
|
||||
apis={collection.filter((api) => api.id.endsWith(".pb"))}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Heading size="2">{strings.api.media}</Heading>
|
||||
<ApiGroup
|
||||
{locale}
|
||||
apis={collection.filter(
|
||||
(api) => !api.id.endsWith(".json") && !api.id.endsWith(".pb"),
|
||||
)}
|
||||
apis={collection
|
||||
.filter((api) => !api.id.endsWith(".pb"))
|
||||
.sort((a, b) => a.id.localeCompare(b.id))}
|
||||
/>
|
||||
</Section>
|
||||
</BaseLayout>
|
||||
@@ -16,7 +16,7 @@ export const getStaticPaths = (async () => {
|
||||
*
|
||||
* @see avatars/list.json for all avatar ids.
|
||||
* @see avatars/[id].webp for avatar images.
|
||||
* @type avatar
|
||||
* @type avatar.Avatar
|
||||
*/
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const avatar = await api.avatar(params.id);
|
||||
|
||||
@@ -4,6 +4,12 @@ import { imageProxy } from "../../../core/api/imageProxy";
|
||||
|
||||
export { getStaticPaths } from "./[id].json";
|
||||
|
||||
/**
|
||||
* Returns an avatar image based on its `id` in the `webp` format.
|
||||
*
|
||||
* @see avatars/list.json for all avatar ids.
|
||||
* @see avatars/[id].json for data on all avatar.
|
||||
*/
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const avatar = await api.avatar(params.id);
|
||||
return await imageProxy(avatar.profile_avatar!.avatar);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
/**
|
||||
* Identical to `avatars/[id].json`, but returns data on all avatars at once,
|
||||
* mapped by `id`. Beware, the returned `JSON` is large.
|
||||
*
|
||||
* @type avatars.Avatars
|
||||
*/
|
||||
export async function GET() {
|
||||
const avatars = await api.avatars();
|
||||
return Response.json(avatars);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
/**
|
||||
* @type avatar_list.AvatarList
|
||||
*/
|
||||
export async function GET() {
|
||||
const { avatars } = await api.avatars();
|
||||
const list = avatars.map((avatar) => avatar.name);
|
||||
const list = await api.avatarList();
|
||||
return Response.json(list);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package blitzkit;
|
||||
|
||||
message AvatarList {
|
||||
repeated string list = 1;
|
||||
}
|
||||
Reference in new issue
Block a user