chore: better dependencies management
updated and pinned dependencies added validation for external deps conflicts removed tl-reference
This commit is contained in:
parent
c591f96b81
commit
0471310382
64 changed files with 1727 additions and 17521 deletions
|
@ -1,7 +1,6 @@
|
|||
private/
|
||||
docs/
|
||||
dist/
|
||||
scripts/
|
||||
|
||||
packages/tl/errors.js
|
||||
packages/tl/errors.d.ts
|
||||
|
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
v18.16.0
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**PROJECT IS ABANDONED**
|
||||
|
||||
If anyone would like to take over it, feel free to DM me.
|
||||
If anyone would like to take over it, feel free to DM me.
|
||||
Meanwhile, check out other MTProto libraries in JS, or, better yet, use TDLib for god's sake.
|
||||
|
||||
> ⚠️ **Warning**: While this library is WIP, storage
|
||||
|
@ -15,6 +15,8 @@ Meanwhile, check out other MTProto libraries in JS, or, better yet, use TDLib fo
|
|||
|
||||
## Installation
|
||||
|
||||
Currently, target TypeScript major is 5.1, target Node major is 18.
|
||||
|
||||
mtcute is currently only published in my private NPM registry.
|
||||
|
||||
You can install it by running:
|
||||
|
@ -28,9 +30,10 @@ npm install @mtcute/node # or any other package
|
|||
## Setting up for development:
|
||||
|
||||
```bash
|
||||
fnm use # or `nvm use`
|
||||
git clone https://github.com/mtcute/mtcute
|
||||
cd mtcute
|
||||
pnpm install
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# generate code from tl schema
|
||||
cd packages/tl
|
||||
|
|
56
package.json
56
package.json
|
@ -4,38 +4,44 @@
|
|||
"version": "1.0.0",
|
||||
"description": "Type-safe library for MTProto (Telegram API) for browser and NodeJS",
|
||||
"license": "LGPL-3.0",
|
||||
"author": "Alina Sireneva <me@tei.su>",
|
||||
"author": "Alina Sireneva <alina@tei.su>",
|
||||
"scripts": {
|
||||
"test-all": "pnpm run -r test",
|
||||
"lint": "eslint \"packages/**/*.ts\"",
|
||||
"postinstall": "node scripts/validate-deps-versions.mjs",
|
||||
"test:all": "pnpm run -r test",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write \"packages/**/*.ts\"",
|
||||
"publish-all": "node scripts/publish.js all",
|
||||
"docs": "pnpm run -r docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-gyp": "^9.0.0"
|
||||
"node-gyp": "9.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/node": "^14.14.22",
|
||||
"@types/node-forge": "^1.0.2",
|
||||
"@types/ws": "^7.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||
"@typescript-eslint/parser": "^4.15.0",
|
||||
"chai": "^4.3.6",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"mocha": "^9.2.2",
|
||||
"node-forge": "^1.3.1",
|
||||
"prettier": "^2.6.2",
|
||||
"ts-node": "^10.8.1",
|
||||
"typedoc": "^0.23.10",
|
||||
"typescript": "^4.7.4",
|
||||
"nyc": "^15.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"dotenv-flow": "^3.2.0",
|
||||
"glob": "^8.0.1",
|
||||
"semver": "^7.3.7"
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.16.0",
|
||||
"@types/node-forge": "1.3.2",
|
||||
"@types/ws": "8.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.8",
|
||||
"@typescript-eslint/parser": "5.59.8",
|
||||
"chai": "4.3.7",
|
||||
"eslint": "8.42.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-import-resolver-typescript": "3.5.5",
|
||||
"eslint-plugin-ascii": "1.0.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-simple-import-sort": "10.0.0",
|
||||
"mocha": "10.2.0",
|
||||
"node-forge": "1.3.1",
|
||||
"prettier": "2.8.8",
|
||||
"ts-node": "10.9.1",
|
||||
"typedoc": "0.24.7",
|
||||
"typescript": "5.0.4",
|
||||
"nyc": "15.1.0",
|
||||
"rimraf": "5.0.1",
|
||||
"dotenv-flow": "3.2.0",
|
||||
"glob": "10.2.6",
|
||||
"semver": "7.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,12 @@
|
|||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/long": "^4.0.1",
|
||||
"@types/node": "^15.12.1",
|
||||
"@types/node": "18.16.0",
|
||||
"@mtcute/tl": "workspace:^150.0.0",
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"@mtcute/file-id": "workspace:^1.0.0",
|
||||
"eager-async-pool": "^1.0.0",
|
||||
"file-type": "^16.2.0",
|
||||
"long": "^4.0.0"
|
||||
"eager-async-pool": "1.0.0",
|
||||
"file-type": "18.5.0",
|
||||
"long": "5.2.3"
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "Core functions and base MTProto client",
|
||||
"author": "Alina Sireneva <me@tei.su>",
|
||||
"author": "Alina Sireneva <alina@tei.su>",
|
||||
"license": "LGPL-3.0",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
|
@ -18,18 +18,17 @@
|
|||
"./storage/json-file.js": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^15.12.1",
|
||||
"@types/events": "^3.0.0",
|
||||
"@types/long": "^4.0.2",
|
||||
"@types/node": "18.16.0",
|
||||
"@types/events": "3.0.0",
|
||||
"@mtcute/tl": "workspace:^150.0.0",
|
||||
"@mtcute/tl-runtime": "workspace:^1.0.0",
|
||||
"big-integer": "1.6.48",
|
||||
"long": "^4.0.0",
|
||||
"big-integer": "1.6.51",
|
||||
"long": "5.2.3",
|
||||
"events": "3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/dispatcher": "workspace:^1.0.0",
|
||||
"@types/ws": "^7.4.1",
|
||||
"ws": "^7.4.4"
|
||||
"@types/ws": "8.5.4",
|
||||
"ws": "8.13.0"
|
||||
}
|
||||
}
|
|
@ -15,6 +15,6 @@
|
|||
"@mtcute/tl": "workspace:^150.0.0",
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"@mtcute/client": "workspace:^1.0.0",
|
||||
"events": "^3.2.0"
|
||||
"events": "3.2.0"
|
||||
}
|
||||
}
|
|
@ -15,9 +15,6 @@
|
|||
"@mtcute/tl": "workspace:^150.0.0",
|
||||
"@mtcute/tl-runtime": "workspace:^1.0.0",
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"long": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/long": "^4.0.1"
|
||||
"long": "5.2.3"
|
||||
}
|
||||
}
|
|
@ -15,10 +15,9 @@
|
|||
"dependencies": {
|
||||
"@mtcute/tl": "workspace:^150.0.0",
|
||||
"htmlparser2": "^6.0.1",
|
||||
"long": "^4.0.0"
|
||||
"long": "5.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/client": "workspace:^1.0.0",
|
||||
"@types/long": "^4.0.1"
|
||||
"@mtcute/client": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
|
@ -14,10 +14,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@mtcute/tl": "workspace:^150.0.0",
|
||||
"long": "^4.0.0"
|
||||
"long": "5.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/client": "workspace:^1.0.0",
|
||||
"@types/long": "^4.0.1"
|
||||
"@mtcute/client": "workspace:^1.0.0"
|
||||
}
|
||||
}
|
|
@ -12,6 +12,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"big-integer": "1.6.48"
|
||||
"big-integer": "1.6.51"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"ip6": "^0.2.6"
|
||||
"ip6": "0.2.10"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,11 @@
|
|||
"dependencies": {
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"@mtcute/tl-runtime": "workspace:^1.0.0",
|
||||
"better-sqlite3": "^7.4.0",
|
||||
"long": "^4.0.0"
|
||||
"better-sqlite3": "8.4.0",
|
||||
"long": "5.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/dispatcher": "workspace:^1.0.0",
|
||||
"@types/sqlite3": "^3.1.7",
|
||||
"@types/better-sqlite3": "^5.4.1",
|
||||
"@types/long": "^4.0.1"
|
||||
"@types/better-sqlite3": "7.6.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
"./platform/gzip.js": "./platform/gzip.web.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"long": "4.0.0",
|
||||
"pako": "2.0.2"
|
||||
"long": "5.2.3",
|
||||
"pako": "2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/long": "4.0.1",
|
||||
"@types/pako": "^1.0.1"
|
||||
"@types/pako": "2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,17 +16,14 @@
|
|||
"fetch-and-gen": "yarn fetch-api && yarn gen-code"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/long": "^4.0.1",
|
||||
"long": "^4.0.0"
|
||||
"long": "5.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mtcute/tl-utils": "workspace:^1.0.0",
|
||||
"@mtcute/core": "workspace:^1.0.0",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"csv-parser": "^3.0.0",
|
||||
"js-yaml": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"@types/node-fetch": "^2.5.10"
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"csv-parser": "3.0.0",
|
||||
"js-yaml": "4.1.0"
|
||||
},
|
||||
"typedoc": {
|
||||
"entryPoint": "index.d.ts"
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
import { readdir, readFile, writeFile } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import cheerio from 'cheerio'
|
||||
import fetch from 'node-fetch'
|
||||
import * as readline from 'readline'
|
||||
import {
|
||||
CORE_DOMAIN,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { TlError, TlErrors } from '@mtcute/tl-utils'
|
||||
import fetch from 'node-fetch'
|
||||
// @ts-ignore
|
||||
import csvParser from 'csv-parser'
|
||||
import { writeFile } from 'fs/promises'
|
||||
|
@ -142,6 +141,10 @@ async function fetchFromTelegram(errors: TlErrors) {
|
|||
|
||||
async function fetchFromTelethon(errors: TlErrors) {
|
||||
const csv = await fetch(ERRORS_PAGE_TELETHON)
|
||||
if (!csv.body) {
|
||||
throw new Error('No body in response')
|
||||
}
|
||||
|
||||
const parser = csvParser()
|
||||
|
||||
function addError(name: string, codes: string, description: string): void {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import fetch, { RequestInit } from 'node-fetch'
|
||||
|
||||
export async function fetchRetry(
|
||||
url: string,
|
||||
params?: RequestInit,
|
||||
|
|
2210
pnpm-lock.yaml
2210
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
27
scripts/utils.mjs
Normal file
27
scripts/utils.mjs
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { fileURLToPath } from 'url'
|
||||
import { join, dirname } from 'path'
|
||||
import { readdir, readFile } from 'fs/promises'
|
||||
|
||||
export const rootDir = join(dirname(fileURLToPath(import.meta.url)), '../');
|
||||
|
||||
export async function tryParsePackageJson(packageName) {
|
||||
try {
|
||||
const path = packageName === '$root' ? rootDir : join(rootDir, 'packages', packageName)
|
||||
|
||||
return JSON.parse(
|
||||
await readFile(join(path, 'package.json'), 'utf-8')
|
||||
)
|
||||
} catch (e) {
|
||||
if (e.code !== 'ENOENT') throw e
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPackageJsons(includeRoot = false) {
|
||||
const packages = (await readdir(join(rootDir, 'packages')))
|
||||
.filter((s) => !s.startsWith('.'))
|
||||
|
||||
if (includeRoot) packages.push('$root')
|
||||
|
||||
return Promise.all(packages.map(tryParsePackageJson))
|
||||
}
|
58
scripts/validate-deps-versions.mjs
Normal file
58
scripts/validate-deps-versions.mjs
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { fileURLToPath } from 'url'
|
||||
import { getPackageJsons } from './utils.mjs'
|
||||
import semver from 'semver'
|
||||
|
||||
export async function validateDepsVersions() {
|
||||
const packageJsons = await getPackageJsons()
|
||||
|
||||
const versions = {}
|
||||
const errors = []
|
||||
|
||||
packageJsons.forEach((json) => {
|
||||
function check(key) {
|
||||
const deps = json[key]
|
||||
if (!deps) return
|
||||
|
||||
Object.entries(deps).forEach(([depName, depVersions]) => {
|
||||
if (depName.startsWith('@mtcute/')) return
|
||||
|
||||
if (!versions[depName]) {
|
||||
versions[depName] = {}
|
||||
}
|
||||
|
||||
Object.entries(versions[depName]).forEach(([pkgName, pkgDepVersions]) => {
|
||||
if (!semver.satisfies(depVersions, pkgDepVersions)) {
|
||||
errors.push(
|
||||
`- at ${json.name} -> ${key} has ${depName}@${depVersions}, but ${pkgName} has @${pkgDepVersions}`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
versions[depName][json.name] = depVersions
|
||||
})
|
||||
}
|
||||
|
||||
check('dependencies')
|
||||
check('devDependencies')
|
||||
check('peerDependencies')
|
||||
check('optionalDependencies')
|
||||
})
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.log('⚠️ Found external dependencies mismatch:')
|
||||
errors.forEach((err) => console.log(err))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('✅ All external dependencies match!')
|
||||
}
|
||||
|
||||
|
||||
if (import.meta.url.startsWith('file:')) {
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (process.argv[1] === modulePath) {
|
||||
validateDepsVersions().catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
|
3
tl-reference/.gitignore
vendored
3
tl-reference/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
node_modules/
|
||||
.cache/
|
||||
public
|
|
@ -1,6 +0,0 @@
|
|||
# TL Reference
|
||||
|
||||
[https://mt.tei.su/tl](https://mt.tei.su/tl)
|
||||
|
||||
Small Gatsby application that allows easy browsing
|
||||
through Telegram APIs.
|
|
@ -1,16 +0,0 @@
|
|||
This directory contains historical data about the TL schema.
|
||||
|
||||
That is, it contains history of the schema over time and pre-calculated difference
|
||||
between consecutive versions.
|
||||
|
||||
The files are generated with scripts and are not in the repository.
|
||||
|
||||
To generate the files, execute scripts from `../scripts`:
|
||||
1. `fetch-history.js`
|
||||
2. `fetch-older-layers.js`
|
||||
3. `generate-type-history.js`
|
||||
|
||||
To update files:
|
||||
1. `fetch-history.js`
|
||||
2. `generate-type-history.js`
|
||||
|
1
tl-reference/data/diffs/.gitignore
vendored
1
tl-reference/data/diffs/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
*.json
|
2
tl-reference/data/history/.gitignore
vendored
2
tl-reference/data/history/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*.json
|
||||
*.txt
|
1
tl-reference/data/types/.gitignore
vendored
1
tl-reference/data/types/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
*.json
|
|
@ -1,34 +0,0 @@
|
|||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: 'TL reference',
|
||||
},
|
||||
pathPrefix: '/tl',
|
||||
plugins: [
|
||||
'gatsby-theme-material-ui',
|
||||
'gatsby-transformer-json',
|
||||
'gatsby-plugin-sass',
|
||||
'gatsby-plugin-react-helmet',
|
||||
{
|
||||
resolve: 'gatsby-plugin-nprogress',
|
||||
options: {
|
||||
color: 'white'
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
path: './data',
|
||||
ignore: [
|
||||
'./data/history/last-fetched.json',
|
||||
'./data/README.md',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-plugin-layout',
|
||||
options: {
|
||||
component: require.resolve('./src/layout.tsx'),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
const rawSchema = require('../packages/tl/raw-schema')
|
||||
const rawErrors = require('../packages/tl/raw-errors')
|
||||
const path = require('path')
|
||||
const { convertToArrays, prepareData } = require('./scripts/prepare-data')
|
||||
|
||||
const TL_NODE_TYPE = 'TlObject'
|
||||
|
||||
exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
|
||||
const { createNode } = actions
|
||||
|
||||
createNode({
|
||||
id: createNodeId(`CurrentTlSchema`),
|
||||
parent: null,
|
||||
children: [],
|
||||
layer: rawSchema.apiLayer,
|
||||
internal: {
|
||||
type: 'CurrentTlSchema',
|
||||
content: JSON.stringify({ layer: rawSchema.apiLayer }),
|
||||
contentDigest: createContentDigest({ layer: rawSchema.apiLayer }),
|
||||
},
|
||||
})
|
||||
|
||||
function createForNs(ns, prefix = '') {
|
||||
ns.classes.forEach((cls) => {
|
||||
createNode({
|
||||
...cls,
|
||||
id: createNodeId(`${TL_NODE_TYPE}-class-${prefix}${cls.name}`),
|
||||
parent: null,
|
||||
children: [],
|
||||
type: 'class',
|
||||
prefix,
|
||||
internal: {
|
||||
type: TL_NODE_TYPE,
|
||||
content: JSON.stringify(cls),
|
||||
contentDigest: createContentDigest(cls),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
ns.methods.forEach((cls) => {
|
||||
createNode({
|
||||
...cls,
|
||||
id: createNodeId(`${TL_NODE_TYPE}-method-${prefix}${cls.name}`),
|
||||
parent: null,
|
||||
children: [],
|
||||
type: 'method',
|
||||
prefix,
|
||||
internal: {
|
||||
type: TL_NODE_TYPE,
|
||||
content: JSON.stringify(cls),
|
||||
contentDigest: createContentDigest(cls),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
ns.unions.forEach((cls) => {
|
||||
createNode({
|
||||
...cls,
|
||||
name: cls.type,
|
||||
id: createNodeId(`${TL_NODE_TYPE}-union-${prefix}${cls.type}`),
|
||||
parent: null,
|
||||
children: [],
|
||||
type: 'union',
|
||||
prefix,
|
||||
internal: {
|
||||
type: TL_NODE_TYPE,
|
||||
content: JSON.stringify(cls),
|
||||
contentDigest: createContentDigest(cls),
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const mtproto = convertToArrays(rawSchema.mtproto)
|
||||
const api = convertToArrays(rawSchema.api)
|
||||
|
||||
prepareData(mtproto)
|
||||
prepareData(api)
|
||||
|
||||
createForNs(mtproto, 'mtproto/')
|
||||
createForNs(api)
|
||||
}
|
||||
|
||||
const TLObject = path.resolve('./src/templates/tl-object.tsx')
|
||||
const TlTypesList = path.resolve('./src/templates/tl-types-list.tsx')
|
||||
const TypeHistory = path.resolve('./src/templates/type-history.tsx')
|
||||
const TlLayer = path.resolve('./src/templates/tl-layer.tsx')
|
||||
const TlDiff = path.resolve('./src/templates/tl-diff.tsx')
|
||||
|
||||
exports.createPages = async ({ graphql, actions }) => {
|
||||
const result = await graphql(`
|
||||
query {
|
||||
allTlObject {
|
||||
nodes {
|
||||
prefix
|
||||
name
|
||||
type
|
||||
namespace
|
||||
subtypes
|
||||
}
|
||||
}
|
||||
|
||||
allTypesJson {
|
||||
nodes {
|
||||
uid
|
||||
name
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
allHistoryJson {
|
||||
nodes {
|
||||
layer
|
||||
rev
|
||||
prev
|
||||
next
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
result.data.allTlObject.nodes.forEach((node) => {
|
||||
actions.createPage({
|
||||
path: `${node.prefix}${node.type}/${node.name}`,
|
||||
component: TLObject,
|
||||
context: {
|
||||
...node,
|
||||
hasSubtypes: !!node.subtypes,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
result.data.allTypesJson.nodes.forEach((node) => {
|
||||
actions.createPage({
|
||||
path: `/history/${node.type}/${node.name}`,
|
||||
component: TypeHistory,
|
||||
context: node
|
||||
})
|
||||
})
|
||||
|
||||
result.data.allHistoryJson.nodes.forEach((node) => {
|
||||
actions.createPage({
|
||||
path: `/history/layer${node.layer}${node.rev > 0 ? `-rev${node.rev}` : ''}`,
|
||||
component: TlLayer,
|
||||
context: node
|
||||
})
|
||||
})
|
||||
|
||||
const result2 = await graphql(`
|
||||
query {
|
||||
allTlObject {
|
||||
group(field: prefix) {
|
||||
fieldValue
|
||||
nodes {
|
||||
namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
result2.data.allTlObject.group.forEach(({ fieldValue: prefix, nodes }) => {
|
||||
const namespaces = [...new Set(nodes.map((i) => i.namespace))]
|
||||
|
||||
namespaces.forEach((ns) => {
|
||||
let namespace
|
||||
if (ns === '$root') namespace = ''
|
||||
else namespace = '/' + ns
|
||||
;['types', 'methods'].forEach((type) => {
|
||||
actions.createPage({
|
||||
path: `${prefix}${type}${namespace}`,
|
||||
component: TlTypesList,
|
||||
context: {
|
||||
prefix,
|
||||
ns,
|
||||
type,
|
||||
isTypes: type === 'types',
|
||||
isMethods: type === 'methods',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -36,7 +36,6 @@
|
|||
"lodash.throttle": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"node-fetch": "^2.6.1",
|
||||
"marked": "^2.0.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
// in js because also used in scripts
|
||||
|
||||
function createTlSchemaIndex(it) {
|
||||
let ret = {}
|
||||
it.classes.forEach((obj) => {
|
||||
obj.uid = 'c_' + obj.name
|
||||
obj._type = 'classes'
|
||||
ret[obj.uid] = obj
|
||||
})
|
||||
it.methods.forEach((obj) => {
|
||||
obj.uid = 'm_' + obj.name
|
||||
obj._type = 'methods'
|
||||
ret[obj.uid] = obj
|
||||
})
|
||||
it.unions.forEach((obj) => {
|
||||
obj.uid = 'u_' + obj.type
|
||||
obj._type = 'unions'
|
||||
ret[obj.uid] = obj
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
function createTlConstructorDifference(old, mod) {
|
||||
const localDiff = {}
|
||||
|
||||
const argDiff = {
|
||||
added: [],
|
||||
removed: [],
|
||||
modified: [],
|
||||
}
|
||||
|
||||
const { oldIndex, modIndex } = (function () {
|
||||
function createIndex(obj) {
|
||||
const ret = {}
|
||||
if (obj.arguments)
|
||||
obj.arguments.forEach((arg) => (ret[arg.name] = arg))
|
||||
return ret
|
||||
}
|
||||
|
||||
return {
|
||||
oldIndex: createIndex(old),
|
||||
modIndex: createIndex(mod),
|
||||
}
|
||||
})()
|
||||
|
||||
Object.keys(modIndex).forEach((argName) => {
|
||||
if (!(argName in oldIndex)) {
|
||||
argDiff.added.push(modIndex[argName])
|
||||
} else {
|
||||
const old = oldIndex[argName]
|
||||
const mod = modIndex[argName]
|
||||
if (
|
||||
old.type !== mod.type ||
|
||||
old.optional !== mod.optional ||
|
||||
old.predicate !== mod.predicate
|
||||
) {
|
||||
argDiff.modified.push({
|
||||
name: argName,
|
||||
old: old,
|
||||
new: mod,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(oldIndex).forEach((argName) => {
|
||||
if (!(argName in modIndex)) {
|
||||
argDiff.removed.push(oldIndex[argName])
|
||||
}
|
||||
})
|
||||
|
||||
if (
|
||||
argDiff.removed.length ||
|
||||
argDiff.added.length ||
|
||||
argDiff.modified.length
|
||||
) {
|
||||
localDiff.arguments = argDiff
|
||||
}
|
||||
|
||||
if (old.id !== mod.id) localDiff.id = { old: old.id, new: mod.id }
|
||||
if (old.type !== mod.type)
|
||||
localDiff.type = { old: old.type, new: mod.type }
|
||||
if (old.returns !== mod.returns)
|
||||
localDiff.returns = { old: old.returns, new: mod.returns }
|
||||
|
||||
|
||||
if (Object.keys(localDiff).length) return localDiff
|
||||
return null
|
||||
}
|
||||
|
||||
function createTlUnionsDifference(old, mod) {
|
||||
const diff = {
|
||||
added: [],
|
||||
removed: [],
|
||||
}
|
||||
|
||||
const { oldIndex, modIndex } = (function () {
|
||||
function createIndex(obj) {
|
||||
const ret = {}
|
||||
obj.subtypes.forEach((typ) => (ret[typ] = 1))
|
||||
return ret
|
||||
}
|
||||
|
||||
return {
|
||||
oldIndex: createIndex(old),
|
||||
modIndex: createIndex(mod),
|
||||
}
|
||||
})()
|
||||
|
||||
Object.keys(modIndex).forEach((typ) => {
|
||||
if (!(typ in oldIndex)) {
|
||||
diff.added.push(typ)
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(oldIndex).forEach((typ) => {
|
||||
if (!(typ in modIndex)) {
|
||||
diff.removed.push(typ)
|
||||
}
|
||||
})
|
||||
|
||||
if (diff.added.length || diff.removed.length) {
|
||||
return { subtypes: diff }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function createTlSchemaDifference(old, mod) {
|
||||
const diff = {
|
||||
added: { classes: [], methods: [], unions: [] },
|
||||
removed: { classes: [], methods: [], unions: [] },
|
||||
modified: { classes: [], methods: [], unions: [] },
|
||||
}
|
||||
|
||||
old = old.tl
|
||||
mod = mod.tl
|
||||
|
||||
// create index for both old and mod
|
||||
const oldIndex = createTlSchemaIndex(old)
|
||||
const modIndex = createTlSchemaIndex(mod)
|
||||
|
||||
Object.keys(modIndex).forEach((uid) => {
|
||||
const type = modIndex[uid]._type
|
||||
|
||||
if (!(uid in oldIndex)) {
|
||||
diff.added[type].push(modIndex[uid])
|
||||
} else {
|
||||
const old = oldIndex[uid]
|
||||
const mod = modIndex[uid]
|
||||
|
||||
let localDiff
|
||||
if (type === 'unions') {
|
||||
localDiff = createTlUnionsDifference(old, mod)
|
||||
} else {
|
||||
localDiff = createTlConstructorDifference(old, mod)
|
||||
}
|
||||
|
||||
if (localDiff) {
|
||||
localDiff.name = old.name || old.type
|
||||
diff.modified[type].push(localDiff)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(oldIndex).forEach((uid) => {
|
||||
if (!(uid in modIndex)) {
|
||||
diff.removed[oldIndex[uid]._type].push(oldIndex[uid])
|
||||
}
|
||||
})
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createTlSchemaIndex,
|
||||
createTlConstructorDifference,
|
||||
createTlUnionsDifference,
|
||||
createTlSchemaDifference,
|
||||
}
|
|
@ -1,279 +0,0 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { convertTlToJson } = require('../../packages/tl/scripts/generate-schema')
|
||||
const fetch = require('node-fetch')
|
||||
const qs = require('querystring')
|
||||
const { convertToArrays } = require('./prepare-data')
|
||||
|
||||
const UNIX_0 = '1970-01-01T00:00:00Z'
|
||||
const CURRENT_FILE = 'Telegram/Resources/tl/api.tl'
|
||||
const FILES = [
|
||||
'Telegram/SourceFiles/mtproto/scheme.tl',
|
||||
'Telegram/Resources/scheme.tl',
|
||||
CURRENT_FILE,
|
||||
]
|
||||
|
||||
async function getLastFetched() {
|
||||
return fs.promises
|
||||
.readFile(
|
||||
path.join(__dirname, '../data/history/last-fetched.txt'),
|
||||
'utf8'
|
||||
)
|
||||
.then((res) => JSON.parse(res))
|
||||
.catch(() =>
|
||||
FILES.reduce((a, b) => {
|
||||
a[b] = UNIX_0
|
||||
return a
|
||||
}, {})
|
||||
)
|
||||
}
|
||||
|
||||
async function updateLastFetched(file, time) {
|
||||
return getLastFetched().then((state) =>
|
||||
fs.promises.writeFile(
|
||||
path.join(__dirname, '../data/history/last-fetched.txt'),
|
||||
JSON.stringify({
|
||||
...state,
|
||||
[file]: time,
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async function getCounts() {
|
||||
return fs.promises
|
||||
.readFile(path.join(__dirname, '../data/history/counts.txt'), 'utf8')
|
||||
.then((res) => JSON.parse(res))
|
||||
.catch(() => ({}))
|
||||
}
|
||||
|
||||
async function setCounts(obj) {
|
||||
return fs.promises.writeFile(
|
||||
path.join(__dirname, '../data/history/counts.txt'),
|
||||
JSON.stringify(obj)
|
||||
)
|
||||
}
|
||||
|
||||
async function getFileContent(file, commit) {
|
||||
return fetch(
|
||||
`https://raw.githubusercontent.com/telegramdesktop/tdesktop/${commit}/${file}`
|
||||
).then((r) => r.text())
|
||||
}
|
||||
|
||||
async function parseRemoteTl(file, commit) {
|
||||
let content = await getFileContent(file, commit)
|
||||
if (content === '404: Not Found') return null
|
||||
|
||||
let layer = (function () {
|
||||
const m = content.match(/^\/\/ LAYER (\d+)/m)
|
||||
if (m) return m[1]
|
||||
return null
|
||||
})()
|
||||
if (!layer) {
|
||||
// older files did not contain layer number in comment.
|
||||
if (content.match(/invokeWithLayer#da9b0d0d/)) {
|
||||
// if this is present, then the layer number is available in
|
||||
// Telegram/SourceFiles/mtproto/mtpCoreTypes.h
|
||||
let mtpCoreTypes = await getFileContent(
|
||||
'Telegram/SourceFiles/mtproto/mtpCoreTypes.h',
|
||||
commit
|
||||
)
|
||||
if (mtpCoreTypes === '404: Not Found') {
|
||||
mtpCoreTypes = await getFileContent(
|
||||
'Telegram/SourceFiles/mtproto/core_types.h',
|
||||
commit
|
||||
)
|
||||
}
|
||||
const m = mtpCoreTypes.match(
|
||||
/^static const mtpPrime mtpCurrentLayer = (\d+);$/m
|
||||
)
|
||||
if (!m)
|
||||
throw new Error(
|
||||
`Could not determine layer number for file ${file} at commit ${commit}`
|
||||
)
|
||||
layer = m[1]
|
||||
} else {
|
||||
// even older files on ancient layers
|
||||
// layer number is the largest available invokeWithLayerN constructor
|
||||
let max = 0
|
||||
content.replace(/invokeWithLayer(\d+)#[0-f]+/g, (_, $1) => {
|
||||
$1 = parseInt($1)
|
||||
if ($1 > max) max = $1
|
||||
})
|
||||
if (max === 0)
|
||||
throw new Error(
|
||||
`Could not determine layer number for file ${file} at commit ${commit}`
|
||||
)
|
||||
layer = max + ''
|
||||
}
|
||||
}
|
||||
|
||||
if (content.match(/bad_server_salt#/)) {
|
||||
// this is an older file that contained both mtproto and api
|
||||
// since we are only interested in api, remove the mtproto part
|
||||
const lines = content.split('\n')
|
||||
const apiIdx = lines.indexOf('///////// Main application API')
|
||||
if (apiIdx === -1)
|
||||
throw new Error('Could not find split point for combined file')
|
||||
content = lines.slice(apiIdx).join('\n')
|
||||
}
|
||||
|
||||
return {
|
||||
layer,
|
||||
content,
|
||||
tl: convertToArrays(await convertTlToJson(content, 'api', true)),
|
||||
}
|
||||
}
|
||||
|
||||
function fileSafeDateFormat(date) {
|
||||
date = new Date(date)
|
||||
return date
|
||||
.toISOString()
|
||||
.replace(/[\-:]|\.\d\d\d/g, '')
|
||||
.split('T')[0]
|
||||
}
|
||||
|
||||
function shortSha(sha) {
|
||||
return sha.substr(0, 7)
|
||||
}
|
||||
|
||||
async function fetchHistory(file, since, counts, defaultPrev = null, defaultPrevFile = null) {
|
||||
const history = await (async function () {
|
||||
const ret = []
|
||||
let page = 1
|
||||
while (true) {
|
||||
const chunk = await fetch(
|
||||
`https://api.github.com/repos/telegramdesktop/tdesktop/commits?` +
|
||||
qs.stringify({
|
||||
since,
|
||||
path: file,
|
||||
per_page: 100,
|
||||
page,
|
||||
})
|
||||
).then((r) => r.json())
|
||||
|
||||
if (!chunk.length) break
|
||||
|
||||
ret.push(...chunk)
|
||||
page += 1
|
||||
}
|
||||
|
||||
return ret
|
||||
})()
|
||||
|
||||
// should not happen
|
||||
if (history.length === 0) throw new Error('history is empty')
|
||||
|
||||
const filename = (schema, commit) =>
|
||||
`layer${schema.layer}-${fileSafeDateFormat(
|
||||
commit.commit.committer.date
|
||||
)}-${shortSha(commit.sha)}.json`
|
||||
|
||||
const uid = (schema, commit) => `${schema.layer}_${shortSha(commit.sha)}`
|
||||
|
||||
function writeSchemaToFile(schema, commit) {
|
||||
return fs.promises.writeFile(
|
||||
path.join(__dirname, `../data/history/${filename(schema, commit)}`),
|
||||
JSON.stringify({
|
||||
// layer is ever-incrementing, sha is random, so no collisions
|
||||
uid: uid(schema, commit),
|
||||
tl: JSON.stringify(schema.tl),
|
||||
layer: parseInt(schema.layer),
|
||||
rev:
|
||||
schema.layer in counts
|
||||
? ++counts[schema.layer]
|
||||
: (counts[schema.layer] = 0),
|
||||
content: schema.content,
|
||||
prev: schema.prev ? schema.prev : defaultPrev,
|
||||
prevFile: schema.prevFile ? schema.prevFile : defaultPrevFile,
|
||||
source: {
|
||||
file,
|
||||
date: commit.commit.committer.date,
|
||||
commit: commit.sha,
|
||||
message: commit.message,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
let base = history.pop()
|
||||
let baseSchema = await parseRemoteTl(file, base.sha)
|
||||
let baseFilename = () => filename(baseSchema, base)
|
||||
|
||||
try {
|
||||
await fs.promises.access(
|
||||
path.join(__dirname, `../data/history/${baseFilename()}`),
|
||||
fs.F_OK
|
||||
)
|
||||
} catch (e) {
|
||||
await writeSchemaToFile(baseSchema, base)
|
||||
}
|
||||
|
||||
while (history.length) {
|
||||
const next = history.pop()
|
||||
const nextSchema = await parseRemoteTl(file, next.sha)
|
||||
if (!nextSchema) break
|
||||
|
||||
nextSchema.prev = uid(baseSchema, base)
|
||||
nextSchema.prevFile = baseFilename()
|
||||
base = next
|
||||
baseSchema = nextSchema
|
||||
await updateLastFetched(file, base.commit.committer.date)
|
||||
await setCounts(counts)
|
||||
await writeSchemaToFile(baseSchema, base)
|
||||
console.log(
|
||||
'Fetched commit %s, file %s (%s)',
|
||||
shortSha(base.sha),
|
||||
file,
|
||||
base.commit.committer.date
|
||||
)
|
||||
}
|
||||
|
||||
if (file !== CURRENT_FILE) {
|
||||
await updateLastFetched(file, `DONE:${uid(baseSchema, base)}:${baseFilename()}`)
|
||||
}
|
||||
|
||||
console.log('No more commits for %s', file)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let last = await getLastFetched()
|
||||
const counts = await getCounts()
|
||||
|
||||
for (let i = 0; i < FILES.length; i++) {
|
||||
const file = FILES[i]
|
||||
const prev = FILES[i - 1]
|
||||
if (!last[file].startsWith('DONE')) {
|
||||
let parent = prev ? last[prev].split(':')[1] : null
|
||||
let parentFile = prev ? last[prev].split(':')[2] : null
|
||||
|
||||
await fetchHistory(file, last[file], counts, parent, parentFile)
|
||||
|
||||
last = await getLastFetched()
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Creating reverse links ("next" field)')
|
||||
for (const file of await fs.promises.readdir(
|
||||
path.join(__dirname, '../data/history')
|
||||
)) {
|
||||
if (!file.startsWith('layer')) continue
|
||||
|
||||
const fullPath = path.join(__dirname, '../data/history', file)
|
||||
const json = JSON.parse(await fs.promises.readFile(fullPath, 'utf-8'))
|
||||
if (json.prev) {
|
||||
const parentPath = path.join(
|
||||
__dirname,
|
||||
'../data/history',
|
||||
json.prevFile
|
||||
)
|
||||
const parentJson = JSON.parse(
|
||||
await fs.promises.readFile(parentPath, 'utf-8')
|
||||
)
|
||||
parentJson.next = json.uid
|
||||
await fs.promises.writeFile(parentPath, JSON.stringify(parentJson))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
|
@ -1,112 +0,0 @@
|
|||
const {
|
||||
convertTlToJson,
|
||||
convertJsonToTl,
|
||||
} = require('../../packages/tl/scripts/generate-schema')
|
||||
const fetch = require('node-fetch')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const cheerio = require('cheerio')
|
||||
const { convertToArrays } = require('./prepare-data')
|
||||
|
||||
const FETCH_UP_TO = 13
|
||||
|
||||
async function fetchAvailableLayers() {
|
||||
return fetch('https://core.telegram.org/schema')
|
||||
.then((i) => i.text())
|
||||
.then((html) => {
|
||||
const $ = cheerio.load(html)
|
||||
|
||||
const links = $('a[href^="?layer="]').toArray().map((it) => it.attribs.href)
|
||||
|
||||
let ret = []
|
||||
links.forEach((link) => {
|
||||
let m = link.match(/\?layer=(\d+)/)
|
||||
if (m) {
|
||||
let layer = parseInt(m[1])
|
||||
if (layer === 1 || layer > FETCH_UP_TO) return
|
||||
|
||||
ret.push(layer)
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchFromLayer(layer) {
|
||||
const html = await fetch('https://core.telegram.org/schema', {
|
||||
headers: {
|
||||
cookie: `stel_dev_layer=${layer}`,
|
||||
},
|
||||
}).then((i) => i.text())
|
||||
|
||||
const $ = cheerio.load(html)
|
||||
return $('.page_scheme code').text()
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')
|
||||
.replace(/&/g, '&')
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// find first non-"old" layer, for linking
|
||||
let firstNext
|
||||
for (const file of fs.readdirSync(
|
||||
path.join(__dirname, '../data/history')
|
||||
)) {
|
||||
if (file.startsWith(`layer${FETCH_UP_TO + 1}-`)) {
|
||||
const json = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(__dirname, `../data/history/${file}`),
|
||||
'utf-8'
|
||||
)
|
||||
)
|
||||
firstNext = json.uid
|
||||
|
||||
json.prev = `${FETCH_UP_TO}_FROM_WEBSITE`
|
||||
json.prevFile = `layer${FETCH_UP_TO}-19700101-0000000.json`
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, `../data/history/${file}`),
|
||||
JSON.stringify(json)
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const layers = await fetchAvailableLayers()
|
||||
|
||||
for (const l of layers) {
|
||||
const tl = await fetchFromLayer(l)
|
||||
const data = await convertTlToJson(tl, 'api', true)
|
||||
|
||||
await fs.promises.writeFile(
|
||||
path.join(
|
||||
__dirname,
|
||||
`../data/history/layer${l}-19700101-0000000.json`
|
||||
),
|
||||
JSON.stringify({
|
||||
// layer is ever-incrementing, sha is random, so no collisions
|
||||
uid: `${l}_FROM_WEBSITE`,
|
||||
tl: JSON.stringify(convertToArrays(data)),
|
||||
layer: l,
|
||||
rev: 0,
|
||||
content: tl,
|
||||
prev: l === 2 ? null : `${l - 1}_FROM_WEBSITE`,
|
||||
prevFile:
|
||||
l === 2 ? null : `layer${l - 1}-19700101-0000000.json`,
|
||||
next: l === FETCH_UP_TO ? firstNext : `${l + 1}_FROM_WEBSITE`,
|
||||
source: {
|
||||
website: true,
|
||||
file: '',
|
||||
date: '1970-01-01T00:00:00Z',
|
||||
commit: '',
|
||||
message: '',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
console.log(`Fetched layer ${l}`)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
|
@ -1,59 +0,0 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { createTlSchemaDifference } = require('./diff-utils')
|
||||
|
||||
function generateDiffs() {
|
||||
// first, load all schemas in memory (expensive, but who cares)
|
||||
const schemas = []
|
||||
|
||||
for (const file of fs.readdirSync(
|
||||
path.join(__dirname, '../data/history')
|
||||
)) {
|
||||
if (!file.startsWith('layer')) continue
|
||||
|
||||
const fullPath = path.join(__dirname, '../data/history', file)
|
||||
const json = JSON.parse(fs.readFileSync(fullPath, 'utf-8'))
|
||||
|
||||
json.tl = JSON.parse(json.tl)
|
||||
delete json.content // useless here
|
||||
schemas.push(json)
|
||||
}
|
||||
|
||||
schemas.sort((a, b) => {
|
||||
if (a.layer !== b.layer) return b.layer - a.layer
|
||||
|
||||
return a.source.date < b.source.date ? 1 : -1
|
||||
})
|
||||
|
||||
// create diff between consecutive pairs.
|
||||
// that way, we can diff any two given schemas by simply
|
||||
// merging the diff using `seq`
|
||||
|
||||
let prev = schemas.pop()
|
||||
let seq = 0
|
||||
|
||||
while (schemas.length) {
|
||||
const current = schemas.pop()
|
||||
|
||||
const uid = `${prev.layer}r${prev.rev}-${current.layer}r${current.rev}`
|
||||
const diff = createTlSchemaDifference(prev, current)
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, `../data/diffs/${uid}.json`), JSON.stringify({
|
||||
seq: seq++,
|
||||
uid,
|
||||
diff: JSON.stringify(diff),
|
||||
prev: {
|
||||
layer: prev.layer,
|
||||
rev: prev.rev
|
||||
},
|
||||
new: {
|
||||
layer: current.layer,
|
||||
rev: current.rev
|
||||
}
|
||||
}))
|
||||
|
||||
prev = current
|
||||
}
|
||||
}
|
||||
|
||||
generateDiffs()
|
|
@ -1,151 +0,0 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const {
|
||||
createTlSchemaIndex,
|
||||
createTlUnionsDifference,
|
||||
createTlConstructorDifference,
|
||||
} = require('./diff-utils')
|
||||
|
||||
function generateTypeHistory() {
|
||||
// first, load all schemas in memory (expensive, but who cares)
|
||||
const schemas = []
|
||||
|
||||
for (const file of fs.readdirSync(
|
||||
path.join(__dirname, '../data/history')
|
||||
)) {
|
||||
if (!file.startsWith('layer')) continue
|
||||
|
||||
const fullPath = path.join(__dirname, '../data/history', file)
|
||||
const json = JSON.parse(fs.readFileSync(fullPath, 'utf-8'))
|
||||
|
||||
json.tl = JSON.parse(json.tl)
|
||||
delete json.content // useless here
|
||||
schemas.push(json)
|
||||
}
|
||||
|
||||
// create a set of all types that have ever existed
|
||||
const types = new Set()
|
||||
|
||||
for (const s of schemas) {
|
||||
s.tl.classes.forEach((it) => types.add('c_' + it.name))
|
||||
s.tl.methods.forEach((it) => types.add('m_' + it.name))
|
||||
s.tl.unions.forEach((it) => types.add('u_' + it.type))
|
||||
}
|
||||
|
||||
function getSchemaInfo(schema) {
|
||||
return {
|
||||
...schema.source,
|
||||
layer: schema.layer,
|
||||
rev: schema.rev
|
||||
}
|
||||
}
|
||||
|
||||
schemas.sort((a, b) => {
|
||||
if (a.layer !== b.layer) return b.layer - a.layer
|
||||
|
||||
return a.source.date < b.source.date ? 1 : -1
|
||||
})
|
||||
|
||||
const history = {}
|
||||
|
||||
const base = schemas.pop()
|
||||
const baseSchemaInfo = getSchemaInfo(base)
|
||||
|
||||
let prevIndex = createTlSchemaIndex(base.tl)
|
||||
|
||||
Object.entries(prevIndex).forEach(([uid, item]) => {
|
||||
if (!(history[uid])) history[uid] = []
|
||||
|
||||
// type was in the first scheme, assume it was added there
|
||||
history[uid].push({
|
||||
action: 'added',
|
||||
in: baseSchemaInfo,
|
||||
diff: item
|
||||
})
|
||||
})
|
||||
|
||||
// for every schema, check changes for each type
|
||||
|
||||
while (schemas.length) {
|
||||
const schema = schemas.pop()
|
||||
const schemaInfo = getSchemaInfo(schema)
|
||||
const newIndex = createTlSchemaIndex(schema.tl)
|
||||
|
||||
types.forEach((uid) => {
|
||||
if (!(uid in history)) history[uid] = []
|
||||
|
||||
if (!(uid in prevIndex) && uid in newIndex) {
|
||||
// type added
|
||||
history[uid].push({
|
||||
action: 'added',
|
||||
in: schemaInfo,
|
||||
diff: newIndex[uid]
|
||||
})
|
||||
}
|
||||
|
||||
if (uid in prevIndex && !(uid in newIndex)) {
|
||||
// type removed
|
||||
history[uid].push({
|
||||
action: 'removed',
|
||||
in: schemaInfo,
|
||||
})
|
||||
}
|
||||
|
||||
if (uid in prevIndex && uid in newIndex) {
|
||||
// modified (maybe)
|
||||
|
||||
let diff
|
||||
if (uid.match(/^u_/)) {
|
||||
// union
|
||||
diff = createTlUnionsDifference(
|
||||
prevIndex[uid],
|
||||
newIndex[uid]
|
||||
)
|
||||
} else {
|
||||
diff = createTlConstructorDifference(
|
||||
prevIndex[uid],
|
||||
newIndex[uid]
|
||||
)
|
||||
}
|
||||
|
||||
if (diff) {
|
||||
history[uid].push({
|
||||
action: 'modified',
|
||||
in: schemaInfo,
|
||||
diff,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
prevIndex = newIndex
|
||||
}
|
||||
|
||||
Object.entries(history).forEach(([uid, history]) => {
|
||||
if (!history.length) return
|
||||
|
||||
history.forEach((it) => {
|
||||
// for simpler graphql queries
|
||||
if (it.diff) it.diff = JSON.stringify(it.diff)
|
||||
})
|
||||
|
||||
// anti-chronological order
|
||||
history.reverse()
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, `../data/types/${uid}.json`),
|
||||
JSON.stringify({
|
||||
uid,
|
||||
type: {
|
||||
c: 'class',
|
||||
m: 'method',
|
||||
u: 'union'
|
||||
}[uid[0]],
|
||||
name: uid.slice(2),
|
||||
history,
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
generateTypeHistory()
|
|
@ -1,176 +0,0 @@
|
|||
// converts object-based schema to array-based
|
||||
function convertToArrays(ns) {
|
||||
const ret = {
|
||||
classes: [],
|
||||
methods: [],
|
||||
unions: [],
|
||||
}
|
||||
|
||||
Object.entries(ns).forEach(([ns, content]) => {
|
||||
const prefix = ns === '$root' ? '' : `${ns}.`
|
||||
|
||||
content.classes.forEach((cls) => {
|
||||
cls.rawName = cls.name
|
||||
cls.name = prefix + cls.name
|
||||
cls.namespace = ns
|
||||
ret.classes.push(cls)
|
||||
})
|
||||
|
||||
content.methods.forEach((cls) => {
|
||||
cls.rawName = cls.name
|
||||
cls.name = prefix + cls.name
|
||||
cls.namespace = ns
|
||||
ret.methods.push(cls)
|
||||
})
|
||||
|
||||
content.unions.forEach((cls) => {
|
||||
cls.rawName = cls.type
|
||||
cls.type = prefix + cls.type
|
||||
cls.namespace = ns
|
||||
ret.unions.push(cls)
|
||||
})
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
const marked = require('marked')
|
||||
const cheerio = require('cheerio')
|
||||
|
||||
const pascalToCamel = (s) => s[0].toLowerCase() + s.substring(1)
|
||||
const camelToSnake = (str) =>
|
||||
str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||
const camelToPascal = (s) => s[0].toUpperCase() + s.substring(1)
|
||||
|
||||
function renderDescription(description) {
|
||||
return marked(
|
||||
description.replace(/{@link (.+?)}/g, (_, name) => {
|
||||
if (name.startsWith('tl.')) {
|
||||
let [ns, type] = name.substring(3).split('.')
|
||||
if (!type) {
|
||||
type = ns
|
||||
ns = undefined
|
||||
}
|
||||
|
||||
let path, displayName, m
|
||||
if ((m = type.match(/^Raw([A-Za-z0-9_]+?)(Request)?$/))) {
|
||||
const [, name, isMethod] = m
|
||||
path = `${ns === 'mtproto' ? ns : ''}/${
|
||||
isMethod ? 'method' : 'class'
|
||||
}/${ns !== 'mtproto' ? ns + '.' : ''}${pascalToCamel(name)}`
|
||||
displayName =
|
||||
(ns ? ns + (ns === 'mtproto' ? '/' : '.') : '') +
|
||||
pascalToCamel(name)
|
||||
} else if ((m = type.match(/^Type([A-Za-z0-9_]+?)$/))) {
|
||||
path = `${ns === 'mtproto' ? ns : ''}/union/${
|
||||
ns !== 'mtproto' ? ns + '.' : ''
|
||||
}${m[1]}`
|
||||
displayName =
|
||||
(ns ? ns + (ns === 'mtproto' ? '/' : '.') : '') +
|
||||
pascalToCamel(name)
|
||||
}
|
||||
|
||||
if (path) {
|
||||
return `[${displayName}](/${path})`
|
||||
}
|
||||
}
|
||||
return `\`${name}\``
|
||||
})
|
||||
).replace(/<(\/)?p>/g, '<$1div>') // replace <p> with <div>
|
||||
}
|
||||
|
||||
function prepareData(data) {
|
||||
Object.values(data).forEach((arr) =>
|
||||
arr.forEach((item) => {
|
||||
// add hex constructor id
|
||||
if (item.id) item.tlId = item.id.toString(16).padStart(8, '0')
|
||||
|
||||
// raw non-array type for usages count
|
||||
if (item.arguments) item.arguments.forEach((arg) => {
|
||||
arg.rawType = arg.type.replace(/\[]$/, '')
|
||||
})
|
||||
if (item.returns) item.rawReturns = item.returns.replace(/\[]$/, '')
|
||||
|
||||
// add typescript types for the item and arguments
|
||||
// basically copy-pasted from generate-types.js
|
||||
const prefix_ = item.prefix === 'mtproto/' ? 'mt_' : ''
|
||||
let baseTypePrefix =
|
||||
item.prefix === 'mtproto/' ? 'tl.mtproto.' : 'tl.'
|
||||
|
||||
const makePascalCaseNotNamespace = (type) => {
|
||||
let split = type.split('.')
|
||||
let name = split.pop()
|
||||
let ns = split
|
||||
|
||||
if (!ns.length) {
|
||||
if (name[0].match(/[A-Z]/))
|
||||
// this is union/alias
|
||||
return 'Type' + name
|
||||
|
||||
return 'Raw' + camelToPascal(name)
|
||||
}
|
||||
if (name[0].match(/[A-Z]/)) return ns.join('.') + '.Type' + name
|
||||
return ns.join('.') + '.Raw' + camelToPascal(name)
|
||||
}
|
||||
const fullTypeName = (type) => {
|
||||
if (type === 'X') return 'any'
|
||||
if (type[0] === '%') type = type.substring(1)
|
||||
if (prefix_ === 'mt_' && type === 'Object') return 'tl.TlObject'
|
||||
if (
|
||||
type === 'number' ||
|
||||
type === 'any' ||
|
||||
type === 'Long' ||
|
||||
type === 'RawLong' ||
|
||||
type === 'Int128' ||
|
||||
type === 'Int256' ||
|
||||
type === 'Double' ||
|
||||
type === 'string' ||
|
||||
type === 'Buffer' ||
|
||||
type.match(/^(boolean|true|false)$/)
|
||||
)
|
||||
return type
|
||||
if (type.endsWith('[]')) {
|
||||
let wrap = type.substr(0, type.length - 2)
|
||||
return fullTypeName(wrap) + '[]'
|
||||
}
|
||||
|
||||
return baseTypePrefix + makePascalCaseNotNamespace(type)
|
||||
}
|
||||
|
||||
if (item.subtypes) {
|
||||
item.ts = 'Type' + item.rawName
|
||||
} else {
|
||||
item.ts =
|
||||
'Raw' +
|
||||
camelToPascal(item.rawName) +
|
||||
(item.returns ? 'Request' : '')
|
||||
item.underscore = prefix_ + item.name
|
||||
}
|
||||
|
||||
// render descriptions in markdown
|
||||
if (item.description) {
|
||||
item.description = renderDescription(item.description)
|
||||
item.descriptionExcerpt = cheerio.load(item.description).root().text().trim()
|
||||
if (item.descriptionExcerpt.length > 100) {
|
||||
const words = item.descriptionExcerpt.split(' ')
|
||||
let i = 0
|
||||
let result = words[i++]
|
||||
|
||||
while (result.length < 90) {
|
||||
result += ' ' + words[i++]
|
||||
}
|
||||
|
||||
item.descriptionExcerpt = result + '...'
|
||||
}
|
||||
}
|
||||
if (item.arguments)
|
||||
item.arguments.forEach((arg) => {
|
||||
if (arg.description)
|
||||
arg.description = renderDescription(arg.description)
|
||||
arg.ts = fullTypeName(arg.type)
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = { convertToArrays, prepareData }
|
|
@ -1,76 +0,0 @@
|
|||
import {
|
||||
createStyles,
|
||||
fade,
|
||||
InputBase,
|
||||
InputBaseProps,
|
||||
makeStyles,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import React from 'react'
|
||||
import SearchIcon from '@material-ui/icons/Search'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
search: {
|
||||
position: 'relative',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: fade(theme.palette.common.white, 0.15),
|
||||
'&:hover': {
|
||||
backgroundColor: fade(theme.palette.common.white, 0.25),
|
||||
},
|
||||
marginRight: theme.spacing(2),
|
||||
marginLeft: 0,
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
marginLeft: theme.spacing(3),
|
||||
width: 'auto',
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
padding: theme.spacing(0, 2),
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
inputRoot: {
|
||||
color: 'inherit',
|
||||
},
|
||||
inputInput: {
|
||||
padding: theme.spacing(1, 1, 1, 0),
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
|
||||
transition: theme.transitions.create('width'),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '40ch',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export function ActionBarSearchField(
|
||||
params: Partial<InputBaseProps> & { inputRef?: React.Ref<any> }
|
||||
): React.ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<div className={classes.search}>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<InputBase
|
||||
ref={params.inputRef}
|
||||
placeholder="Search…"
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput,
|
||||
}}
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
{...params}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import Fuse from 'fuse.js'
|
||||
import React from 'react'
|
||||
|
||||
function highlight(
|
||||
value: string,
|
||||
ranges: ReadonlyArray<Fuse.RangeTuple>,
|
||||
className: string,
|
||||
pos = ranges.length
|
||||
): React.ReactElement {
|
||||
const pair = ranges[pos - 1]
|
||||
|
||||
return pair ? (
|
||||
<>
|
||||
{highlight(value.substring(0, pair[0]), ranges, className, pos - 1)}
|
||||
<span className={className}>
|
||||
{value.substring(pair[0], pair[1] + 1)}
|
||||
</span>
|
||||
{value.substring(pair[1] + 1)}
|
||||
</>
|
||||
) : (
|
||||
<span>{value}</span>
|
||||
)
|
||||
}
|
||||
|
||||
export function FuseHighlight({
|
||||
matches,
|
||||
value,
|
||||
className,
|
||||
}: {
|
||||
matches: ReadonlyArray<Fuse.FuseResultMatch>
|
||||
value: string
|
||||
className: string
|
||||
}): React.ReactElement {
|
||||
return highlight(value, matches![0].indices, className)
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
import { graphql, Link, useStaticQuery } from 'gatsby'
|
||||
import { ChangeEvent, useState } from 'react'
|
||||
import { ExtendedTlObject, GraphqlAllResponse } from '../types'
|
||||
import SearchIcon from '@material-ui/icons/Search'
|
||||
import {
|
||||
Avatar,
|
||||
createStyles,
|
||||
Fade,
|
||||
fade,
|
||||
InputBase,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Popper,
|
||||
Theme,
|
||||
Button,
|
||||
ClickAwayListener,
|
||||
} from '@material-ui/core'
|
||||
import React from 'react'
|
||||
import { useLocalState } from '../hooks/use-local-state'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
import blue from '@material-ui/core/colors/blue'
|
||||
import red from '@material-ui/core/colors/red'
|
||||
import yellow from '@material-ui/core/colors/yellow'
|
||||
|
||||
import UnionIcon from '@material-ui/icons/AccountTree'
|
||||
import ClassIcon from '@material-ui/icons/Class'
|
||||
import FunctionsIcon from '@material-ui/icons/Functions'
|
||||
import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'
|
||||
import { useFuse } from '../hooks/use-fuse'
|
||||
import { FuseHighlight } from './fuse-highlight'
|
||||
import { ActionBarSearchField } from './actionbar-search-field'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
popup: {
|
||||
display: 'flex',
|
||||
height: 250,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
popupEmpty: {
|
||||
padding: theme.spacing(2),
|
||||
flex: '1 1 auto',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
popupEmptyIcon: {
|
||||
fontSize: '48px',
|
||||
display: 'block',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
popupList: {
|
||||
width: '100%',
|
||||
},
|
||||
popupListItem: {
|
||||
padding: theme.spacing(0, 2),
|
||||
},
|
||||
searchItemMatch: {
|
||||
color: theme.palette.type === 'dark' ? blue[300] : blue[700],
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export function GlobalSearchField({ isMobile }: { isMobile: boolean }): React.ReactElement {
|
||||
const classes = useStyles()
|
||||
const allObjects: {
|
||||
allTlObject: GraphqlAllResponse<ExtendedTlObject>
|
||||
} = useStaticQuery(graphql`
|
||||
query {
|
||||
allTlObject {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const [includeMtproto, setIncludeMtproto] = useLocalState('mtproto', false)
|
||||
const { hits, query, onSearch } = useFuse(
|
||||
allObjects.allTlObject.edges,
|
||||
{
|
||||
keys: ['node.name'],
|
||||
includeMatches: true,
|
||||
threshold: 0.3,
|
||||
},
|
||||
{ limit: 25 },
|
||||
includeMtproto ? undefined : (it) => it.node.prefix !== 'mtproto/'
|
||||
)
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const notFound = () => (
|
||||
<>
|
||||
<ErrorOutlineIcon className={classes.popupEmptyIcon} />
|
||||
Nothing found
|
||||
{!includeMtproto && (
|
||||
<Button
|
||||
variant="text"
|
||||
size="small"
|
||||
style={{
|
||||
margin: '4px auto',
|
||||
}}
|
||||
onClick={() => {
|
||||
setIncludeMtproto(true)
|
||||
}}
|
||||
>
|
||||
Retry including MTProto objects
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
const emptyField = () => (
|
||||
<>
|
||||
<SearchIcon className={classes.popupEmptyIcon} />
|
||||
Start typing...
|
||||
</>
|
||||
)
|
||||
|
||||
const renderSearchItem = (
|
||||
node: ExtendedTlObject,
|
||||
matches: ReadonlyArray<Fuse.FuseResultMatch>
|
||||
) => (
|
||||
<ListItem
|
||||
button
|
||||
divider
|
||||
component={Link}
|
||||
to={`/${node.prefix}${node.type}/${node.name}`}
|
||||
className={classes.popupListItem}
|
||||
onClick={() => setOpen(false)}
|
||||
key={node.id}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor:
|
||||
node.type === 'class'
|
||||
? blue[600]
|
||||
: node.type === 'method'
|
||||
? red[600]
|
||||
: yellow[700],
|
||||
}}
|
||||
>
|
||||
{node.type === 'class' ? (
|
||||
<ClassIcon />
|
||||
) : node.type === 'method' ? (
|
||||
<FunctionsIcon />
|
||||
) : (
|
||||
<UnionIcon />
|
||||
)}
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<>
|
||||
{node.prefix}
|
||||
<FuseHighlight
|
||||
matches={matches}
|
||||
value={node.name}
|
||||
className={classes.searchItemMatch}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
secondary={node.type}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
|
||||
const popupContent = (
|
||||
<Paper className={classes.popup}>
|
||||
{query.length <= 1 || !hits.length ? (
|
||||
<div className={classes.popupEmpty}>
|
||||
{query.length <= 1 ? emptyField() : notFound()}
|
||||
</div>
|
||||
) : (
|
||||
<List disablePadding dense className={classes.popupList}>
|
||||
{hits.map(({ item: { node }, matches }) =>
|
||||
renderSearchItem(node, matches!)
|
||||
)}
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
variant="text"
|
||||
size="small"
|
||||
style={{
|
||||
margin: '4px auto',
|
||||
}}
|
||||
onClick={() => {
|
||||
setIncludeMtproto(!includeMtproto)
|
||||
}}
|
||||
>
|
||||
{includeMtproto ? 'Hide' : 'Include'} MTProto
|
||||
objects
|
||||
</Button>
|
||||
</div>
|
||||
</List>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
|
||||
return (
|
||||
<ClickAwayListener onClickAway={() => setOpen(false)}>
|
||||
<>
|
||||
<ActionBarSearchField
|
||||
inputRef={setAnchorEl}
|
||||
autoComplete="off"
|
||||
onFocus={() => setOpen(true)}
|
||||
onBlur={() => setOpen(false)}
|
||||
onChange={onSearch}
|
||||
/>
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
placement="bottom"
|
||||
transition
|
||||
style={{
|
||||
width: isMobile ? '100%' : anchorEl?.clientWidth,
|
||||
zIndex: 9999,
|
||||
}}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Fade {...TransitionProps} timeout={350}>
|
||||
{popupContent}
|
||||
</Fade>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
</ClickAwayListener>
|
||||
)
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
import { Link as MuiLink } from '@material-ui/core'
|
||||
import { Link } from 'gatsby'
|
||||
import React from 'react'
|
||||
import { ExtendedTlObject } from '../../types'
|
||||
|
||||
|
||||
export function LinkToTl(name: string, history?: boolean): React.ReactElement
|
||||
export function LinkToTl(obj: ExtendedTlObject, history?: boolean): React.ReactElement
|
||||
export function LinkToTl(
|
||||
prefix: string,
|
||||
type: string,
|
||||
name: string,
|
||||
history?: boolean
|
||||
): React.ReactElement
|
||||
export function LinkToTl(
|
||||
prefix: string | ExtendedTlObject,
|
||||
type?: string | boolean,
|
||||
name?: string,
|
||||
history?: boolean
|
||||
): React.ReactElement {
|
||||
if (typeof prefix !== 'string') {
|
||||
history = !!type
|
||||
type = prefix.type
|
||||
name = prefix.name
|
||||
prefix = prefix.prefix
|
||||
}
|
||||
|
||||
// this kind of invocation is used in parameters table and for return type
|
||||
if ((!type || typeof type === 'boolean') && !name) {
|
||||
const fullType = prefix
|
||||
|
||||
// core types
|
||||
if (
|
||||
fullType === 'number' ||
|
||||
fullType === 'Long' ||
|
||||
fullType === 'Int128' ||
|
||||
fullType === 'Int256' ||
|
||||
fullType === 'Double' ||
|
||||
fullType === 'string' ||
|
||||
fullType === 'Buffer' ||
|
||||
fullType === 'boolean' ||
|
||||
fullType === 'true' ||
|
||||
fullType === 'any' ||
|
||||
fullType === '$FlagsBitField'
|
||||
) {
|
||||
return (
|
||||
<MuiLink component={Link} to="/#core-types">
|
||||
{fullType === '$FlagsBitField' ? 'TlFlags' : fullType}
|
||||
</MuiLink>
|
||||
)
|
||||
}
|
||||
|
||||
// array
|
||||
if (fullType.substr(fullType.length - 2) === '[]') {
|
||||
return <>{LinkToTl(fullType.substr(0, fullType.length - 2))}[]</>
|
||||
}
|
||||
|
||||
// must be union since this is from parameters type
|
||||
history = !!type
|
||||
prefix = ''
|
||||
type = 'union'
|
||||
name = fullType
|
||||
}
|
||||
|
||||
if (history) type = 'history/' + type
|
||||
|
||||
return (
|
||||
<MuiLink component={Link} to={`/${prefix}${type}/${name}`}>
|
||||
{prefix}
|
||||
{name}
|
||||
</MuiLink>
|
||||
)
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
import { ExtendedTlObject } from '../../types'
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from '@material-ui/core'
|
||||
import { LinkToTl } from './link-to-tl'
|
||||
import { Description } from '../page'
|
||||
import React from 'react'
|
||||
|
||||
import { green, red, blue } from '@material-ui/core/colors'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
table: {
|
||||
'& th, & td': {
|
||||
fontSize: 15,
|
||||
},
|
||||
},
|
||||
mono: {
|
||||
fontFamily: 'Fira Mono, Consolas, monospace',
|
||||
},
|
||||
bold: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
changed: {
|
||||
fontWeight: 500,
|
||||
border: 'none',
|
||||
width: 100,
|
||||
},
|
||||
added: {
|
||||
backgroundColor:
|
||||
theme.palette.type === 'light' ? green[100] : green[900],
|
||||
color: theme.palette.type === 'light' ? green[900] : green[100],
|
||||
},
|
||||
modified: {
|
||||
backgroundColor:
|
||||
theme.palette.type === 'light' ? blue[100] : blue[900],
|
||||
color: theme.palette.type === 'light' ? blue[900] : blue[100],
|
||||
},
|
||||
removed: {
|
||||
backgroundColor:
|
||||
theme.palette.type === 'light' ? red[100] : red[900],
|
||||
color: theme.palette.type === 'light' ? red[900] : red[100],
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export function ObjectParameters({
|
||||
obj,
|
||||
diff,
|
||||
history,
|
||||
}: {
|
||||
obj: ExtendedTlObject
|
||||
diff?: boolean
|
||||
history?: boolean
|
||||
}): JSX.Element {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{diff && <TableCell>Change</TableCell>}
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{obj.arguments.map((arg) => (
|
||||
<TableRow key={arg.name} className={arg.className}>
|
||||
{diff && (
|
||||
<TableCell
|
||||
className={clsx(
|
||||
classes.changed,
|
||||
classes[arg.changed!]
|
||||
)}
|
||||
>
|
||||
{arg.changed}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<code
|
||||
className={
|
||||
!arg.optional &&
|
||||
arg.type !== '$FlagsBitField'
|
||||
? classes.bold
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{arg.name}
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell className={classes.mono}>
|
||||
{arg.optional ? (
|
||||
<span title={arg.predicate}>
|
||||
{LinkToTl(arg.type, history)}?
|
||||
</span>
|
||||
) : (
|
||||
LinkToTl(arg.type, history)
|
||||
)}
|
||||
</TableCell>
|
||||
<Description
|
||||
description={arg.description}
|
||||
component={TableCell}
|
||||
/>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
import { ExtendedTlObject } from '../../types'
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useCodeArea } from '../../hooks/use-code-area'
|
||||
|
||||
export function ObjectTsCode({
|
||||
obj,
|
||||
children,
|
||||
}: {
|
||||
obj: ExtendedTlObject
|
||||
children?: ExtendedTlObject[]
|
||||
}): JSX.Element {
|
||||
const code = useCodeArea()
|
||||
|
||||
const entities: ReactNode[] = []
|
||||
if (obj.type === 'union') {
|
||||
entities.push(
|
||||
code.keyword('export type'),
|
||||
' ',
|
||||
code.identifier(obj.ts),
|
||||
' ='
|
||||
)
|
||||
|
||||
children!.forEach((it) => {
|
||||
const ns =
|
||||
it.namespace === '$root'
|
||||
? it.prefix === 'mtproto/'
|
||||
? 'mtproto.'
|
||||
: ''
|
||||
: it.namespace + '.'
|
||||
|
||||
entities.push('\n | ', code.typeName(`tl.${ns}${it.ts}`))
|
||||
})
|
||||
} else {
|
||||
entities.push(
|
||||
code.keyword('export interface'),
|
||||
' ',
|
||||
code.identifier(obj.ts),
|
||||
' {\n ',
|
||||
code.property('_'),
|
||||
': ',
|
||||
code.string(
|
||||
`'${obj.prefix === 'mtproto/' ? 'mt_' : ''}${obj.name}'`
|
||||
)
|
||||
)
|
||||
|
||||
obj.arguments.forEach((arg) => {
|
||||
if (arg.type === '$FlagsBitField') {
|
||||
return entities.push(
|
||||
code.comment(
|
||||
`\n // ${arg.name}: TlFlags // handled automatically`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
entities.push(
|
||||
'\n ',
|
||||
code.property(arg.name),
|
||||
`${arg.optional ? '?' : ''}: `,
|
||||
code.typeName(arg.ts)
|
||||
)
|
||||
|
||||
if (arg.predicate) {
|
||||
entities.push(
|
||||
' ',
|
||||
code.comment('// present if ' + arg.predicate)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
entities.push('\n}')
|
||||
}
|
||||
|
||||
return code.code(entities)
|
||||
|
||||
// const typeName = (s: string): string => {
|
||||
// if (
|
||||
// s === 'string' ||
|
||||
// s === 'number' ||
|
||||
// s === 'boolean' ||
|
||||
// s === 'true'
|
||||
// ) {
|
||||
// return keyword(s)
|
||||
// }
|
||||
//
|
||||
// if (s.substr(s.length - 2) === '[]')
|
||||
// return typeName(s.substr(0, s.length - 2)) + '[]'
|
||||
//
|
||||
// return s.split('.').map(identifier).join('.')
|
||||
// }
|
||||
//
|
||||
// let html
|
||||
// if (obj.type === 'union') {
|
||||
// html = `${keyword('export type')} ${identifier(obj.ts)} =`
|
||||
// html += children!
|
||||
// .map((it) => {
|
||||
// const ns =
|
||||
// it.namespace === '$root'
|
||||
// ? it.prefix === 'mtproto/'
|
||||
// ? 'mtproto.'
|
||||
// : ''
|
||||
// : it.namespace + '.'
|
||||
//
|
||||
// return `\n | ${typeName(`tl.${ns}${it.ts}`)}`
|
||||
// })
|
||||
// .join('')
|
||||
// } else {
|
||||
// html = `${keyword('export interface')} ${identifier(obj.ts)} {`
|
||||
// html += `\n ${property('_')}: `
|
||||
// html += _string(
|
||||
// `'${obj.prefix === 'mtproto/' ? 'mt_' : ''}${obj.name}'`
|
||||
// )
|
||||
// html += obj.arguments
|
||||
// .map((arg) => {
|
||||
// if (arg.type === '$FlagsBitField') {
|
||||
// return comment(
|
||||
// `\n // ${arg.name}: TlFlags // handled automatically`
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// const opt = arg.optional ? '?' : ''
|
||||
// const comm = arg.predicate
|
||||
// ? ' ' + comment('// present if ' + arg.predicate)
|
||||
// : ''
|
||||
//
|
||||
// const typ = typeName(arg.ts)
|
||||
// return `\n ${property(arg.name)}${opt}: ${typ}${comm}`
|
||||
// })
|
||||
// .join('')
|
||||
// html += '\n}'
|
||||
// }
|
||||
//
|
||||
// return (
|
||||
// <pre
|
||||
// className={classes.code}
|
||||
// dangerouslySetInnerHTML={{ __html: html }}
|
||||
// />
|
||||
// )
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
import { TableOfContents, TableOfContentsItem } from './table-of-contents'
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
createStyles,
|
||||
Divider,
|
||||
Link as MuiLink,
|
||||
List,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { ExtendedTlObject } from '../types'
|
||||
import { Link } from 'gatsby'
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
container: {
|
||||
height: '100%',
|
||||
padding: 16,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
inner: {
|
||||
flex: '1 1 auto',
|
||||
},
|
||||
box: {
|
||||
paddingBottom: 32,
|
||||
},
|
||||
footer: {
|
||||
marginTop: 64,
|
||||
textAlign: 'center',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export const usePageStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
heading0: {
|
||||
margin: theme.spacing(4, 0),
|
||||
},
|
||||
heading1: {
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
heading: {
|
||||
marginTop: theme.spacing(6),
|
||||
marginBottom: theme.spacing(4),
|
||||
},
|
||||
paragraph: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
rev: {
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
marginLeft: 2,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export function Page({
|
||||
toc,
|
||||
children,
|
||||
}: {
|
||||
toc?: TableOfContentsItem[]
|
||||
children: React.ReactNode
|
||||
}): React.ReactElement {
|
||||
const classes = useStyles()
|
||||
const [container, setContainer] = useState<HTMLElement | null>(null)
|
||||
|
||||
return (
|
||||
<Paper ref={setContainer} elevation={0} className={classes.container}>
|
||||
<Container maxWidth="md" className={classes.inner}>
|
||||
<Box className={classes.box}>
|
||||
{children}
|
||||
|
||||
<footer>
|
||||
<Typography
|
||||
color="textSecondary"
|
||||
variant="body2"
|
||||
className={classes.footer}
|
||||
>
|
||||
© mtcute TL reference. This website is{' '}
|
||||
<MuiLink href="https://github.com/mtcute/mtcute/tree/master/tl-reference">
|
||||
open-source
|
||||
</MuiLink>{' '}
|
||||
and licensed under MIT.
|
||||
<br />
|
||||
This website is not affiliated with Telegram.
|
||||
</Typography>
|
||||
</footer>
|
||||
</Box>
|
||||
</Container>
|
||||
{toc && <TableOfContents items={toc} container={container} />}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
export function Description(params: {
|
||||
description?: string | null
|
||||
component?: any
|
||||
className?: string
|
||||
}) {
|
||||
const { description, component: Component, ...other } = params as any
|
||||
|
||||
return Component ? (
|
||||
<Component
|
||||
{...other}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: description || 'No description available :(',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
{...other}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: description || 'No description available :(',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListItemTlObject({ node }: { node: ExtendedTlObject }) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ padding: '16px 32px' }}>
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${node.prefix}${node.type}/${node.name}`}
|
||||
>
|
||||
<Typography variant="h5" color="textPrimary">
|
||||
{node.prefix}
|
||||
{node.name}
|
||||
</Typography>
|
||||
</MuiLink>
|
||||
<Description description={node.description} />
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function ListItemTlLink({
|
||||
name,
|
||||
type,
|
||||
history,
|
||||
}: {
|
||||
type: string
|
||||
name: string
|
||||
history?: boolean
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div style={{ padding: '16px 32px' }}>
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${history ? 'history/' : ''}${type}/${name}`}
|
||||
>
|
||||
<Typography variant="h5" color="textPrimary">
|
||||
{name}
|
||||
</Typography>
|
||||
</MuiLink>
|
||||
<Description />
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Section({
|
||||
title,
|
||||
id,
|
||||
children,
|
||||
}: {
|
||||
title?: string
|
||||
id?: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const pageClasses = usePageStyles()
|
||||
|
||||
return (
|
||||
<>
|
||||
{title && id && (
|
||||
<Typography
|
||||
variant="h4"
|
||||
id={id}
|
||||
className={pageClasses.heading}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function SectionWithList({
|
||||
nodes,
|
||||
children,
|
||||
...params
|
||||
}: Omit<Parameters<typeof Section>[0], 'children'> & {
|
||||
children?: React.ReactNode
|
||||
nodes: ExtendedTlObject[]
|
||||
}) {
|
||||
return (
|
||||
<Section {...params}>
|
||||
{children && <Typography variant="body1">{children}</Typography>}
|
||||
<List>
|
||||
{nodes.map((node) => (
|
||||
<ListItemTlObject node={node} key={node.id} />
|
||||
))}
|
||||
</List>
|
||||
</Section>
|
||||
)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import { styled } from '@material-ui/core'
|
||||
|
||||
export const Spacer = styled('div')({
|
||||
flex: '1 1 auto'
|
||||
})
|
|
@ -1,237 +0,0 @@
|
|||
import { createStyles, Link, makeStyles, Typography } from '@material-ui/core'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import throttle from 'lodash/throttle'
|
||||
import React, { MouseEvent } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
// based on https://github.com/mui-org/material-ui/blob/master/docs/src/modules/components/AppTableOfContents.js
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
top: 0,
|
||||
// Fix IE 11 position sticky issue.
|
||||
width: 175,
|
||||
flexShrink: 0,
|
||||
order: 2,
|
||||
position: 'sticky',
|
||||
height: 'calc(100vh - 80px)',
|
||||
overflowX: 'auto',
|
||||
padding: theme.spacing(2, 2, 2, 0),
|
||||
display: 'none',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'block',
|
||||
},
|
||||
},
|
||||
contents: {
|
||||
marginTop: theme.spacing(2),
|
||||
paddingLeft: theme.spacing(1.5),
|
||||
},
|
||||
ul: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
listStyleType: 'none',
|
||||
},
|
||||
item: {
|
||||
fontSize: 13,
|
||||
padding: theme.spacing(0.5, 0, 0.5, 1),
|
||||
borderLeft: '4px solid transparent',
|
||||
boxSizing: 'content-box',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
|
||||
'&:hover': {
|
||||
borderLeft: `4px solid ${
|
||||
theme.palette.type === 'light'
|
||||
? theme.palette.grey[200]
|
||||
: theme.palette.grey[900]
|
||||
}`,
|
||||
},
|
||||
'&$active,&:active': {
|
||||
borderLeft: `4px solid ${
|
||||
theme.palette.type === 'light'
|
||||
? theme.palette.grey[300]
|
||||
: theme.palette.grey[800]
|
||||
}`,
|
||||
},
|
||||
},
|
||||
secondaryItem: {
|
||||
paddingLeft: theme.spacing(2.5),
|
||||
},
|
||||
active: {},
|
||||
})
|
||||
)
|
||||
const noop = () => {}
|
||||
|
||||
function useThrottledOnScroll(
|
||||
callback: ((evt: Event) => void) | null,
|
||||
delay: number,
|
||||
container: HTMLElement | null
|
||||
) {
|
||||
const throttledCallback = useMemo(
|
||||
() => (callback ? throttle(callback, delay) : noop),
|
||||
[callback, delay]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (throttledCallback === noop || container === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
container.addEventListener('scroll', throttledCallback)
|
||||
return () => {
|
||||
container.removeEventListener('scroll', throttledCallback)
|
||||
}
|
||||
}, [throttledCallback, container])
|
||||
}
|
||||
|
||||
export interface TableOfContentsItem {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface TocWithNode extends TableOfContentsItem {
|
||||
node: HTMLElement
|
||||
}
|
||||
|
||||
export function TableOfContents({
|
||||
items,
|
||||
container,
|
||||
}: {
|
||||
items: TableOfContentsItem[]
|
||||
container: HTMLElement | null
|
||||
}): React.ReactElement {
|
||||
const classes = useStyles()
|
||||
|
||||
const itemsWithNodeRef = useRef<TocWithNode[]>([])
|
||||
useEffect(() => {
|
||||
itemsWithNodeRef.current = items
|
||||
? items.map(({ id, title }) => {
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
node: document.getElementById(id)!,
|
||||
}
|
||||
})
|
||||
: []
|
||||
}, [items])
|
||||
|
||||
const [activeState, setActiveState] = useState<string | null>(items[0]?.id || null)
|
||||
const clickedRef = useRef(false)
|
||||
const unsetClickedRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const findActiveIndex = useCallback(() => {
|
||||
// Don't set the active index based on scroll if a link was just clicked
|
||||
if (clickedRef.current || !container) {
|
||||
return
|
||||
}
|
||||
|
||||
let active
|
||||
for (let i = itemsWithNodeRef.current.length - 1; i >= 0; i -= 1) {
|
||||
const item = itemsWithNodeRef.current[i]
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!item.node) {
|
||||
console.error(
|
||||
`Missing node on the item ${JSON.stringify(
|
||||
item,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
item.node &&
|
||||
item.node.offsetTop <
|
||||
container.scrollTop + container.clientHeight / 8
|
||||
) {
|
||||
active = item
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (active && activeState !== active.id) {
|
||||
setActiveState(active.id!)
|
||||
}
|
||||
}, [activeState, container])
|
||||
|
||||
// Corresponds to 10 frames at 60 Hz
|
||||
useThrottledOnScroll(
|
||||
items && items.length > 0 ? findActiveIndex : null,
|
||||
166,
|
||||
container
|
||||
)
|
||||
|
||||
const handleClick = (id: string) => (
|
||||
event: MouseEvent<HTMLAnchorElement>
|
||||
) => {
|
||||
// Ignore click for new tab/new window behavior
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.button !== 0 || // ignore everything but left-click
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
event.altKey ||
|
||||
event.shiftKey
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Used to disable findActiveIndex if the page scrolls due to a click
|
||||
clickedRef.current = true
|
||||
unsetClickedRef.current = setTimeout(() => {
|
||||
clickedRef.current = false
|
||||
}, 1000)
|
||||
|
||||
if (activeState !== id) {
|
||||
setActiveState(id)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
findActiveIndex()
|
||||
|
||||
return () => {
|
||||
if (unsetClickedRef.current) {
|
||||
clearTimeout(unsetClickedRef.current)
|
||||
}
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const itemLink = (item: TableOfContentsItem): React.ReactElement => (
|
||||
<Link
|
||||
display="block"
|
||||
color={activeState === item.id ? 'textPrimary' : 'textSecondary'}
|
||||
href={`#${item.id}`}
|
||||
underline="none"
|
||||
onClick={handleClick(item.id)}
|
||||
className={clsx(
|
||||
classes.item,
|
||||
activeState === item.id ? classes.active : undefined
|
||||
)}
|
||||
>
|
||||
<span dangerouslySetInnerHTML={{ __html: item.title }} />
|
||||
</Link>
|
||||
)
|
||||
|
||||
return (
|
||||
<nav className={classes.root}>
|
||||
{items && items.length > 0 ? (
|
||||
<>
|
||||
<Typography gutterBottom className={classes.contents}>
|
||||
Contents
|
||||
</Typography>
|
||||
<Typography component="ul" className={classes.ul}>
|
||||
{items.map((item) => (
|
||||
<li key={item.id}>{itemLink(item)}</li>
|
||||
))}
|
||||
</Typography>
|
||||
</>
|
||||
) : null}
|
||||
</nav>
|
||||
)
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
import { useCodeArea } from '../hooks/use-code-area'
|
||||
import { ReactNode } from 'react'
|
||||
import { Link } from 'gatsby'
|
||||
import React from 'react'
|
||||
|
||||
const LineRegex = /^(.+?)(?:#([0-f]{1,8}))?(?: \?)?(?: {(.+?:.+?)})? ((?:.+? )*)= (.+);$/
|
||||
|
||||
export function TlSchemaCode({ tl }: { tl: string }) {
|
||||
const code = useCodeArea()
|
||||
|
||||
const highlightType = (s: string): ReactNode[] => {
|
||||
if (
|
||||
s === '#' ||
|
||||
s === 'int' ||
|
||||
s === 'long' ||
|
||||
s === 'double' ||
|
||||
s === 'string' ||
|
||||
s === 'bytes'
|
||||
)
|
||||
return [code.keyword(s)]
|
||||
if (s.match(/^[Vv]ector<(.+?)>$/)) {
|
||||
return [
|
||||
code.identifier(s.substr(0, 6)),
|
||||
'<',
|
||||
...highlightType(s.substring(7, s.length - 1)),
|
||||
'>',
|
||||
]
|
||||
}
|
||||
|
||||
return [<Link to={`/history/union/${s}`}>{code.identifier(s)}</Link>]
|
||||
}
|
||||
|
||||
let inTypes = true
|
||||
const entities: ReactNode[] = []
|
||||
|
||||
tl.split('\n').forEach((line) => {
|
||||
if (line.match(/^\/\//)) {
|
||||
return entities.push(code.comment(line + '\n'))
|
||||
}
|
||||
|
||||
let m
|
||||
if ((m = line.match(LineRegex))) {
|
||||
const [, fullName, typeId, generics, args, type] = m
|
||||
|
||||
entities.push(
|
||||
<Link to={`/history/${inTypes ? 'class' : 'method'}/${fullName}`}>
|
||||
{code.identifier(fullName)}
|
||||
</Link>
|
||||
)
|
||||
if (typeId) {
|
||||
entities.push('#', code.string(typeId))
|
||||
}
|
||||
|
||||
if (generics) {
|
||||
entities.push(' {')
|
||||
generics.split(' ').forEach((pair) => {
|
||||
const [name, type] = pair.trim().split(':')
|
||||
entities.push(
|
||||
code.property(name),
|
||||
':',
|
||||
code.identifier(type)
|
||||
)
|
||||
})
|
||||
entities.push('}')
|
||||
}
|
||||
|
||||
if (args) {
|
||||
if (args.trim().match(/\[ [a-z]+ ]/i)) {
|
||||
// for generics
|
||||
entities.push(' ', code.comment(args.trim()))
|
||||
} else {
|
||||
const parsed = args
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map((j) => j.split(':'))
|
||||
|
||||
if (parsed.length) {
|
||||
parsed.forEach(([name, typ]) => {
|
||||
const [predicate, type] = typ.split('?')
|
||||
|
||||
if (!type) {
|
||||
return entities.push(
|
||||
' ',
|
||||
code.property(name),
|
||||
':',
|
||||
...highlightType(predicate)
|
||||
)
|
||||
}
|
||||
|
||||
return entities.push(
|
||||
' ',
|
||||
code.property(name),
|
||||
':',
|
||||
code.string(predicate),
|
||||
'?',
|
||||
...highlightType(type)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entities.push(
|
||||
' = ',
|
||||
<Link to={`/history/union/${type}`}>{code.identifier(type)}</Link>
|
||||
)
|
||||
|
||||
entities.push(';\n')
|
||||
return
|
||||
}
|
||||
|
||||
if (line.match(/^---(functions|types)---$/)) {
|
||||
inTypes = line === '---types---'
|
||||
return entities.push(code.keyword(line + '\n'))
|
||||
}
|
||||
|
||||
// unable to highlight
|
||||
return entities.push(line + '\n')
|
||||
})
|
||||
|
||||
return code.code(entities)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
html, body, #___gatsby, #gatsby-focus-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
html:not(.touch) *:not(.default-scroll) {
|
||||
scrollbar-color: #7f7f7f transparent;
|
||||
scrollbar-width: thin;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #7f7f7f;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
// 80px offset for scroll anchors
|
||||
section, .is-anchor {
|
||||
margin-top: -80px;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
h2, h3, h4 {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
// table scroll for mobiles
|
||||
@media screen and (max-width: 640px) {
|
||||
table {
|
||||
overflow-x: auto;
|
||||
display: block !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table tbody {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import { createStyles, makeStyles } from '@material-ui/core'
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
// theme ported from one dark
|
||||
code: {
|
||||
fontFamily: 'Iosevka SS05, Fira Mono, Consolas, monospace',
|
||||
background: '#282c34',
|
||||
color: '#bbbbbb',
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
overflowX: 'auto',
|
||||
padding: 8,
|
||||
|
||||
'& a': {
|
||||
textDecoration: 'none'
|
||||
}
|
||||
},
|
||||
keyword: {
|
||||
fontStyle: 'italic',
|
||||
color: '#c678dd',
|
||||
},
|
||||
identifier: {
|
||||
color: '#e5c07b',
|
||||
},
|
||||
property: {
|
||||
color: '#e06c75',
|
||||
},
|
||||
comment: {
|
||||
color: '#5c6370',
|
||||
},
|
||||
string: {
|
||||
color: '#98c379',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export function useCodeArea() {
|
||||
const classes = useStyles()
|
||||
|
||||
const keyword = (s: ReactNode) => (
|
||||
<span className={classes.keyword}>{s}</span>
|
||||
)
|
||||
|
||||
const identifier = (s: ReactNode) => (
|
||||
<span className={classes.identifier}>{s}</span>
|
||||
)
|
||||
|
||||
const property = (s: ReactNode) => (
|
||||
<span className={classes.property}>{s}</span>
|
||||
)
|
||||
|
||||
const comment = (s: ReactNode) => (
|
||||
<span className={classes.comment}>{s}</span>
|
||||
)
|
||||
|
||||
const string = (s: ReactNode) => <span className={classes.string}>{s}</span>
|
||||
|
||||
const typeName = (s: string): ReactNode => {
|
||||
if (
|
||||
s === 'string' ||
|
||||
s === 'number' ||
|
||||
s === 'boolean' ||
|
||||
s === 'any' ||
|
||||
s === 'true'
|
||||
) {
|
||||
return keyword(s)
|
||||
}
|
||||
|
||||
if (s.substr(s.length - 2) === '[]')
|
||||
return [typeName(s.substr(0, s.length - 2)), '[]']
|
||||
|
||||
const ret: ReactNode[] = []
|
||||
s.split('.').forEach((it, idx) => {
|
||||
if (idx !== 0) ret.push('.')
|
||||
ret.push(identifier(it))
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
const code = (s: ReactNode) => <pre className={classes.code}>{s}</pre>
|
||||
|
||||
return {
|
||||
keyword,
|
||||
identifier,
|
||||
property,
|
||||
comment,
|
||||
string,
|
||||
typeName,
|
||||
code,
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import Fuse from 'fuse.js'
|
||||
import { ChangeEvent, useCallback, useMemo, useState } from 'react'
|
||||
import { debounce } from '@material-ui/core'
|
||||
|
||||
export function useFuse<T>(
|
||||
items: T[],
|
||||
options: Fuse.IFuseOptions<T>,
|
||||
searchOptions: Fuse.FuseSearchOptions,
|
||||
customFilter?: (it: T) => boolean
|
||||
) {
|
||||
const [query, updateQuery] = useState('')
|
||||
|
||||
const fuse = useMemo(() => new Fuse(items, options), [items, options])
|
||||
|
||||
const hits = useMemo(() => {
|
||||
if (!query) return []
|
||||
let res = fuse.search(query, searchOptions)
|
||||
if (customFilter) res = res.filter((it) => customFilter(it.item))
|
||||
return res
|
||||
}, [
|
||||
fuse,
|
||||
query,
|
||||
options,
|
||||
searchOptions,
|
||||
customFilter
|
||||
])
|
||||
|
||||
const setQuery = useCallback(debounce(updateQuery, 100), [])
|
||||
|
||||
const onSearch = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => setQuery(e.target.value),
|
||||
[]
|
||||
)
|
||||
|
||||
return {
|
||||
hits,
|
||||
onSearch,
|
||||
query,
|
||||
setQuery
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { useState } from 'react'
|
||||
|
||||
export function useLocalState<T>(key: string, init: T): [T, (val: T) => void] {
|
||||
const local = typeof localStorage !== 'undefined' ? localStorage[key] : undefined
|
||||
const [item, setItem] = useState<T>(local ? JSON.parse(local) : init)
|
||||
|
||||
return [
|
||||
item,
|
||||
(val: T) => {
|
||||
setItem(val)
|
||||
localStorage[key] = JSON.stringify(val)
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
|
@ -1,210 +0,0 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
createMuiTheme, createStyles,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText, makeStyles,
|
||||
MuiThemeProvider,
|
||||
SwipeableDrawer,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
} from '@material-ui/core'
|
||||
import { Link } from 'gatsby'
|
||||
import { Spacer } from './components/spacer'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
import NightsStayIcon from '@material-ui/icons/NightsStay'
|
||||
import Brightness7Icon from '@material-ui/icons/Brightness7'
|
||||
import MenuIcon from '@material-ui/icons/Menu'
|
||||
|
||||
import './global.scss'
|
||||
import { GlobalSearchField } from './components/global-search-field'
|
||||
import blue from '@material-ui/core/colors/blue'
|
||||
import { isTouchDevice } from './utils'
|
||||
|
||||
const pages = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'About',
|
||||
regex: /^(?:\/tl|\/)\/?$/
|
||||
},
|
||||
{
|
||||
path: '/types',
|
||||
name: 'Types',
|
||||
regex: /^(?:\/tl)?(?:\/mtproto)?\/(class|union|types)(\/|$)/,
|
||||
},
|
||||
{
|
||||
path: '/methods',
|
||||
name: 'Methods',
|
||||
regex: /^(?:\/tl)?(?:\/mtproto)?\/methods?(\/|$)/,
|
||||
},
|
||||
{
|
||||
path: '/history',
|
||||
name: 'History',
|
||||
regex: /^(?:\/tl)?\/history(\/|$)/,
|
||||
},
|
||||
]
|
||||
|
||||
const drawerWidth = 240
|
||||
const useStyles = makeStyles((t) => createStyles({
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
},
|
||||
drawerPaper: {
|
||||
width: drawerWidth,
|
||||
},
|
||||
drawerItem: {
|
||||
padding: t.spacing(2, 4),
|
||||
fontSize: 18
|
||||
}
|
||||
}))
|
||||
|
||||
function MobileNavigation({ path }: { path: string }) {
|
||||
const [drawer, setDrawer] = useState(false)
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
onClick={() => setDrawer(true)}
|
||||
edge="start"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<SwipeableDrawer
|
||||
onClose={() => setDrawer(false)}
|
||||
onOpen={() => setDrawer(true)}
|
||||
open={drawer}
|
||||
className={classes.drawer}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{pages.map((page) => (
|
||||
<ListItem
|
||||
button
|
||||
component={Link}
|
||||
to={page.path}
|
||||
selected={
|
||||
page.regex
|
||||
? !!path.match(page.regex)
|
||||
: path === page.path
|
||||
}
|
||||
className={classes.drawerItem}
|
||||
key={page.name}
|
||||
>
|
||||
<ListItemText primary={page.name} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</SwipeableDrawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function DesktopNavigation({ path }: { path: string }) {
|
||||
return (
|
||||
<>
|
||||
{pages.map((page) => (
|
||||
<span
|
||||
style={{
|
||||
color: (
|
||||
page.regex
|
||||
? path.match(page.regex)
|
||||
: path === page.path
|
||||
)
|
||||
? '#fff'
|
||||
: '#ccc',
|
||||
}}
|
||||
key={page.path}
|
||||
>
|
||||
<Button color="inherit" component={Link} to={page.path}>
|
||||
{page.name}
|
||||
</Button>
|
||||
</span>
|
||||
))}
|
||||
<Spacer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ({
|
||||
children,
|
||||
location,
|
||||
}: {
|
||||
children: NonNullable<React.ReactNode>
|
||||
location: any
|
||||
}): React.ReactElement {
|
||||
const [theme, setTheme] = useState<'light' | 'dark'>('light')
|
||||
const path: string = location.pathname
|
||||
|
||||
useEffect(() => {
|
||||
if (isTouchDevice()) document.documentElement.classList.add('touch')
|
||||
}, [])
|
||||
|
||||
const muiTheme = createMuiTheme({
|
||||
palette: {
|
||||
type: theme,
|
||||
primary:
|
||||
theme === 'dark'
|
||||
? {
|
||||
main: blue[300],
|
||||
}
|
||||
: undefined,
|
||||
secondary: {
|
||||
main: blue[800],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const isDesktop = useMediaQuery(muiTheme.breakpoints.up('sm'))
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | TL Reference"
|
||||
defaultTitle="TL Reference"
|
||||
/>
|
||||
<MuiThemeProvider theme={muiTheme}>
|
||||
<>
|
||||
<AppBar position="static" color="secondary">
|
||||
<Toolbar>
|
||||
{isDesktop ? (
|
||||
<DesktopNavigation path={path} />
|
||||
) : (
|
||||
<MobileNavigation path={path} />
|
||||
)}
|
||||
<GlobalSearchField isMobile={!isDesktop} />
|
||||
<Tooltip title="Toggle dark theme">
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={() =>
|
||||
setTheme(
|
||||
theme === 'dark' ? 'light' : 'dark'
|
||||
)
|
||||
}
|
||||
>
|
||||
{theme === 'light' ? (
|
||||
<NightsStayIcon />
|
||||
) : (
|
||||
<Brightness7Icon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
{children}
|
||||
</>
|
||||
</MuiThemeProvider>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import { Typography, Link as MuiLink } from '@material-ui/core'
|
||||
import { Page, usePageStyles } from '../components/page'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { Link } from 'gatsby'
|
||||
|
||||
const NotFoundPage = ({ location }: any) => {
|
||||
const classes = usePageStyles()
|
||||
const path: string = location.pathname
|
||||
|
||||
let historyReference = undefined
|
||||
let m
|
||||
if ((m = path.match(/^(?:\/tl|\/)((?:class|union|method)\/.+?)$/))) {
|
||||
historyReference = (
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
This type might no longer exist, but you could check{' '}
|
||||
<MuiLink component={Link} to={`/history/${m[1]}`}>
|
||||
History section
|
||||
</MuiLink>
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Helmet>
|
||||
<title>404 Not found</title>
|
||||
<meta name="robots" content="noindex" />
|
||||
</Helmet>
|
||||
<div className={classes.heading1}>
|
||||
<Typography variant="h3" id="tl-reference">
|
||||
404
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
This page does not exist
|
||||
</Typography>
|
||||
{historyReference}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFoundPage
|
|
@ -1,105 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Page, Section, usePageStyles } from '../components/page'
|
||||
import { Link as MuiLink, Typography } from '@material-ui/core'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
|
||||
interface GraphqlResult {
|
||||
layers: {
|
||||
nodes: {
|
||||
layer: number
|
||||
rev: number
|
||||
source: {
|
||||
date: string
|
||||
commit: string
|
||||
website: boolean
|
||||
file: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export default function HistoryPage({ data }: { data: GraphqlResult }) {
|
||||
const classes = usePageStyles()
|
||||
|
||||
data.layers.nodes.sort((a, b) =>
|
||||
a.layer === b.layer ? b.rev - a.rev : b.layer - a.layer
|
||||
)
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Helmet>
|
||||
<title>History</title>
|
||||
</Helmet>
|
||||
|
||||
<div className={classes.heading1}>
|
||||
<Typography variant="h3" id="tl-reference">
|
||||
History
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
In this section of the website, you can explore history of the
|
||||
TL schema, and how it changed over the time.
|
||||
<br />
|
||||
<br />
|
||||
Schemas are fetched automatically from <code>
|
||||
tdesktop
|
||||
</code>{' '}
|
||||
repository, and older schemas (<14) are fetched directly from
|
||||
Telegram's website.
|
||||
<br />
|
||||
<br />
|
||||
Note that schemas for mtcute are merged from Telegram Desktop
|
||||
and TDLib repositories, and thus may not be exactly the same as
|
||||
in history.
|
||||
</Typography>
|
||||
|
||||
<Section id="schemas" title="Schemas">
|
||||
{data.layers.nodes.map((layer) => (
|
||||
<Typography variant="h5">
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/history/layer${layer.layer}${
|
||||
layer.rev ? `-rev${layer.rev}` : ''
|
||||
}`}
|
||||
>
|
||||
Layer {layer.layer}
|
||||
{layer.rev > 0 && (
|
||||
<span className={classes.rev}>
|
||||
{' '}
|
||||
rev. {layer.rev}
|
||||
</span>
|
||||
)}
|
||||
</MuiLink>
|
||||
|
||||
<small>
|
||||
{' '}
|
||||
(from{' '}
|
||||
{layer.source.website
|
||||
? 'website'
|
||||
: layer.source.date}
|
||||
)
|
||||
</small>
|
||||
</Typography>
|
||||
))}
|
||||
</Section>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query {
|
||||
layers: allHistoryJson {
|
||||
nodes {
|
||||
layer
|
||||
rev
|
||||
source {
|
||||
website
|
||||
date(formatString: "DD-MM-YYYY")
|
||||
commit
|
||||
file
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,355 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Link as MuiLink, Typography } from '@material-ui/core'
|
||||
import { Page, usePageStyles } from '../components/page'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
|
||||
interface Data {
|
||||
mtClasses: { totalCount: number }
|
||||
classes: { totalCount: number }
|
||||
mtMethods: { totalCount: number }
|
||||
methods: { totalCount: number }
|
||||
mtUnions: { totalCount: number }
|
||||
unions: { totalCount: number }
|
||||
|
||||
clsWithDesc: { totalCount: number }
|
||||
clsWithoutDesc: { totalCount: number }
|
||||
argWithDesc: {
|
||||
totalCount: number
|
||||
nodes: { arguments: { description: string | null }[] }[]
|
||||
}
|
||||
argWithoutDesc: {
|
||||
totalCount: number
|
||||
nodes: { arguments: { description: string | null }[] }[]
|
||||
}
|
||||
|
||||
currentTlSchema: {
|
||||
layer: string
|
||||
}
|
||||
|
||||
historySchemas: { totalCount: number }
|
||||
historyTypes: { totalCount: number }
|
||||
}
|
||||
|
||||
function countMissingDescriptionArguments(
|
||||
item: Data['argWithDesc'],
|
||||
eqNull: boolean
|
||||
) {
|
||||
let count = 0
|
||||
item.nodes.forEach((node) =>
|
||||
node.arguments?.forEach((arg) => {
|
||||
if (eqNull ? arg.description === null : arg.description !== null)
|
||||
count += 1
|
||||
})
|
||||
)
|
||||
item.totalCount = count
|
||||
}
|
||||
|
||||
export default function IndexPage({ data }: { data: Data }) {
|
||||
const classes = usePageStyles()
|
||||
|
||||
countMissingDescriptionArguments(data.argWithoutDesc, true)
|
||||
countMissingDescriptionArguments(data.argWithDesc, false)
|
||||
|
||||
return (
|
||||
<Page
|
||||
toc={[
|
||||
{ id: 'tl-reference', title: 'TL Reference' },
|
||||
{ id: 'types', title: 'Types' },
|
||||
{ id: 'core-types', title: 'Core types' },
|
||||
{ id: 'statistics', title: 'Statistics' },
|
||||
]}
|
||||
>
|
||||
<div className={classes.heading1}>
|
||||
<Typography variant="h3" id="tl-reference">
|
||||
TL Reference
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
layer {data.currentTlSchema.layer}
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
This web application allows easily browsing through myriads of
|
||||
TL objects and reading through their documentation. Unlike{' '}
|
||||
<MuiLink href="//core.telegram.org/schema">
|
||||
official documentation
|
||||
</MuiLink>
|
||||
, this app has simpler structure, search and nice interface.
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
Even though this reference is intended to be used with{' '}
|
||||
<MuiLink href="//github.com/mtcute/mtcute">mtcute</MuiLink>{' '}
|
||||
library, the objects are common to any other MTProto library.
|
||||
The key difference is that mtcute (and this reference) use{' '}
|
||||
<code>camelCase</code> for arguments, while the original schema
|
||||
and some other libraries use <code>snake_case</code>.
|
||||
</Typography>
|
||||
<Typography variant="h4" id="types" className={classes.heading}>
|
||||
Types
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
In TL, there are 3 main groups of types: <i>Classes</i>,{' '}
|
||||
<i>Methods</i> and Unions (officially they are called{' '}
|
||||
<i>constructors</i>, <i>methods</i> and <i>types</i>{' '}
|
||||
respectively).
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
<i>Classes</i> and <i>Methods</i> are simply typed objects, that
|
||||
contain some data. The only difference is that Methods are used
|
||||
in RPC calls (i.e. they are sent to the server), and Classes are
|
||||
used inside methods, or sent by the server back (either as an
|
||||
RPC result, or as an update).
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
<i>Union</i> is a type that combines multiple <i>Classes</i> in
|
||||
one type. In some languages, this can be represented as an
|
||||
abstract class. <i>Unions</i> are sent by Telegram in response
|
||||
to RPC results, as well as they are used as arguments for other{' '}
|
||||
<i>Classes</i> or <i>Methods</i>.
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
In TL, every single <i>Class</i> is a part of exactly one{' '}
|
||||
<i>Union</i>, and every <i>Union</i> contains at least one{' '}
|
||||
<i>Class</i>.
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
In mtcute, all types are exposed as a namespace <code>tl</code>{' '}
|
||||
of package <code>@mtcute/tl</code>. By design, we use immutable
|
||||
plain objects with type discriminator to represent{' '}
|
||||
<i>Classes</i> and <i>Methods</i>, and TypeScript unions to
|
||||
represent <i>Unions</i>.<br />
|
||||
To differentiate between different groups of types, we use
|
||||
different naming for each of them:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className={classes.paragraph}
|
||||
component="ul"
|
||||
>
|
||||
<li>
|
||||
<i>Classes</i> are prefixed with <code>Raw</code> (e.g.{' '}
|
||||
<code>tl.RawMessage</code>)
|
||||
</li>
|
||||
<li>
|
||||
Additionally, <i>Methods</i> are postfixed with{' '}
|
||||
<code>Request</code> and (e.g.{' '}
|
||||
<code>tl.RawGetMessageRequest</code>)
|
||||
</li>
|
||||
<li>
|
||||
Finally, <i>Unions</i> are simply prefixed with{' '}
|
||||
<code>Type</code> (e.g. <code>tl.TypeUser</code>)
|
||||
</li>
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h4"
|
||||
id="core-types"
|
||||
className={classes.heading}
|
||||
>
|
||||
Core types
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
Core types are basic built-in types that are used in TL schema.
|
||||
Quick reference:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className={classes.paragraph}
|
||||
component="ul"
|
||||
>
|
||||
<li>
|
||||
<code>number</code>: 32-bit signed integer
|
||||
</li>
|
||||
<li>
|
||||
<code>Long</code>: 64-bit signed integer
|
||||
</li>
|
||||
<li>
|
||||
<code>Int128</code>: 128-bit signed integer (only used for
|
||||
MTProto)
|
||||
</li>
|
||||
<li>
|
||||
<code>Int256</code>: 256-bit signed integer (only used for
|
||||
MTProto)
|
||||
</li>
|
||||
<li>
|
||||
<code>Double</code>: 64-bit floating point value
|
||||
</li>
|
||||
<li>
|
||||
<code>string</code>: UTF-16 string (strings in JS are also
|
||||
UTF-16)
|
||||
</li>
|
||||
<li>
|
||||
<code>Buffer</code>: Byte array of a known size
|
||||
</li>
|
||||
<li>
|
||||
<code>boolean</code>: One-byte boolean value (true/false)
|
||||
</li>
|
||||
<li>
|
||||
<code>true</code>: Zero-size <code>true</code> value, used
|
||||
for TL flags
|
||||
</li>
|
||||
<li>
|
||||
<code>any</code>: Any other TL object (usually another
|
||||
method)
|
||||
</li>
|
||||
<li>
|
||||
<code>T[]</code>: Array of <code>T</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>TlFlags</code>: 32-bit signed value representing
|
||||
object's TL flags
|
||||
</li>
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h4"
|
||||
className={classes.heading}
|
||||
id="statistics"
|
||||
>
|
||||
Statistics
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
className={classes.paragraph}
|
||||
component="ul"
|
||||
>
|
||||
<li>
|
||||
Generated from layer <b>{data.currentTlSchema.layer}</b>{' '}
|
||||
</li>
|
||||
<li>
|
||||
Current schema contains{' '}
|
||||
<b>
|
||||
{data.methods.totalCount +
|
||||
data.classes.totalCount +
|
||||
data.unions.totalCount}
|
||||
</b>{' '}
|
||||
types (+{' '}
|
||||
<b>
|
||||
{data.mtClasses.totalCount +
|
||||
data.mtMethods.totalCount +
|
||||
data.mtUnions.totalCount}
|
||||
</b>{' '}
|
||||
for MTProto)
|
||||
</li>
|
||||
<li>
|
||||
Current schema contains <b>{data.classes.totalCount}</b>{' '}
|
||||
classes (+ <b>{data.mtClasses.totalCount}</b> for MTProto)
|
||||
</li>
|
||||
<li>
|
||||
Current schema contains <b>{data.methods.totalCount}</b>{' '}
|
||||
methods (+ <b>{data.mtMethods.totalCount}</b> for MTProto)
|
||||
</li>
|
||||
<li>
|
||||
Current schema contains <b>{data.unions.totalCount}</b>{' '}
|
||||
unions (+ <b>{data.mtUnions.totalCount}</b> for MTProto)
|
||||
</li>
|
||||
<li>
|
||||
Description coverage:{' '}
|
||||
{(function () {
|
||||
const totalWith =
|
||||
data.argWithDesc.totalCount +
|
||||
data.clsWithDesc.totalCount
|
||||
const totalWithout =
|
||||
data.argWithoutDesc.totalCount +
|
||||
data.clsWithoutDesc.totalCount
|
||||
const total = totalWith + totalWithout
|
||||
|
||||
return (
|
||||
<>
|
||||
<b>
|
||||
{Math.round((totalWith / total) * 10000) /
|
||||
100}
|
||||
%
|
||||
</b>{' '}
|
||||
(out of {total} items, {totalWithout}{' '}
|
||||
<MuiLink component={Link} to="/no-description">
|
||||
don't have description
|
||||
</MuiLink>{' '}
|
||||
- that is {data.clsWithoutDesc.totalCount} types
|
||||
and {data.argWithoutDesc.totalCount} arguments)
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</li>
|
||||
<li>
|
||||
History is available for{' '}
|
||||
<MuiLink component={Link} to="/history">
|
||||
<b>{data.historySchemas.totalCount}</b> schemas
|
||||
</MuiLink>{' '}
|
||||
and <b>{data.historyTypes.totalCount}</b> types
|
||||
</li>
|
||||
</Typography>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query {
|
||||
mtClasses: allTlObject(
|
||||
filter: { type: { eq: "class" }, prefix: { eq: "mtproto/" } }
|
||||
) {
|
||||
totalCount
|
||||
}
|
||||
classes: allTlObject(
|
||||
filter: { type: { eq: "class" }, prefix: { ne: "mtproto/" } }
|
||||
) {
|
||||
totalCount
|
||||
}
|
||||
mtMethods: allTlObject(
|
||||
filter: { type: { eq: "method" }, prefix: { eq: "mtproto/" } }
|
||||
) {
|
||||
totalCount
|
||||
}
|
||||
methods: allTlObject(
|
||||
filter: { type: { eq: "method" }, prefix: { ne: "mtproto/" } }
|
||||
) {
|
||||
totalCount
|
||||
}
|
||||
mtUnions: allTlObject(
|
||||
filter: { type: { eq: "union" }, prefix: { eq: "mtproto/" } }
|
||||
) {
|
||||
totalCount
|
||||
}
|
||||
unions: allTlObject(
|
||||
filter: { type: { eq: "union" }, prefix: { ne: "mtproto/" } }
|
||||
) {
|
||||
totalCount
|
||||
}
|
||||
|
||||
currentTlSchema {
|
||||
layer
|
||||
}
|
||||
|
||||
clsWithDesc: allTlObject(filter: { description: { ne: null } }) {
|
||||
totalCount
|
||||
}
|
||||
clsWithoutDesc: allTlObject(filter: { description: { eq: null } }) {
|
||||
totalCount
|
||||
}
|
||||
argWithDesc: allTlObject(
|
||||
filter: { arguments: { elemMatch: { description: { ne: null } } } }
|
||||
) {
|
||||
totalCount
|
||||
nodes {
|
||||
arguments {
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
argWithoutDesc: allTlObject(
|
||||
filter: { arguments: { elemMatch: { description: { eq: null } } } }
|
||||
) {
|
||||
totalCount
|
||||
nodes {
|
||||
arguments {
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
historySchemas: allHistoryJson {
|
||||
totalCount
|
||||
}
|
||||
|
||||
historyTypes: allTypesJson {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,182 +0,0 @@
|
|||
import { graphql } from 'gatsby'
|
||||
import React from 'react'
|
||||
import { ExtendedTlObject } from '../types'
|
||||
import { Page, Section, usePageStyles } from '../components/page'
|
||||
import {
|
||||
Link as MuiLink,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { LinkToTl } from '../components/objects/link-to-tl'
|
||||
|
||||
interface Data {
|
||||
classes: { nodes: ExtendedTlObject[] }
|
||||
arguments: { nodes: ExtendedTlObject[] }
|
||||
}
|
||||
|
||||
export default function NoDescriptionPage({ data }: { data: Data }) {
|
||||
const classes = usePageStyles()
|
||||
|
||||
// i dont care
|
||||
const args: any[] = []
|
||||
data.arguments.nodes.forEach((node) => {
|
||||
if (node.arguments) {
|
||||
node.arguments.forEach((arg) => {
|
||||
if (arg.description === null) {
|
||||
;(arg as any).node = node
|
||||
args.push(arg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Page
|
||||
toc={[
|
||||
{ id: 'types', title: 'Types' },
|
||||
{ id: 'arguments', title: 'Arguments' },
|
||||
]}
|
||||
>
|
||||
<div className={classes.heading1}>
|
||||
<Typography variant="h3" id="tl-reference">
|
||||
No description
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{data.classes.nodes.length} types, {args.length} arguments
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
This page lists all items (types and their arguments) from the
|
||||
schema that currently do not have a description, neither
|
||||
official nor unofficial. You can improve this reference by
|
||||
adding description to missing items in{' '}
|
||||
<MuiLink href="https://github.com/mtcute/mtcute/blob/master/packages/tl/descriptions.yaml">
|
||||
descriptions.yaml
|
||||
</MuiLink>
|
||||
.
|
||||
</Typography>
|
||||
<Section id="types" title="Types">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>
|
||||
<MuiLink href="https://github.com/mtcute/mtcute/blob/master/packages/tl/descriptions.yaml">
|
||||
descriptions.yaml
|
||||
</MuiLink>{' '}
|
||||
key
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.classes.nodes.map((node) => (
|
||||
<TableRow key={node.id}>
|
||||
<TableCell>
|
||||
{node.type === 'method'
|
||||
? 'Method'
|
||||
: node.type === 'union'
|
||||
? 'Union'
|
||||
: 'Class'}
|
||||
</TableCell>
|
||||
<TableCell>{LinkToTl(node)}</TableCell>
|
||||
<TableCell>
|
||||
{(node.type === 'method'
|
||||
? 'm_'
|
||||
: node.type === 'union'
|
||||
? 'u_'
|
||||
: 'o_') +
|
||||
(node.prefix === 'mtproto/'
|
||||
? 'mt_'
|
||||
: '') +
|
||||
(node.type === 'union'
|
||||
? node.name
|
||||
: node.underscore)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
<Section id="arguments" title="Arguments">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>
|
||||
<MuiLink href="https://github.com/mtcute/mtcute/blob/master/packages/tl/descriptions.yaml">
|
||||
descriptions.yaml
|
||||
</MuiLink>{' '}
|
||||
key
|
||||
</TableCell>
|
||||
<TableCell>Argument</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{args.map((arg) => (
|
||||
<TableRow key={arg.node.id + arg.name}>
|
||||
<TableCell>
|
||||
{arg.node.type === 'method'
|
||||
? 'Method'
|
||||
: arg.node.type === 'union'
|
||||
? 'Union'
|
||||
: 'Class'}
|
||||
</TableCell>
|
||||
<TableCell>{LinkToTl(arg.node)}</TableCell>
|
||||
<TableCell>
|
||||
{(arg.node.type === 'method'
|
||||
? 'm_'
|
||||
: 'o_') +
|
||||
(arg.node.prefix === 'mtproto/'
|
||||
? 'mt_'
|
||||
: '') +
|
||||
(arg.node.type === 'union'
|
||||
? arg.node.name
|
||||
: arg.node.underscore)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<code>{arg.name}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query {
|
||||
classes: allTlObject(filter: { description: { eq: null } }) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
underscore
|
||||
}
|
||||
}
|
||||
arguments: allTlObject(
|
||||
filter: { arguments: { elemMatch: { description: { eq: null } } } }
|
||||
) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
underscore
|
||||
arguments {
|
||||
name
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,273 +0,0 @@
|
|||
import React, { ReactNode, useState } from 'react'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
import { Page, usePageStyles } from '../components/page'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Button,
|
||||
createStyles,
|
||||
Link as MuiLink,
|
||||
makeStyles,
|
||||
Snackbar,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { Spacer } from '../components/spacer'
|
||||
import { TlSchemaCode } from '../components/tl-schema-code'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
|
||||
import ChevronRightIcon from '@material-ui/icons/ChevronRight'
|
||||
import CodeIcon from '@material-ui/icons/Code'
|
||||
import CloudDownloadIcon from '@material-ui/icons/CloudDownload'
|
||||
|
||||
interface GraphqlResult {
|
||||
layer: {
|
||||
layer: number
|
||||
rev: number
|
||||
content: string
|
||||
source: {
|
||||
date: string
|
||||
commit: string
|
||||
website: boolean
|
||||
file: string
|
||||
}
|
||||
}
|
||||
|
||||
prev: {
|
||||
layer: number
|
||||
rev: number
|
||||
}
|
||||
|
||||
next: {
|
||||
layer: number
|
||||
rev: number
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
navigation: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
width: '100%',
|
||||
},
|
||||
btn: {
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export default function TlLayer({
|
||||
data: { layer, prev, next },
|
||||
}: {
|
||||
data: GraphqlResult
|
||||
}) {
|
||||
const pageClasses = usePageStyles()
|
||||
const classes = useStyles()
|
||||
|
||||
const [snackText, setSnackText] = useState<string | undefined>(undefined)
|
||||
|
||||
function copyToClipboard() {
|
||||
// https://stackoverflow.com/a/30810322
|
||||
const area = document.createElement('textarea')
|
||||
area.style.position = 'fixed'
|
||||
area.style.top = '0'
|
||||
area.style.left = '0'
|
||||
area.style.width = '2em'
|
||||
area.style.height = '2em'
|
||||
area.style.padding = '0'
|
||||
area.style.border = 'none'
|
||||
area.style.outline = 'none'
|
||||
area.style.boxShadow = 'none'
|
||||
area.style.background = 'transparent'
|
||||
|
||||
area.value = layer.content
|
||||
|
||||
document.body.appendChild(area)
|
||||
area.focus()
|
||||
area.select()
|
||||
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(area)
|
||||
|
||||
setSnackText('Copied to clipboard!')
|
||||
}
|
||||
|
||||
function downloadAsFile() {
|
||||
const link = document.createElement('a')
|
||||
link.setAttribute(
|
||||
'href',
|
||||
'data:text/plain;charset=utf-8,' + encodeURIComponent(layer.content)
|
||||
)
|
||||
link.setAttribute(
|
||||
'download',
|
||||
`layer${layer.layer}${layer.rev ? `-rev${layer.rev}` : ''}.tl`
|
||||
)
|
||||
|
||||
link.style.display = 'none'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Helmet>
|
||||
<title>
|
||||
{`Layer ${layer.layer}` +
|
||||
`${layer.rev > 0 ? ` rev. ${layer.rev}` : ''}`}
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
`TL code representing layer ${layer.layer}` +
|
||||
`${layer.rev > 0 && ` rev. ${layer.rev}`}` +
|
||||
` (from ${
|
||||
layer.source.website ? 'website' : layer.source.date
|
||||
})`
|
||||
}
|
||||
/>
|
||||
</Helmet>
|
||||
|
||||
<div className={classes.navigation}>
|
||||
{prev && (
|
||||
<Button
|
||||
component={Link}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
to={`/history/layer${prev.layer}${
|
||||
prev.rev ? `-rev${prev.rev}` : ''
|
||||
}`}
|
||||
startIcon={<ChevronLeftIcon />}
|
||||
>
|
||||
Layer {prev.layer}
|
||||
{prev.rev > 0 && ` rev. ${prev.rev}`}
|
||||
</Button>
|
||||
)}
|
||||
<Spacer />
|
||||
{next && (
|
||||
<Button
|
||||
component={Link}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
to={`/history/layer${next.layer}${
|
||||
next.rev ? `-rev${next.rev}` : ''
|
||||
}`}
|
||||
endIcon={<ChevronRightIcon />}
|
||||
>
|
||||
Layer {next.layer}
|
||||
{next.rev > 0 && ` rev. ${next.rev}`}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={pageClasses.heading0}>
|
||||
<Breadcrumbs>
|
||||
<MuiLink component={Link} to={`/history`}>
|
||||
History
|
||||
</MuiLink>
|
||||
<Typography color="textPrimary">
|
||||
Layer {layer.layer}
|
||||
{layer.rev > 0 && ` rev. ${layer.rev}`}
|
||||
</Typography>
|
||||
</Breadcrumbs>
|
||||
<Typography variant="h3" id="title">
|
||||
Layer {layer.layer}
|
||||
{layer.rev > 0 && (
|
||||
<span className={pageClasses.rev}>
|
||||
{' '}
|
||||
rev. {layer.rev}
|
||||
</span>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
from {layer.source.website ? 'website' : layer.source.date}
|
||||
{!layer.source.website && (
|
||||
<>
|
||||
{' '}
|
||||
/ commit{' '}
|
||||
<MuiLink
|
||||
href={`https://github.com/telegramdesktop/tdesktop/commit/${layer.source.commit}`}
|
||||
target="_blank"
|
||||
>
|
||||
{layer.source.commit.substr(0, 7)}
|
||||
</MuiLink>{' '}
|
||||
(
|
||||
<MuiLink
|
||||
href={`https://github.com/telegramdesktop/tdesktop/blob/${layer.source.commit}/${layer.source.file}`}
|
||||
target="_blank"
|
||||
>
|
||||
file
|
||||
</MuiLink>
|
||||
)
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
open={snackText !== undefined}
|
||||
autoHideDuration={5000}
|
||||
onClose={() => setSnackText(undefined)}
|
||||
message={snackText}
|
||||
/>
|
||||
<div
|
||||
className={classes.navigation}
|
||||
style={{ justifyContent: 'flex-end' }}
|
||||
>
|
||||
<Button
|
||||
className={classes.btn}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<CodeIcon />}
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
Copy to clipboard
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.btn}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
startIcon={<CloudDownloadIcon />}
|
||||
onClick={downloadAsFile}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<TlSchemaCode tl={layer.content} />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query($layer: Int!, $rev: Int!, $prev: String, $next: String) {
|
||||
layer: historyJson(layer: { eq: $layer }, rev: { eq: $rev }) {
|
||||
layer
|
||||
rev
|
||||
content
|
||||
prev
|
||||
next
|
||||
source {
|
||||
website
|
||||
date(formatString: "DD-MM-YYYY")
|
||||
commit
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
prev: historyJson(uid: { eq: $prev }) {
|
||||
layer
|
||||
rev
|
||||
}
|
||||
|
||||
next: historyJson(uid: { eq: $next }) {
|
||||
layer
|
||||
rev
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,361 +0,0 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import { graphql } from 'gatsby'
|
||||
import { ExtendedTlObject } from '../types'
|
||||
import {
|
||||
Description,
|
||||
Page,
|
||||
Section,
|
||||
SectionWithList,
|
||||
usePageStyles,
|
||||
} from '../components/page'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from '@material-ui/core'
|
||||
import {
|
||||
createStyles,
|
||||
Link as MuiLink,
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { Link } from 'gatsby'
|
||||
import { LinkToTl } from '../components/objects/link-to-tl'
|
||||
import { TableOfContentsItem } from '../components/table-of-contents'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { ObjectParameters } from '../components/objects/object-parameters'
|
||||
import { ObjectTsCode } from '../components/objects/object-ts-code'
|
||||
|
||||
interface GraphqlResult {
|
||||
self: ExtendedTlObject
|
||||
parent: ExtendedTlObject
|
||||
children: { nodes: ExtendedTlObject[] }
|
||||
usageMethods: { nodes: ExtendedTlObject[] }
|
||||
usageTypes: { nodes: ExtendedTlObject[] }
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
description: {
|
||||
marginBottom: theme.spacing(2),
|
||||
fontSize: 16,
|
||||
},
|
||||
table: {
|
||||
'& th, & td': {
|
||||
fontSize: 15,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
function useToc(obj: ExtendedTlObject): TableOfContentsItem[] {
|
||||
return useMemo(() => {
|
||||
const ret = [{ id: 'title', title: obj.prefix + obj.name }]
|
||||
|
||||
if (obj.type !== 'union') {
|
||||
ret.push({ id: 'parameters', title: 'Parameters' })
|
||||
} else {
|
||||
ret.push({ id: 'subtypes', title: 'Subtypes' })
|
||||
ret.push({ id: 'usage', title: 'Usage' })
|
||||
}
|
||||
|
||||
if (obj.type === 'method' && obj.throws) {
|
||||
ret.push({ id: 'throws', title: 'Throws' })
|
||||
}
|
||||
|
||||
ret.push({ id: 'typescript', title: 'TypeScript' })
|
||||
|
||||
return ret
|
||||
}, [obj])
|
||||
}
|
||||
|
||||
export default function TlObject({ data }: { data: GraphqlResult }) {
|
||||
const pageClasses = usePageStyles()
|
||||
const classes = useStyles()
|
||||
|
||||
const obj = data.self
|
||||
const toc = useToc(obj)
|
||||
|
||||
return (
|
||||
<Page toc={toc}>
|
||||
<Helmet>
|
||||
<title>
|
||||
{obj.prefix}
|
||||
{obj.name}
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
obj.descriptionExcerpt ||
|
||||
obj.prefix +
|
||||
obj.name +
|
||||
" currently doesn't have a description."
|
||||
}
|
||||
/>
|
||||
</Helmet>
|
||||
|
||||
<div className={pageClasses.heading0}>
|
||||
<Breadcrumbs>
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${obj.prefix}${
|
||||
obj.type === 'method' ? 'methods' : 'types'
|
||||
}`}
|
||||
>
|
||||
{obj.prefix}
|
||||
{obj.type === 'method' ? 'Methods' : 'Types'}
|
||||
</MuiLink>
|
||||
{obj.namespace !== '$root' && (
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${obj.prefix}${
|
||||
obj.type === 'method' ? 'methods' : 'types'
|
||||
}/${obj.namespace}`}
|
||||
>
|
||||
{obj.prefix}
|
||||
{obj.namespace}
|
||||
</MuiLink>
|
||||
)}
|
||||
<Typography color="textPrimary">{obj.name}</Typography>
|
||||
</Breadcrumbs>
|
||||
<Typography variant="h3" id="title">
|
||||
{obj.prefix}
|
||||
{obj.name}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{obj.type === 'class' ? (
|
||||
<>
|
||||
constructor ID 0x{obj.tlId} / belongs to union{' '}
|
||||
{LinkToTl(data.parent)}
|
||||
</>
|
||||
) : obj.type === 'union' ? (
|
||||
<>
|
||||
has{' '}
|
||||
<MuiLink href="#subtypes">
|
||||
{data.children.nodes.length} sub-types
|
||||
</MuiLink>{' '}
|
||||
and{' '}
|
||||
<MuiLink href="#usage">
|
||||
{data.usageTypes.nodes.length +
|
||||
data.usageMethods.nodes.length}{' '}
|
||||
usages
|
||||
</MuiLink>
|
||||
</>
|
||||
) : (
|
||||
obj.returns && (
|
||||
<>
|
||||
constructor ID 0x{obj.tlId} / returns{' '}
|
||||
{LinkToTl(obj.returns)}
|
||||
{obj.available &&
|
||||
' / available for ' +
|
||||
(obj.available === 'both'
|
||||
? 'both users and bots'
|
||||
: obj.available + 's only')}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
{obj.prefix === '' && (
|
||||
<>
|
||||
{' / '}
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/history/${obj.type}/${obj.name}`}
|
||||
>
|
||||
history
|
||||
</MuiLink>
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<Description
|
||||
description={obj.description}
|
||||
className={classes.description}
|
||||
/>
|
||||
{obj.type !== 'union' && (
|
||||
<Section id="parameters" title="Parameters">
|
||||
<ObjectParameters obj={obj} />
|
||||
</Section>
|
||||
)}
|
||||
{obj.type === 'union' && (
|
||||
<>
|
||||
<SectionWithList
|
||||
id="subtypes"
|
||||
title="Subtypes"
|
||||
nodes={data.children.nodes}
|
||||
>
|
||||
{obj.prefix}
|
||||
{obj.name} can be represented with{' '}
|
||||
{obj.subtypes.length > 1
|
||||
? `one of ${obj.subtypes.length} classes`
|
||||
: 'only one class'}
|
||||
:
|
||||
</SectionWithList>
|
||||
|
||||
<Section id="usage" title="Usage">
|
||||
{data.usageMethods.nodes.length > 0 && (
|
||||
<SectionWithList nodes={data.usageMethods.nodes}>
|
||||
{obj.prefix}
|
||||
{obj.name} is returned by{' '}
|
||||
{data.usageMethods.nodes.length > 1
|
||||
? `${data.usageMethods.nodes.length} methods`
|
||||
: 'only one method'}
|
||||
:
|
||||
</SectionWithList>
|
||||
)}
|
||||
|
||||
{data.usageTypes.nodes.length > 0 && (
|
||||
<SectionWithList nodes={data.usageTypes.nodes}>
|
||||
{obj.prefix}
|
||||
{obj.name} is used in{' '}
|
||||
{data.usageTypes.nodes.length > 1
|
||||
? `${data.usageTypes.nodes.length} types`
|
||||
: 'only one type'}
|
||||
:
|
||||
</SectionWithList>
|
||||
)}
|
||||
|
||||
{data.usageMethods.nodes.length === 0 &&
|
||||
data.usageTypes.nodes.length === 0 && (
|
||||
<Typography color="textSecondary">
|
||||
This union is never used :(
|
||||
</Typography>
|
||||
)}
|
||||
</Section>
|
||||
</>
|
||||
)}
|
||||
{obj.throws && (
|
||||
<Section id="throws" title="Throws">
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Code</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{obj.throws.map((err) => (
|
||||
<TableRow key={err.name}>
|
||||
<TableCell>
|
||||
<code>{err.code}</code>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<code>{err.name}</code>
|
||||
</TableCell>
|
||||
<Description
|
||||
description={err.description}
|
||||
component={TableCell}
|
||||
/>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
<Section id="typescript" title="TypeScript declaration">
|
||||
<ObjectTsCode obj={obj} children={data.children?.nodes} />
|
||||
</Section>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query(
|
||||
$prefix: String!
|
||||
$type: String!
|
||||
$name: String!
|
||||
$hasSubtypes: Boolean!
|
||||
$subtypes: [String]
|
||||
) {
|
||||
self: tlObject(
|
||||
prefix: { eq: $prefix }
|
||||
type: { eq: $type }
|
||||
name: { eq: $name }
|
||||
) {
|
||||
tlId
|
||||
ts
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
descriptionExcerpt
|
||||
namespace
|
||||
returns
|
||||
available
|
||||
arguments {
|
||||
name
|
||||
ts
|
||||
type
|
||||
description
|
||||
optional
|
||||
predicate
|
||||
}
|
||||
subtypes
|
||||
throws {
|
||||
code
|
||||
description
|
||||
name
|
||||
}
|
||||
}
|
||||
parent: tlObject(
|
||||
prefix: { eq: $prefix }
|
||||
type: { eq: "union" }
|
||||
subtypes: { eq: $name }
|
||||
) {
|
||||
prefix
|
||||
name
|
||||
type
|
||||
description
|
||||
subtypes
|
||||
}
|
||||
children: allTlObject(
|
||||
filter: {
|
||||
prefix: { eq: $prefix }
|
||||
type: { eq: "class" }
|
||||
name: { in: $subtypes }
|
||||
}
|
||||
) @include(if: $hasSubtypes) {
|
||||
nodes {
|
||||
ts
|
||||
id
|
||||
namespace
|
||||
prefix
|
||||
name
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
usageMethods: allTlObject(
|
||||
filter: {
|
||||
prefix: { eq: $prefix }
|
||||
type: { eq: "method" }
|
||||
rawReturns: { eq: $name }
|
||||
}
|
||||
) @include(if: $hasSubtypes) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
usageTypes: allTlObject(
|
||||
filter: {
|
||||
prefix: { eq: $prefix }
|
||||
arguments: { elemMatch: { rawType: { eq: $name } } }
|
||||
}
|
||||
) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,279 +0,0 @@
|
|||
import { graphql, Link } from 'gatsby'
|
||||
import React, { useMemo } from 'react'
|
||||
import { ExtendedTlObject } from '../types'
|
||||
import {
|
||||
Page,
|
||||
Section,
|
||||
SectionWithList,
|
||||
usePageStyles,
|
||||
} from '../components/page'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
createStyles,
|
||||
Link as MuiLink,
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { TableOfContentsItem } from '../components/table-of-contents'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
interface Data {
|
||||
classes: { nodes: ExtendedTlObject[] }
|
||||
unions: { nodes: ExtendedTlObject[] }
|
||||
methods: { nodes: ExtendedTlObject[] }
|
||||
other: { group: { fieldValue: string }[] }
|
||||
}
|
||||
|
||||
interface Context {
|
||||
ns: string
|
||||
prefix: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
namespace: {
|
||||
display: 'inline-block',
|
||||
margin: theme.spacing(0, 1),
|
||||
fontSize: 16,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
function useToc(data: Data, ctx: Context): TableOfContentsItem[] {
|
||||
return useMemo(() => {
|
||||
const ret = [{ id: 'namespaces', title: 'Namespaces' }]
|
||||
|
||||
if (ctx.type === 'types') {
|
||||
ret.push({ id: 'classes', title: 'Classes' })
|
||||
ret.push({ id: 'unions', title: 'Unions' })
|
||||
} else {
|
||||
ret.push({ id: 'methods', title: 'Methods' })
|
||||
}
|
||||
|
||||
return ret
|
||||
}, [data, ctx])
|
||||
}
|
||||
|
||||
export default function TlTypesList({
|
||||
data,
|
||||
pageContext: ctx,
|
||||
}: {
|
||||
data: Data
|
||||
pageContext: Context
|
||||
}) {
|
||||
const pageClasses = usePageStyles()
|
||||
const classes = useStyles()
|
||||
|
||||
const toc = useToc(data, ctx)
|
||||
|
||||
const title = `${ctx.type === 'methods' ? 'Methods' : 'Types'}
|
||||
${
|
||||
ctx.ns === '$root' && ctx.prefix === ''
|
||||
? ''
|
||||
: ctx.ns === '$root'
|
||||
? `in ${ctx.prefix.slice(0, ctx.prefix.length - 1)}`
|
||||
: ` in ${ctx.prefix}${ctx.ns}`
|
||||
}`
|
||||
|
||||
const plural = (val: number, singular: string, postfix = 's') =>
|
||||
val + ' ' + (val === 1 ? singular : singular + postfix)
|
||||
|
||||
let description = ''
|
||||
{
|
||||
if (ctx.prefix === 'mtproto/') description = 'MTProto '
|
||||
if (ctx.ns === '$root') {
|
||||
description += 'TL Schema'
|
||||
} else {
|
||||
description += 'Namespace ' + ctx.ns
|
||||
}
|
||||
description += ' contains '
|
||||
if (ctx.ns === '$root' && data.other.group.length) {
|
||||
description += plural(data.other.group.length, 'namespace') + ', '
|
||||
}
|
||||
|
||||
if (ctx.type === 'methods') {
|
||||
description +=
|
||||
plural(data.methods.nodes.length, 'method') +
|
||||
', including: ' +
|
||||
data.methods.nodes
|
||||
.slice(0, 3)
|
||||
.map((i) => i.name)
|
||||
.join(', ')
|
||||
|
||||
if (data.methods.nodes.length > 3) description += ' and others.'
|
||||
} else {
|
||||
description +=
|
||||
plural(data.classes.nodes.length, 'class', 'es') +
|
||||
' and ' +
|
||||
plural(data.unions.nodes.length, 'union') +
|
||||
', including: ' +
|
||||
data.classes.nodes
|
||||
.slice(0, 3)
|
||||
.map((i) => i.name)
|
||||
.join(', ')
|
||||
|
||||
if (data.classes.nodes.length > 3) description += ' and others.'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Page toc={toc}>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
</Helmet>
|
||||
|
||||
<div className={pageClasses.heading0}>
|
||||
<Breadcrumbs>
|
||||
{ctx.ns === '$root' ? (
|
||||
<Typography color="textPrimary">
|
||||
{ctx.prefix}{ctx.type === 'methods' ? 'Methods' : 'Types'}
|
||||
</Typography>
|
||||
) : (
|
||||
[
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${ctx.prefix}${ctx.type}`}
|
||||
key="type"
|
||||
>
|
||||
{ctx.prefix}{ctx.type === 'methods' ? 'Methods' : 'Types'}
|
||||
</MuiLink>,
|
||||
<Typography color="textPrimary" key="namespace">
|
||||
{ctx.ns}
|
||||
</Typography>,
|
||||
]
|
||||
)}
|
||||
</Breadcrumbs>
|
||||
<Typography variant="h3" id="title">
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{ctx.ns === '$root' &&
|
||||
`has ${plural(
|
||||
data.other.group.length,
|
||||
'namespace'
|
||||
)} / `}
|
||||
{ctx.type === 'methods'
|
||||
? `has ${plural(data.methods.nodes.length, 'method')}`
|
||||
: `has ${plural(
|
||||
data.classes.nodes.length,
|
||||
'class',
|
||||
'es'
|
||||
)}` +
|
||||
` and ${plural(data.unions.nodes.length, 'union')}`}
|
||||
</Typography>
|
||||
</div>
|
||||
{data.other.group.length && (
|
||||
<Section id="namespaces" title="Namespaces">
|
||||
{data.other.group.map(({ fieldValue: it }) =>
|
||||
it === ctx.ns ? (
|
||||
<Typography
|
||||
color="textPrimary"
|
||||
className={classes.namespace}
|
||||
key={it}
|
||||
>
|
||||
{ctx.ns === '$root' ? 'root' : ctx.ns}
|
||||
</Typography>
|
||||
) : (
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${ctx.prefix}${ctx.type}${
|
||||
it === '$root' ? '' : '/' + it
|
||||
}`}
|
||||
className={classes.namespace}
|
||||
key={it}
|
||||
>
|
||||
{it === '$root' ? 'root' : it}
|
||||
</MuiLink>
|
||||
)
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
{ctx.type === 'types' && (
|
||||
<>
|
||||
<SectionWithList
|
||||
id="classes"
|
||||
title="Classes"
|
||||
nodes={data.classes.nodes}
|
||||
/>
|
||||
<SectionWithList
|
||||
id="unions"
|
||||
title="Unions"
|
||||
nodes={data.unions.nodes}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{ctx.type === 'methods' && (
|
||||
<SectionWithList
|
||||
id="methods"
|
||||
title="Methods"
|
||||
nodes={data.methods.nodes}
|
||||
/>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query(
|
||||
$ns: String!
|
||||
$prefix: String!
|
||||
$isTypes: Boolean!
|
||||
$isMethods: Boolean!
|
||||
) {
|
||||
classes: allTlObject(
|
||||
filter: {
|
||||
prefix: { eq: $prefix }
|
||||
namespace: { eq: $ns }
|
||||
type: { eq: "class" }
|
||||
}
|
||||
) @include(if: $isTypes) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
|
||||
unions: allTlObject(
|
||||
filter: {
|
||||
prefix: { eq: $prefix }
|
||||
namespace: { eq: $ns }
|
||||
type: { eq: "union" }
|
||||
}
|
||||
) @include(if: $isTypes) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
|
||||
methods: allTlObject(
|
||||
filter: {
|
||||
prefix: { eq: $prefix }
|
||||
namespace: { eq: $ns }
|
||||
type: { eq: "method" }
|
||||
}
|
||||
) @include(if: $isMethods) {
|
||||
nodes {
|
||||
id
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
|
||||
other: allTlObject(filter: { prefix: { eq: $prefix } }) {
|
||||
group(field: namespace) {
|
||||
fieldValue
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,409 +0,0 @@
|
|||
import {
|
||||
Description,
|
||||
ListItemTlLink,
|
||||
ListItemTlObject,
|
||||
Page,
|
||||
Section,
|
||||
usePageStyles,
|
||||
} from '../components/page'
|
||||
import React from 'react'
|
||||
import { graphql, Link } from 'gatsby'
|
||||
import { ExtendedTlObject } from '../types'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
createStyles,
|
||||
Divider,
|
||||
Link as MuiLink,
|
||||
List,
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { LinkToTl } from '../components/objects/link-to-tl'
|
||||
import { TableOfContentsItem } from '../components/table-of-contents'
|
||||
import { ObjectParameters } from '../components/objects/object-parameters'
|
||||
import { hexConstructorId } from '../utils'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
interface GraphqlResult {
|
||||
info: {
|
||||
uid: string
|
||||
type: string
|
||||
name: string
|
||||
history: {
|
||||
action: 'added' | 'modified' | 'removed'
|
||||
diff: string
|
||||
in: {
|
||||
date: string
|
||||
layer: number
|
||||
rev: number
|
||||
commit: string
|
||||
website: boolean
|
||||
file: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
object: ExtendedTlObject
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
description: {
|
||||
marginBottom: theme.spacing(2),
|
||||
fontSize: 16,
|
||||
},
|
||||
fakeStrikethrough: {
|
||||
textDecoration: 'line-through',
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const capitalize = (s: string) => s[0].toUpperCase() + s.substr(1)
|
||||
|
||||
export default function TypeHistoryPage({
|
||||
data,
|
||||
pageContext,
|
||||
}: {
|
||||
data: GraphqlResult
|
||||
pageContext: ExtendedTlObject // in fact not, but who cares
|
||||
}) {
|
||||
const pageClasses = usePageStyles()
|
||||
const classes = useStyles()
|
||||
|
||||
const obj = data.object ?? pageContext
|
||||
const history = data.info.history
|
||||
const first = history[history.length - 1]
|
||||
|
||||
const toc: TableOfContentsItem[] = [{ id: 'title', title: obj.name }]
|
||||
|
||||
history.forEach((item) =>
|
||||
toc.push({
|
||||
id: `layer${item.in.layer}${
|
||||
item.in.rev ? `-rev${item.in.rev}` : ''
|
||||
}`,
|
||||
title: `Layer ${item.in.layer}${
|
||||
item.in.rev ? ` rev. ${item.in.rev}` : ''
|
||||
}`,
|
||||
})
|
||||
)
|
||||
|
||||
// documentation is not fetched for historical schemas (yet?)
|
||||
const fillDescriptionFromCurrent = (it: ExtendedTlObject): void => {
|
||||
if (!it.arguments || !obj.arguments) return
|
||||
|
||||
it.arguments.forEach((arg) => {
|
||||
if (arg.description) return
|
||||
|
||||
const curr = obj.arguments.find((i) => i.name === arg.name)
|
||||
if (curr) arg.description = curr.description
|
||||
})
|
||||
}
|
||||
|
||||
const HistoryItem = (
|
||||
item: GraphqlResult['info']['history'][number]
|
||||
): JSX.Element => {
|
||||
let content: JSX.Element | undefined = undefined
|
||||
|
||||
if (pageContext.type === 'union') {
|
||||
if (item.action === 'added') {
|
||||
content = (
|
||||
<>
|
||||
<Typography variant="h5">Types</Typography>
|
||||
<List>
|
||||
{JSON.parse(item.diff).subtypes.map(
|
||||
(type: string) => (
|
||||
<ListItemTlLink
|
||||
key={type}
|
||||
type="class"
|
||||
name={type}
|
||||
history
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</List>
|
||||
</>
|
||||
)
|
||||
} else if (item.action === 'modified') {
|
||||
let added = undefined
|
||||
let removed = undefined
|
||||
|
||||
const diff = JSON.parse(item.diff).subtypes
|
||||
|
||||
if (diff.added.length) {
|
||||
added = (
|
||||
<>
|
||||
<Typography variant="h5">Added</Typography>
|
||||
<List>
|
||||
{diff.added.map((type: string) => (
|
||||
<ListItemTlLink
|
||||
key={type}
|
||||
type="class"
|
||||
name={type}
|
||||
history
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (diff.removed.length) {
|
||||
removed = (
|
||||
<>
|
||||
<Typography variant="h5">Removed</Typography>
|
||||
<List>
|
||||
{diff.removed.map((type: string) => (
|
||||
<ListItemTlLink
|
||||
key={type}
|
||||
type="class"
|
||||
name={type}
|
||||
history
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
content = (
|
||||
<>
|
||||
{added}
|
||||
{removed}
|
||||
</>
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (item.action === 'added') {
|
||||
const object = JSON.parse(item.diff)
|
||||
fillDescriptionFromCurrent(object)
|
||||
|
||||
content = (
|
||||
<>
|
||||
<Typography className={classes.description}>
|
||||
Constructor ID: {hexConstructorId(object.id)}
|
||||
<br />
|
||||
{object.returns ? (
|
||||
<>Returns: {LinkToTl(object.returns, true)}</>
|
||||
) : (
|
||||
<>Belongs to: {LinkToTl(object.type, true)}</>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="h5">Parameters</Typography>
|
||||
<ObjectParameters obj={object} history />
|
||||
</>
|
||||
)
|
||||
} else if (item.action === 'modified') {
|
||||
const stub: ExtendedTlObject = {
|
||||
arguments: [],
|
||||
} as any
|
||||
|
||||
const diff = JSON.parse(item.diff)
|
||||
|
||||
if (diff.arguments) {
|
||||
diff.arguments.added.forEach((arg: any) =>
|
||||
stub.arguments.push({ ...arg, changed: 'added' })
|
||||
)
|
||||
diff.arguments.modified.forEach((arg: any) => {
|
||||
stub.arguments.push({
|
||||
...arg.old,
|
||||
changed: 'modified',
|
||||
className: classes.fakeStrikethrough,
|
||||
})
|
||||
stub.arguments.push({ ...arg.new, changed: 'modified' })
|
||||
})
|
||||
diff.arguments.removed.forEach((arg: any) =>
|
||||
stub.arguments.push({ ...arg, changed: 'removed' })
|
||||
)
|
||||
}
|
||||
fillDescriptionFromCurrent(stub)
|
||||
|
||||
let constructorId = undefined
|
||||
let returns = undefined
|
||||
let union = undefined
|
||||
|
||||
if (diff.id) {
|
||||
constructorId = (
|
||||
<Typography>
|
||||
Constructor ID:{' '}
|
||||
<span className={classes.fakeStrikethrough}>
|
||||
{hexConstructorId(diff.id.old)}
|
||||
</span>{' '}
|
||||
→ {hexConstructorId(diff.id.new)}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
if (diff.returns) {
|
||||
returns = (
|
||||
<Typography>
|
||||
Returns:{' '}
|
||||
<span className={classes.fakeStrikethrough}>
|
||||
{LinkToTl(diff.returns.old, true)}
|
||||
</span>{' '}
|
||||
→ {LinkToTl(diff.returns.new, true)}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
if (diff.type) {
|
||||
union = (
|
||||
<Typography>
|
||||
Belongs to:{' '}
|
||||
<span className={classes.fakeStrikethrough}>
|
||||
{LinkToTl(diff.type.old, true)}
|
||||
</span>{' '}
|
||||
→ {LinkToTl(diff.type.new, true)}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
|
||||
content = (
|
||||
<>
|
||||
<Typography className={classes.description}>
|
||||
{constructorId}
|
||||
{returns}
|
||||
{union}
|
||||
</Typography>
|
||||
<Typography variant="h5">Parameters</Typography>
|
||||
{diff.arguments && (
|
||||
<ObjectParameters obj={stub} diff history />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={pageClasses.heading0}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
id={`layer${item.in.layer}${
|
||||
item.in.rev ? `-rev${item.in.rev}` : ''
|
||||
}`}
|
||||
>
|
||||
{capitalize(item.action)} in Layer {item.in.layer}
|
||||
{item.in.rev > 0 && (
|
||||
<span className={pageClasses.rev}>
|
||||
{' '}
|
||||
rev. {item.in.rev}
|
||||
</span>
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
on {item.in.website ? 'website' : item.in.date}
|
||||
{!item.in.website && (
|
||||
<>
|
||||
{' '}
|
||||
/ commit{' '}
|
||||
<MuiLink
|
||||
href={`https://github.com/telegramdesktop/tdesktop/commit/${item.in.commit}`}
|
||||
target="_blank"
|
||||
>
|
||||
{item.in.commit.substr(0, 7)}
|
||||
</MuiLink>{' '}
|
||||
(
|
||||
<MuiLink
|
||||
href={`https://github.com/telegramdesktop/tdesktop/blob/${item.in.commit}/${item.in.file}`}
|
||||
target="_blank"
|
||||
>
|
||||
file
|
||||
</MuiLink>
|
||||
)
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
{content}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page toc={toc}>
|
||||
<Helmet>
|
||||
<title>History of {obj.name}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
`${obj.name}, first introduced in layer ${first.in.layer}` +
|
||||
`, has had ${history.length - 1} changes over time`
|
||||
}
|
||||
/>
|
||||
</Helmet>
|
||||
|
||||
<div className={pageClasses.heading0}>
|
||||
<Breadcrumbs>
|
||||
<MuiLink component={Link} to={`/history`}>
|
||||
History
|
||||
</MuiLink>
|
||||
<Typography color="textPrimary">Types</Typography>
|
||||
<Typography color="textPrimary">{obj.name}</Typography>
|
||||
</Breadcrumbs>
|
||||
<Typography variant="h3" id="title">
|
||||
{obj.name}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
first introduced in layer {first.in.layer} on{' '}
|
||||
{first.in.website ? 'website' : first.in.date}
|
||||
{data.object && (
|
||||
<>
|
||||
{' '}
|
||||
/{' '}
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/${obj.type}/${obj.name}`}
|
||||
>
|
||||
current
|
||||
</MuiLink>
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<Description
|
||||
description={obj.description}
|
||||
className={classes.description}
|
||||
/>
|
||||
{history.map(HistoryItem)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export const query = graphql`
|
||||
query($uid: String!, $name: String!, $type: String!) {
|
||||
info: typesJson(uid: { eq: $uid }) {
|
||||
uid
|
||||
type
|
||||
name
|
||||
history {
|
||||
action
|
||||
diff
|
||||
in {
|
||||
date(formatString: "DD-MM-YYYY")
|
||||
layer
|
||||
rev
|
||||
commit
|
||||
file
|
||||
website
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object: tlObject(
|
||||
prefix: { eq: "" }
|
||||
name: { eq: $name }
|
||||
type: { eq: $type }
|
||||
) {
|
||||
prefix
|
||||
type
|
||||
name
|
||||
description
|
||||
arguments {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -1,37 +0,0 @@
|
|||
export interface ExtendedTlObject {
|
||||
id: string
|
||||
tlId: number
|
||||
ts: string
|
||||
prefix: string
|
||||
available: 'user' | 'bot' | 'both'
|
||||
type: 'union' | 'class' | 'method'
|
||||
name: string
|
||||
namespace: string
|
||||
returns: string
|
||||
underscore: string
|
||||
description: string | null
|
||||
descriptionExcerpt: string
|
||||
arguments: {
|
||||
ts: string
|
||||
optional?: boolean
|
||||
name: string
|
||||
type: string
|
||||
predicate: string
|
||||
description: string | null
|
||||
|
||||
changed?: 'added' | 'modified' | 'removed'
|
||||
className?: string
|
||||
}[]
|
||||
throws: {
|
||||
name: string
|
||||
code: string
|
||||
description: string
|
||||
}[]
|
||||
subtypes: string[]
|
||||
}
|
||||
|
||||
export interface GraphqlAllResponse<T> {
|
||||
edges: {
|
||||
node: T
|
||||
}[]
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
export const isTouchDevice = function (): boolean {
|
||||
// because windows touch support stinks
|
||||
if (navigator.userAgent.match(/Windows NT/i)) return false;
|
||||
|
||||
const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
|
||||
|
||||
const mq = function (query: string): boolean {
|
||||
return window.matchMedia(query).matches
|
||||
}
|
||||
|
||||
if ('ontouchstart' in window
|
||||
|| (navigator.maxTouchPoints > 0)
|
||||
|| (navigator.msMaxTouchPoints > 0)
|
||||
|| (window as any).DocumentTouch && document instanceof (window as any).DocumentTouch) {
|
||||
return true
|
||||
}
|
||||
|
||||
const query = prefixes.map(i => `(${i}touch-enabled)`).join(',')
|
||||
return mq(query)
|
||||
}
|
||||
|
||||
export const hexConstructorId = (id: number): string => {
|
||||
return '0x' + id.toString(16).padStart(8, '0')
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": "src"
|
||||
}
|
11857
tl-reference/yarn.lock
11857
tl-reference/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue