feat(tl-ref): history section, containing type history and older schemas
This commit is contained in:
parent
685d75effd
commit
599250d0af
27 changed files with 1996 additions and 362 deletions
|
@ -3,4 +3,14 @@ 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 `fetch-history.js` script and are not in the repository.
|
||||
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 +1,2 @@
|
|||
*.json
|
||||
*.txt
|
||||
|
|
1
packages/tl-reference/data/types/.gitignore
vendored
Normal file
1
packages/tl-reference/data/types/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.json
|
|
@ -71,6 +71,9 @@ exports.sourceNodes = ({ actions, createNodeId, createContentDigest }) => {
|
|||
|
||||
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(`
|
||||
|
@ -84,6 +87,23 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||
subtypes
|
||||
}
|
||||
}
|
||||
|
||||
allTypesJson {
|
||||
nodes {
|
||||
uid
|
||||
name
|
||||
type
|
||||
}
|
||||
}
|
||||
|
||||
allHistoryJson {
|
||||
nodes {
|
||||
layer
|
||||
rev
|
||||
prev
|
||||
next
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
|
@ -98,6 +118,22 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||
})
|
||||
})
|
||||
|
||||
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 {
|
||||
|
@ -112,14 +148,13 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||
`)
|
||||
|
||||
result2.data.allTlObject.group.forEach(({ fieldValue: prefix, nodes }) => {
|
||||
const namespaces = [...new Set(nodes.map(i => i.namespace))]
|
||||
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) => {
|
||||
;['types', 'methods'].forEach((type) => {
|
||||
actions.createPage({
|
||||
path: `${prefix}${type}${namespace}`,
|
||||
component: TlTypesList,
|
||||
|
@ -128,8 +163,8 @@ exports.createPages = async ({ graphql, actions }) => {
|
|||
ns,
|
||||
type,
|
||||
isTypes: type === 'types',
|
||||
isMethods: type === 'methods'
|
||||
}
|
||||
isMethods: type === 'methods',
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
180
packages/tl-reference/scripts/diff-utils.js
Normal file
180
packages/tl-reference/scripts/diff-utils.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
// 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,
|
||||
}
|
|
@ -15,20 +15,23 @@ const FILES = [
|
|||
|
||||
async function getLastFetched() {
|
||||
return fs.promises
|
||||
.readFile(path.join(__dirname, '../data/history/last-fetched.json'), 'utf8')
|
||||
.readFile(
|
||||
path.join(__dirname, '../data/history/last-fetched.txt'),
|
||||
'utf8'
|
||||
)
|
||||
.then((res) => JSON.parse(res))
|
||||
.catch(() => ({
|
||||
...FILES.reduce((a, b) => {
|
||||
.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.json'),
|
||||
path.join(__dirname, '../data/history/last-fetched.txt'),
|
||||
JSON.stringify({
|
||||
...state,
|
||||
[file]: time,
|
||||
|
@ -37,6 +40,20 @@ async function updateLastFetched(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}`
|
||||
|
@ -104,149 +121,23 @@ async function parseRemoteTl(file, commit) {
|
|||
return {
|
||||
layer,
|
||||
content,
|
||||
tl: await convertTlToJson(content, 'api', true),
|
||||
tl: convertToArrays(convertTlToJson(content, 'api', true)),
|
||||
}
|
||||
}
|
||||
|
||||
function createTlDifference(old, mod) {
|
||||
const diff = {
|
||||
added: { classes: [], methods: [], unions: [] },
|
||||
removed: { classes: [], methods: [], unions: [] },
|
||||
modified: { classes: [], methods: [], unions: [] },
|
||||
}
|
||||
|
||||
old = convertToArrays(old.tl)
|
||||
mod = convertToArrays(mod.tl)
|
||||
|
||||
// create index for both old and mod
|
||||
const { oldIndex, modIndex } = (function () {
|
||||
function createIndex(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
|
||||
}
|
||||
|
||||
return {
|
||||
oldIndex: createIndex(old),
|
||||
modIndex: createIndex(mod),
|
||||
}
|
||||
})()
|
||||
|
||||
// find difference between constructor arguments
|
||||
function createArgsDifference(old, mod) {
|
||||
const diff = {
|
||||
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)) {
|
||||
diff.added.push(modIndex[argName])
|
||||
} else {
|
||||
const old = oldIndex[argName]
|
||||
const mod = modIndex[argName]
|
||||
if (
|
||||
old.type !== mod.type ||
|
||||
old.optional !== mod.optional ||
|
||||
mod.predicate !== mod.predicate
|
||||
) {
|
||||
diff.modified.push({
|
||||
name: argName,
|
||||
old: old,
|
||||
new: mod,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(oldIndex).forEach((argName) => {
|
||||
if (!(argName in modIndex)) {
|
||||
diff.removed.push(oldIndex[argName])
|
||||
}
|
||||
})
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
Object.keys(modIndex).forEach((uid) => {
|
||||
if (!(uid in oldIndex)) {
|
||||
diff.added[modIndex[uid]._type].push(modIndex[uid])
|
||||
} else {
|
||||
const old = oldIndex[uid]
|
||||
const mod = modIndex[uid]
|
||||
|
||||
const localDiff = {}
|
||||
|
||||
const argDiff = createArgsDifference(old, mod)
|
||||
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) {
|
||||
localDiff.name = old.name
|
||||
diff.modified[oldIndex[uid]._type].push(localDiff)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(oldIndex).forEach((uid) => {
|
||||
if (!(uid in modIndex)) {
|
||||
diff.removed[oldIndex[uid]._type].push(oldIndex[uid])
|
||||
}
|
||||
})
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
function fileSafeDateFormat(date) {
|
||||
date = new Date(date)
|
||||
return date.toISOString().replace(/[\-:]|\.\d\d\d/g, '')
|
||||
return date
|
||||
.toISOString()
|
||||
.replace(/[\-:]|\.\d\d\d/g, '')
|
||||
.split('T')[0]
|
||||
}
|
||||
|
||||
function shortSha(sha) {
|
||||
return sha.substr(0, 7)
|
||||
}
|
||||
|
||||
async function fetchHistory(file, since, defaultParent = null) {
|
||||
async function fetchHistory(file, since, counts, defaultPrev = null, defaultPrevFile = null) {
|
||||
const history = await (async function () {
|
||||
const ret = []
|
||||
let page = 1
|
||||
|
@ -278,15 +169,23 @@ async function fetchHistory(file, since, defaultParent = null) {
|
|||
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,
|
||||
// idk where parent: '00' comes from but whatever
|
||||
parent: schema.parent && schema.parent !== '00' ? schema.parent : defaultParent,
|
||||
prev: schema.prev ? schema.prev : defaultPrev,
|
||||
prevFile: schema.prevFile ? schema.prevFile : defaultPrevFile,
|
||||
source: {
|
||||
file,
|
||||
date: commit.commit.committer.date,
|
||||
|
@ -315,27 +214,12 @@ async function fetchHistory(file, since, defaultParent = null) {
|
|||
const nextSchema = await parseRemoteTl(file, next.sha)
|
||||
if (!nextSchema) break
|
||||
|
||||
const diff = createTlDifference(baseSchema, nextSchema)
|
||||
|
||||
await fs.promises.writeFile(
|
||||
path.join(
|
||||
__dirname,
|
||||
`../data/diffs/${shortSha(base.sha)}-${shortSha(next.sha)}.json`
|
||||
),
|
||||
JSON.stringify({
|
||||
...diff,
|
||||
// yeah they sometimes update schema w/out changing layer number
|
||||
layer:
|
||||
baseSchema.layer === nextSchema.layer
|
||||
? undefined
|
||||
: nextSchema.layer,
|
||||
})
|
||||
)
|
||||
|
||||
nextSchema.parent = baseFilename()
|
||||
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)',
|
||||
|
@ -346,20 +230,26 @@ async function fetchHistory(file, since, defaultParent = null) {
|
|||
}
|
||||
|
||||
if (file !== CURRENT_FILE) {
|
||||
await updateLastFetched(file, 'DONE:' + baseFilename())
|
||||
await updateLastFetched(file, `DONE:${uid(baseSchema, base)}:${baseFilename()}`)
|
||||
}
|
||||
|
||||
console.log('No more commits for %s', file)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const last = await getLastFetched()
|
||||
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
|
||||
await fetchHistory(file, last[file], parent)
|
||||
let parentFile = prev ? last[prev].split(':')[2] : null
|
||||
|
||||
await fetchHistory(file, last[file], counts, parent, parentFile)
|
||||
|
||||
last = await getLastFetched()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,15 +261,16 @@ async function main() {
|
|||
|
||||
const fullPath = path.join(__dirname, '../data/history', file)
|
||||
const json = JSON.parse(await fs.promises.readFile(fullPath, 'utf-8'))
|
||||
if (json.parent) {
|
||||
const parentPath = path.join(__dirname, '../data/history', json.parent)
|
||||
const parentJson = JSON.parse(
|
||||
await fs.promises.readFile(
|
||||
parentPath,
|
||||
'utf-8'
|
||||
)
|
||||
if (json.prev) {
|
||||
const parentPath = path.join(
|
||||
__dirname,
|
||||
'../data/history',
|
||||
json.prevFile
|
||||
)
|
||||
parentJson.next = parentPath
|
||||
const parentJson = JSON.parse(
|
||||
await fs.promises.readFile(parentPath, 'utf-8')
|
||||
)
|
||||
parentJson.next = json.uid
|
||||
await fs.promises.writeFile(parentPath, JSON.stringify(parentJson))
|
||||
}
|
||||
}
|
||||
|
|
112
packages/tl-reference/scripts/fetch-older-layers.js
Normal file
112
packages/tl-reference/scripts/fetch-older-layers.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
const {
|
||||
convertTlToJson,
|
||||
convertJsonToTl,
|
||||
} = require('../../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 = 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)
|
59
packages/tl-reference/scripts/generate-diffs.js
Normal file
59
packages/tl-reference/scripts/generate-diffs.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
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()
|
151
packages/tl-reference/scripts/generate-type-history.js
Normal file
151
packages/tl-reference/scripts/generate-type-history.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
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()
|
|
@ -4,26 +4,29 @@ import React from 'react'
|
|||
import { ExtendedTlObject } from '../../types'
|
||||
|
||||
|
||||
export function LinkToTl(name: string): React.ReactElement
|
||||
export function LinkToTl(obj: ExtendedTlObject): React.ReactElement
|
||||
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
|
||||
name: string,
|
||||
history?: boolean
|
||||
): React.ReactElement
|
||||
export function LinkToTl(
|
||||
prefix: string | ExtendedTlObject,
|
||||
type?: string,
|
||||
name?: string
|
||||
type?: string | boolean,
|
||||
name?: string,
|
||||
history?: boolean
|
||||
): React.ReactElement {
|
||||
if (typeof prefix !== 'string') {
|
||||
type = prefix.type
|
||||
name = prefix.name
|
||||
prefix = prefix.prefix
|
||||
history = !!type
|
||||
}
|
||||
|
||||
// this kind of invocation is used in parameters table and for return type
|
||||
if (!type && !name) {
|
||||
if ((!type || typeof type === 'boolean') && !name) {
|
||||
const fullType = prefix
|
||||
|
||||
// core types
|
||||
|
@ -53,11 +56,14 @@ export function LinkToTl(
|
|||
}
|
||||
|
||||
// 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}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
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>
|
||||
)
|
||||
}
|
138
packages/tl-reference/src/components/objects/object-ts-code.tsx
Normal file
138
packages/tl-reference/src/components/objects/object-ts-code.tsx
Normal file
|
@ -0,0 +1,138 @@
|
|||
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 }}
|
||||
// />
|
||||
// )
|
||||
}
|
|
@ -51,6 +51,11 @@ export const usePageStyles = makeStyles((theme) =>
|
|||
paragraph: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
rev: {
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
marginLeft: 2,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -80,7 +85,8 @@ export function Page({
|
|||
<MuiLink href="https://github.com/teidesu/mtcute/tree/master/packages/tl-reference">
|
||||
open-source
|
||||
</MuiLink>{' '}
|
||||
and licensed under MIT.<br/>
|
||||
and licensed under MIT.
|
||||
<br />
|
||||
This website is not affiliated with Telegram.
|
||||
</Typography>
|
||||
</footer>
|
||||
|
@ -92,7 +98,7 @@ export function Page({
|
|||
}
|
||||
|
||||
export function Description(params: {
|
||||
description: string | null
|
||||
description?: string | null
|
||||
component?: any
|
||||
className?: string
|
||||
}) {
|
||||
|
@ -135,6 +141,33 @@ export function ListItemTlObject({ node }: { node: ExtendedTlObject }) {
|
|||
)
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -9,14 +9,14 @@ import clsx from 'clsx'
|
|||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
top: 80,
|
||||
top: 0,
|
||||
// Fix IE 11 position sticky issue.
|
||||
width: 175,
|
||||
flexShrink: 0,
|
||||
order: 2,
|
||||
position: 'sticky',
|
||||
height: 'calc(100vh - 80px)',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'auto',
|
||||
padding: theme.spacing(2, 2, 2, 0),
|
||||
display: 'none',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
|
@ -37,6 +37,9 @@ const useStyles = makeStyles((theme) =>
|
|||
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'
|
||||
|
|
122
packages/tl-reference/src/components/tl-schema-code.tsx
Normal file
122
packages/tl-reference/src/components/tl-schema-code.tsx
Normal file
|
@ -0,0 +1,122 @@
|
|||
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)
|
||||
}
|
93
packages/tl-reference/src/hooks/use-code-area.tsx
Normal file
93
packages/tl-reference/src/hooks/use-code-area.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
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,
|
||||
}
|
||||
}
|
|
@ -42,11 +42,11 @@ const pages = [
|
|||
name: 'Methods',
|
||||
regex: /^(?:\/tl)?(?:\/mtproto)?\/methods?(\/|$)/,
|
||||
},
|
||||
// {
|
||||
// path: '/history',
|
||||
// name: 'History',
|
||||
// regex: /^\/history(\/|$)/,
|
||||
// },
|
||||
{
|
||||
path: '/history',
|
||||
name: 'History',
|
||||
regex: /^\/history(\/|$)/,
|
||||
},
|
||||
]
|
||||
|
||||
const drawerWidth = 240
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
import * as React from 'react'
|
||||
import { Typography } from '@material-ui/core'
|
||||
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 = () => {
|
||||
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>
|
||||
|
@ -20,6 +35,7 @@ const NotFoundPage = () => {
|
|||
<Typography variant="body1" className={classes.paragraph}>
|
||||
This page does not exist
|
||||
</Typography>
|
||||
{historyReference}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,100 @@
|
|||
import React from 'react'
|
||||
import { Page, usePageStyles } from '../components/page'
|
||||
import { Typography } from '@material-ui/core'
|
||||
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'
|
||||
|
||||
export default function HistoryPage() {
|
||||
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}>
|
||||
This page is currently under construction
|
||||
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.
|
||||
</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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -26,6 +26,7 @@ interface Data {
|
|||
nodes: [
|
||||
{
|
||||
layer: number
|
||||
rev: number
|
||||
source: {
|
||||
date: string
|
||||
commit: string
|
||||
|
@ -34,6 +35,9 @@ interface Data {
|
|||
}
|
||||
]
|
||||
}
|
||||
|
||||
historySchemas: { totalCount: number }
|
||||
historyTypes: { totalCount: number }
|
||||
}
|
||||
|
||||
function countMissingDescriptionArguments(
|
||||
|
@ -56,6 +60,8 @@ export default function IndexPage({ data }: { data: Data }) {
|
|||
countMissingDescriptionArguments(data.argWithoutDesc, true)
|
||||
countMissingDescriptionArguments(data.argWithDesc, false)
|
||||
|
||||
const currentLayer = data.updated.nodes[0]
|
||||
|
||||
return (
|
||||
<Page
|
||||
toc={[
|
||||
|
@ -70,8 +76,17 @@ export default function IndexPage({ data }: { data: Data }) {
|
|||
TL Reference
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
layer {data.updated.nodes[0].layer} / updated{' '}
|
||||
{data.updated.nodes[0].source.date}
|
||||
layer {currentLayer.layer}
|
||||
{currentLayer.rev > 0 ? ` rev. ${currentLayer.rev}` : ''} /
|
||||
updated {currentLayer.source.date} /{' '}
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/history/layer${currentLayer.layer}${
|
||||
currentLayer.rev ? `-rev${currentLayer.rev}` : ''
|
||||
}`}
|
||||
>
|
||||
view source
|
||||
</MuiLink>
|
||||
</Typography>
|
||||
</div>
|
||||
<Typography variant="body1" className={classes.paragraph}>
|
||||
|
@ -284,6 +299,13 @@ export default function IndexPage({ data }: { data: Data }) {
|
|||
)
|
||||
})()}
|
||||
</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>
|
||||
)
|
||||
|
@ -329,6 +351,7 @@ export const query = graphql`
|
|||
) {
|
||||
nodes {
|
||||
layer
|
||||
rev
|
||||
source {
|
||||
date(formatString: "DD-MM-YYYY")
|
||||
commit
|
||||
|
@ -363,5 +386,13 @@ export const query = graphql`
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
historySchemas: allHistoryJson {
|
||||
totalCount
|
||||
}
|
||||
|
||||
historyTypes: allTypesJson {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -85,7 +85,11 @@ export default function NoDescriptionPage({ data }: { data: Data }) {
|
|||
</TableCell>
|
||||
<TableCell>{LinkToTl(node)}</TableCell>
|
||||
<TableCell>
|
||||
{(node.type === 'method' ? 'm_' : 'o_') +
|
||||
{(node.type === 'method'
|
||||
? 'm_'
|
||||
: node.type === 'union'
|
||||
? 'u_'
|
||||
: 'o_') +
|
||||
(node.prefix === 'mtproto/'
|
||||
? 'mt_'
|
||||
: '') +
|
||||
|
|
273
packages/tl-reference/src/templates/tl-layer.tsx
Normal file
273
packages/tl-reference/src/templates/tl-layer.tsx
Normal file
|
@ -0,0 +1,273 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
`
|
|
@ -26,6 +26,8 @@ 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
|
||||
|
@ -46,38 +48,6 @@ const useStyles = makeStyles((theme) =>
|
|||
fontSize: 15,
|
||||
},
|
||||
},
|
||||
mono: {
|
||||
fontFamily: 'Fira Mono, Consolas, monospace',
|
||||
},
|
||||
// theme ported from one dark
|
||||
code: {
|
||||
fontFamily: 'Fira Mono, Consolas, monospace',
|
||||
background: '#282c34',
|
||||
color: '#bbbbbb',
|
||||
fontSize: 16,
|
||||
borderRadius: 4,
|
||||
overflowX: 'auto',
|
||||
padding: 8,
|
||||
},
|
||||
keyword: {
|
||||
fontStyle: 'italic',
|
||||
color: '#c678dd',
|
||||
},
|
||||
identifier: {
|
||||
color: '#e5c07b',
|
||||
},
|
||||
property: {
|
||||
color: '#e06c75',
|
||||
},
|
||||
comment: {
|
||||
color: '#5c6370',
|
||||
},
|
||||
string: {
|
||||
color: '#98c379',
|
||||
},
|
||||
bold: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -109,41 +79,6 @@ export default function TlObject({ data }: { data: GraphqlResult }) {
|
|||
const obj = data.self
|
||||
const toc = useToc(obj)
|
||||
|
||||
const keyword = (s: string) =>
|
||||
`<span class='${classes.keyword}'>${s}</span>`
|
||||
const identifier = (s: string) =>
|
||||
`<span class='${classes.identifier}'>${s}</span>`
|
||||
const property = (s: string) =>
|
||||
`<span class='${classes.property}'>${s}</span>`
|
||||
const comment = (s: string) =>
|
||||
`<span class='${classes.comment}'>${s}</span>`
|
||||
const _string = (s: string) => `<span class='${classes.string}'>${s}</span>`
|
||||
|
||||
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('.')
|
||||
}
|
||||
|
||||
const code = (s: string) => {
|
||||
return (
|
||||
<pre
|
||||
className={classes.code}
|
||||
dangerouslySetInnerHTML={{ __html: s }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Page toc={toc}>
|
||||
<Helmet>
|
||||
|
@ -222,6 +157,17 @@ export default function TlObject({ data }: { data: GraphqlResult }) {
|
|||
</>
|
||||
)
|
||||
)}
|
||||
{obj.prefix === '' && (
|
||||
<>
|
||||
{' / '}
|
||||
<MuiLink
|
||||
component={Link}
|
||||
to={`/history/${obj.type}/${obj.name}`}
|
||||
>
|
||||
history
|
||||
</MuiLink>
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<Description
|
||||
|
@ -230,41 +176,7 @@ export default function TlObject({ data }: { data: GraphqlResult }) {
|
|||
/>
|
||||
{obj.type !== 'union' && (
|
||||
<Section id="parameters" title="Parameters">
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{obj.arguments.map((arg) => (
|
||||
<TableRow key={arg.name}>
|
||||
<TableCell>
|
||||
<code
|
||||
className={
|
||||
!arg.optional &&
|
||||
arg.type !== '$FlagsBitField'
|
||||
? classes.bold
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{arg.name}
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell className={classes.mono}>
|
||||
{LinkToTl(arg.type)}
|
||||
{arg.optional ? '?' : ''}
|
||||
</TableCell>
|
||||
<Description
|
||||
description={arg.description}
|
||||
component={TableCell}
|
||||
/>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<ObjectParameters obj={obj} />
|
||||
</Section>
|
||||
)}
|
||||
{obj.type === 'union' && (
|
||||
|
@ -343,61 +255,9 @@ export default function TlObject({ data }: { data: GraphqlResult }) {
|
|||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
<Typography
|
||||
variant="h4"
|
||||
id="typescript"
|
||||
className={pageClasses.heading}
|
||||
>
|
||||
TypeScript declaration
|
||||
</Typography>
|
||||
|
||||
{/* this is a mess, but who cares */}
|
||||
{code(
|
||||
obj.type === 'union'
|
||||
? `${keyword('export type')} ${identifier(obj.ts)} =` +
|
||||
data.children.nodes
|
||||
.map(
|
||||
(it) =>
|
||||
`\n | ${typeName(
|
||||
'tl.' +
|
||||
(it.namespace === '$root'
|
||||
? it.prefix === 'mtproto/'
|
||||
? 'mtproto.'
|
||||
: ''
|
||||
: it.namespace + '.') +
|
||||
it.ts
|
||||
)}`
|
||||
)
|
||||
.join('')
|
||||
: `${keyword('export interface')} ${identifier(obj.ts)} {` +
|
||||
`\n ${property('_')}: ${_string(
|
||||
`'${obj.prefix === 'mtproto/' ? 'mt_' : ''}${
|
||||
obj.name
|
||||
}'`
|
||||
)}` +
|
||||
obj.arguments
|
||||
.map((arg) =>
|
||||
arg.type === '$FlagsBitField'
|
||||
? comment(
|
||||
'\n // ' +
|
||||
arg.name +
|
||||
': TlFlags // handled automatically'
|
||||
)
|
||||
: `\n ${property(arg.name)}${
|
||||
arg.optional ? '?' : ''
|
||||
}: ${typeName(arg.ts)}${
|
||||
arg.predicate
|
||||
? ' ' +
|
||||
comment(
|
||||
'// present if ' +
|
||||
arg.predicate
|
||||
)
|
||||
: ''
|
||||
}`
|
||||
)
|
||||
.join('') +
|
||||
'\n}'
|
||||
)}
|
||||
<Section id="typescript" title="TypeScript declaration">
|
||||
<ObjectTsCode obj={obj} children={data.children?.nodes} />
|
||||
</Section>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
|
409
packages/tl-reference/src/templates/type-history.tsx
Normal file
409
packages/tl-reference/src/templates/type-history.tsx
Normal file
|
@ -0,0 +1,409 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
|
@ -17,6 +17,9 @@ export interface ExtendedTlObject {
|
|||
type: string
|
||||
predicate: string
|
||||
description: string | null
|
||||
|
||||
changed?: 'added' | 'modified' | 'removed'
|
||||
className?: string
|
||||
}[]
|
||||
throws: {
|
||||
name: string
|
||||
|
|
|
@ -18,3 +18,7 @@ export const isTouchDevice = function (): boolean {
|
|||
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')
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ function getJSType(typ, argName) {
|
|||
return normalizeGenerics(typ)
|
||||
}
|
||||
|
||||
async function convertTlToJson(tlText, tlType, silent = false) {
|
||||
function convertTlToJson(tlText, tlType, silent = false) {
|
||||
let lines = tlText.split('\n')
|
||||
let pos = 0
|
||||
let line = lines[0].trim()
|
||||
|
@ -470,9 +470,9 @@ function convertJsonToTl(json) {
|
|||
json.methods = json.methods.filter((it) => it.method !== 'http_wait')
|
||||
json.constructors.push(httpWait)
|
||||
|
||||
json.constructors.forEach(objectToLine)
|
||||
json.constructors.filter(Boolean).forEach(objectToLine)
|
||||
lines.push('---functions---')
|
||||
json.methods.forEach(objectToLine)
|
||||
json.methods.filter(Boolean).forEach(objectToLine)
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
|
@ -494,14 +494,14 @@ async function main() {
|
|||
.then((json) => convertJsonToTl(json))
|
||||
let ret = {}
|
||||
|
||||
ret.mtproto = await convertTlToJson(mtprotoTl, 'mtproto')
|
||||
ret.mtproto = convertTlToJson(mtprotoTl, 'mtproto')
|
||||
|
||||
console.log('[i] Fetching api.tl')
|
||||
let apiTl = await fetch(
|
||||
'https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/api.tl'
|
||||
).then((i) => i.text())
|
||||
ret.apiLayer = apiTl.match(/^\/\/ LAYER (\d+)/m)[1]
|
||||
ret.api = await convertTlToJson(apiTl, 'api')
|
||||
ret.api = convertTlToJson(apiTl, 'api')
|
||||
await addDocumentation(ret.api)
|
||||
|
||||
await applyDescriptionsFile(ret, descriptionsYaml)
|
||||
|
@ -526,7 +526,8 @@ async function main() {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
convertTlToJson
|
||||
convertTlToJson,
|
||||
convertJsonToTl
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
|
Loading…
Reference in a new issue