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
|
That is, it contains history of the schema over time and pre-calculated difference
|
||||||
between consecutive versions.
|
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
|
*.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 TLObject = path.resolve('./src/templates/tl-object.tsx')
|
||||||
const TlTypesList = path.resolve('./src/templates/tl-types-list.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 }) => {
|
exports.createPages = async ({ graphql, actions }) => {
|
||||||
const result = await graphql(`
|
const result = await graphql(`
|
||||||
|
@ -84,6 +87,23 @@ exports.createPages = async ({ graphql, actions }) => {
|
||||||
subtypes
|
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(`
|
const result2 = await graphql(`
|
||||||
query {
|
query {
|
||||||
allTlObject {
|
allTlObject {
|
||||||
|
@ -112,14 +148,13 @@ exports.createPages = async ({ graphql, actions }) => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
result2.data.allTlObject.group.forEach(({ fieldValue: prefix, nodes }) => {
|
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) => {
|
namespaces.forEach((ns) => {
|
||||||
let namespace
|
let namespace
|
||||||
if (ns === '$root') namespace = ''
|
if (ns === '$root') namespace = ''
|
||||||
else namespace = '/' + ns
|
else namespace = '/' + ns
|
||||||
|
;['types', 'methods'].forEach((type) => {
|
||||||
;(['types', 'methods']).forEach((type) => {
|
|
||||||
actions.createPage({
|
actions.createPage({
|
||||||
path: `${prefix}${type}${namespace}`,
|
path: `${prefix}${type}${namespace}`,
|
||||||
component: TlTypesList,
|
component: TlTypesList,
|
||||||
|
@ -128,8 +163,8 @@ exports.createPages = async ({ graphql, actions }) => {
|
||||||
ns,
|
ns,
|
||||||
type,
|
type,
|
||||||
isTypes: type === 'types',
|
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() {
|
async function getLastFetched() {
|
||||||
return fs.promises
|
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))
|
.then((res) => JSON.parse(res))
|
||||||
.catch(() => ({
|
.catch(() =>
|
||||||
...FILES.reduce((a, b) => {
|
FILES.reduce((a, b) => {
|
||||||
a[b] = UNIX_0
|
a[b] = UNIX_0
|
||||||
return a
|
return a
|
||||||
}, {}),
|
}, {})
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateLastFetched(file, time) {
|
async function updateLastFetched(file, time) {
|
||||||
return getLastFetched().then((state) =>
|
return getLastFetched().then((state) =>
|
||||||
fs.promises.writeFile(
|
fs.promises.writeFile(
|
||||||
path.join(__dirname, '../data/history/last-fetched.json'),
|
path.join(__dirname, '../data/history/last-fetched.txt'),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
...state,
|
...state,
|
||||||
[file]: time,
|
[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) {
|
async function getFileContent(file, commit) {
|
||||||
return fetch(
|
return fetch(
|
||||||
`https://raw.githubusercontent.com/telegramdesktop/tdesktop/${commit}/${file}`
|
`https://raw.githubusercontent.com/telegramdesktop/tdesktop/${commit}/${file}`
|
||||||
|
@ -104,149 +121,23 @@ async function parseRemoteTl(file, commit) {
|
||||||
return {
|
return {
|
||||||
layer,
|
layer,
|
||||||
content,
|
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) {
|
function fileSafeDateFormat(date) {
|
||||||
date = new Date(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) {
|
function shortSha(sha) {
|
||||||
return sha.substr(0, 7)
|
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 history = await (async function () {
|
||||||
const ret = []
|
const ret = []
|
||||||
let page = 1
|
let page = 1
|
||||||
|
@ -278,15 +169,23 @@ async function fetchHistory(file, since, defaultParent = null) {
|
||||||
commit.commit.committer.date
|
commit.commit.committer.date
|
||||||
)}-${shortSha(commit.sha)}.json`
|
)}-${shortSha(commit.sha)}.json`
|
||||||
|
|
||||||
|
const uid = (schema, commit) => `${schema.layer}_${shortSha(commit.sha)}`
|
||||||
|
|
||||||
function writeSchemaToFile(schema, commit) {
|
function writeSchemaToFile(schema, commit) {
|
||||||
return fs.promises.writeFile(
|
return fs.promises.writeFile(
|
||||||
path.join(__dirname, `../data/history/${filename(schema, commit)}`),
|
path.join(__dirname, `../data/history/${filename(schema, commit)}`),
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
// layer is ever-incrementing, sha is random, so no collisions
|
||||||
|
uid: uid(schema, commit),
|
||||||
tl: JSON.stringify(schema.tl),
|
tl: JSON.stringify(schema.tl),
|
||||||
layer: parseInt(schema.layer),
|
layer: parseInt(schema.layer),
|
||||||
|
rev:
|
||||||
|
schema.layer in counts
|
||||||
|
? ++counts[schema.layer]
|
||||||
|
: (counts[schema.layer] = 0),
|
||||||
content: schema.content,
|
content: schema.content,
|
||||||
// idk where parent: '00' comes from but whatever
|
prev: schema.prev ? schema.prev : defaultPrev,
|
||||||
parent: schema.parent && schema.parent !== '00' ? schema.parent : defaultParent,
|
prevFile: schema.prevFile ? schema.prevFile : defaultPrevFile,
|
||||||
source: {
|
source: {
|
||||||
file,
|
file,
|
||||||
date: commit.commit.committer.date,
|
date: commit.commit.committer.date,
|
||||||
|
@ -315,27 +214,12 @@ async function fetchHistory(file, since, defaultParent = null) {
|
||||||
const nextSchema = await parseRemoteTl(file, next.sha)
|
const nextSchema = await parseRemoteTl(file, next.sha)
|
||||||
if (!nextSchema) break
|
if (!nextSchema) break
|
||||||
|
|
||||||
const diff = createTlDifference(baseSchema, nextSchema)
|
nextSchema.prev = uid(baseSchema, base)
|
||||||
|
nextSchema.prevFile = baseFilename()
|
||||||
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()
|
|
||||||
base = next
|
base = next
|
||||||
baseSchema = nextSchema
|
baseSchema = nextSchema
|
||||||
await updateLastFetched(file, base.commit.committer.date)
|
await updateLastFetched(file, base.commit.committer.date)
|
||||||
|
await setCounts(counts)
|
||||||
await writeSchemaToFile(baseSchema, base)
|
await writeSchemaToFile(baseSchema, base)
|
||||||
console.log(
|
console.log(
|
||||||
'Fetched commit %s, file %s (%s)',
|
'Fetched commit %s, file %s (%s)',
|
||||||
|
@ -346,20 +230,26 @@ async function fetchHistory(file, since, defaultParent = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file !== CURRENT_FILE) {
|
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)
|
console.log('No more commits for %s', file)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const last = await getLastFetched()
|
let last = await getLastFetched()
|
||||||
|
const counts = await getCounts()
|
||||||
|
|
||||||
for (let i = 0; i < FILES.length; i++) {
|
for (let i = 0; i < FILES.length; i++) {
|
||||||
const file = FILES[i]
|
const file = FILES[i]
|
||||||
const prev = FILES[i - 1]
|
const prev = FILES[i - 1]
|
||||||
if (!last[file].startsWith('DONE')) {
|
if (!last[file].startsWith('DONE')) {
|
||||||
let parent = prev ? last[prev].split(':')[1] : null
|
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 fullPath = path.join(__dirname, '../data/history', file)
|
||||||
const json = JSON.parse(await fs.promises.readFile(fullPath, 'utf-8'))
|
const json = JSON.parse(await fs.promises.readFile(fullPath, 'utf-8'))
|
||||||
if (json.parent) {
|
if (json.prev) {
|
||||||
const parentPath = path.join(__dirname, '../data/history', json.parent)
|
const parentPath = path.join(
|
||||||
const parentJson = JSON.parse(
|
__dirname,
|
||||||
await fs.promises.readFile(
|
'../data/history',
|
||||||
parentPath,
|
json.prevFile
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
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))
|
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'
|
import { ExtendedTlObject } from '../../types'
|
||||||
|
|
||||||
|
|
||||||
export function LinkToTl(name: string): React.ReactElement
|
export function LinkToTl(name: string, history?: boolean): React.ReactElement
|
||||||
export function LinkToTl(obj: ExtendedTlObject): React.ReactElement
|
export function LinkToTl(obj: ExtendedTlObject, history?: boolean): React.ReactElement
|
||||||
export function LinkToTl(
|
export function LinkToTl(
|
||||||
prefix: string,
|
prefix: string,
|
||||||
type: string,
|
type: string,
|
||||||
name: string
|
name: string,
|
||||||
|
history?: boolean
|
||||||
): React.ReactElement
|
): React.ReactElement
|
||||||
export function LinkToTl(
|
export function LinkToTl(
|
||||||
prefix: string | ExtendedTlObject,
|
prefix: string | ExtendedTlObject,
|
||||||
type?: string,
|
type?: string | boolean,
|
||||||
name?: string
|
name?: string,
|
||||||
|
history?: boolean
|
||||||
): React.ReactElement {
|
): React.ReactElement {
|
||||||
if (typeof prefix !== 'string') {
|
if (typeof prefix !== 'string') {
|
||||||
type = prefix.type
|
type = prefix.type
|
||||||
name = prefix.name
|
name = prefix.name
|
||||||
prefix = prefix.prefix
|
prefix = prefix.prefix
|
||||||
|
history = !!type
|
||||||
}
|
}
|
||||||
|
|
||||||
// this kind of invocation is used in parameters table and for return 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
|
const fullType = prefix
|
||||||
|
|
||||||
// core types
|
// core types
|
||||||
|
@ -53,11 +56,14 @@ export function LinkToTl(
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be union since this is from parameters type
|
// must be union since this is from parameters type
|
||||||
|
history = !!type
|
||||||
prefix = ''
|
prefix = ''
|
||||||
type = 'union'
|
type = 'union'
|
||||||
name = fullType
|
name = fullType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (history) type = 'history/' + type
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiLink component={Link} to={`/${prefix}${type}/${name}`}>
|
<MuiLink component={Link} to={`/${prefix}${type}/${name}`}>
|
||||||
{prefix}
|
{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: {
|
paragraph: {
|
||||||
marginBottom: theme.spacing(2),
|
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">
|
<MuiLink href="https://github.com/teidesu/mtcute/tree/master/packages/tl-reference">
|
||||||
open-source
|
open-source
|
||||||
</MuiLink>{' '}
|
</MuiLink>{' '}
|
||||||
and licensed under MIT.<br/>
|
and licensed under MIT.
|
||||||
|
<br />
|
||||||
This website is not affiliated with Telegram.
|
This website is not affiliated with Telegram.
|
||||||
</Typography>
|
</Typography>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -92,7 +98,7 @@ export function Page({
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Description(params: {
|
export function Description(params: {
|
||||||
description: string | null
|
description?: string | null
|
||||||
component?: any
|
component?: any
|
||||||
className?: string
|
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({
|
export function Section({
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -9,14 +9,14 @@ import clsx from 'clsx'
|
||||||
const useStyles = makeStyles((theme) =>
|
const useStyles = makeStyles((theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
top: 80,
|
top: 0,
|
||||||
// Fix IE 11 position sticky issue.
|
// Fix IE 11 position sticky issue.
|
||||||
width: 175,
|
width: 175,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
order: 2,
|
order: 2,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
height: 'calc(100vh - 80px)',
|
height: 'calc(100vh - 80px)',
|
||||||
overflowY: 'auto',
|
overflowX: 'auto',
|
||||||
padding: theme.spacing(2, 2, 2, 0),
|
padding: theme.spacing(2, 2, 2, 0),
|
||||||
display: 'none',
|
display: 'none',
|
||||||
[theme.breakpoints.up('sm')]: {
|
[theme.breakpoints.up('sm')]: {
|
||||||
|
@ -37,6 +37,9 @@ const useStyles = makeStyles((theme) =>
|
||||||
padding: theme.spacing(0.5, 0, 0.5, 1),
|
padding: theme.spacing(0.5, 0, 0.5, 1),
|
||||||
borderLeft: '4px solid transparent',
|
borderLeft: '4px solid transparent',
|
||||||
boxSizing: 'content-box',
|
boxSizing: 'content-box',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
borderLeft: `4px solid ${
|
borderLeft: `4px solid ${
|
||||||
theme.palette.type === 'light'
|
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',
|
name: 'Methods',
|
||||||
regex: /^(?:\/tl)?(?:\/mtproto)?\/methods?(\/|$)/,
|
regex: /^(?:\/tl)?(?:\/mtproto)?\/methods?(\/|$)/,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: '/history',
|
path: '/history',
|
||||||
// name: 'History',
|
name: 'History',
|
||||||
// regex: /^\/history(\/|$)/,
|
regex: /^\/history(\/|$)/,
|
||||||
// },
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const drawerWidth = 240
|
const drawerWidth = 240
|
||||||
|
|
|
@ -1,10 +1,25 @@
|
||||||
import * as React from 'react'
|
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 { Page, usePageStyles } from '../components/page'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
import { Link } from 'gatsby'
|
||||||
|
|
||||||
const NotFoundPage = () => {
|
const NotFoundPage = ({ location }: any) => {
|
||||||
const classes = usePageStyles()
|
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 (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
@ -20,6 +35,7 @@ const NotFoundPage = () => {
|
||||||
<Typography variant="body1" className={classes.paragraph}>
|
<Typography variant="body1" className={classes.paragraph}>
|
||||||
This page does not exist
|
This page does not exist
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{historyReference}
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,100 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Page, usePageStyles } from '../components/page'
|
import { Page, Section, usePageStyles } from '../components/page'
|
||||||
import { Typography } from '@material-ui/core'
|
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()
|
const classes = usePageStyles()
|
||||||
|
|
||||||
|
data.layers.nodes.sort((a, b) =>
|
||||||
|
a.layer === b.layer ? b.rev - a.rev : b.layer - a.layer
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<Helmet>
|
||||||
|
<title>History</title>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
<div className={classes.heading1}>
|
<div className={classes.heading1}>
|
||||||
<Typography variant="h3" id="tl-reference">
|
<Typography variant="h3" id="tl-reference">
|
||||||
History
|
History
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<Typography variant="body1" className={classes.paragraph}>
|
<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>
|
</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>
|
</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: [
|
nodes: [
|
||||||
{
|
{
|
||||||
layer: number
|
layer: number
|
||||||
|
rev: number
|
||||||
source: {
|
source: {
|
||||||
date: string
|
date: string
|
||||||
commit: string
|
commit: string
|
||||||
|
@ -34,6 +35,9 @@ interface Data {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
historySchemas: { totalCount: number }
|
||||||
|
historyTypes: { totalCount: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
function countMissingDescriptionArguments(
|
function countMissingDescriptionArguments(
|
||||||
|
@ -56,6 +60,8 @@ export default function IndexPage({ data }: { data: Data }) {
|
||||||
countMissingDescriptionArguments(data.argWithoutDesc, true)
|
countMissingDescriptionArguments(data.argWithoutDesc, true)
|
||||||
countMissingDescriptionArguments(data.argWithDesc, false)
|
countMissingDescriptionArguments(data.argWithDesc, false)
|
||||||
|
|
||||||
|
const currentLayer = data.updated.nodes[0]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
toc={[
|
toc={[
|
||||||
|
@ -70,8 +76,17 @@ export default function IndexPage({ data }: { data: Data }) {
|
||||||
TL Reference
|
TL Reference
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
layer {data.updated.nodes[0].layer} / updated{' '}
|
layer {currentLayer.layer}
|
||||||
{data.updated.nodes[0].source.date}
|
{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>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<Typography variant="body1" className={classes.paragraph}>
|
<Typography variant="body1" className={classes.paragraph}>
|
||||||
|
@ -284,6 +299,13 @@ export default function IndexPage({ data }: { data: Data }) {
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
</li>
|
</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>
|
</Typography>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
@ -329,6 +351,7 @@ export const query = graphql`
|
||||||
) {
|
) {
|
||||||
nodes {
|
nodes {
|
||||||
layer
|
layer
|
||||||
|
rev
|
||||||
source {
|
source {
|
||||||
date(formatString: "DD-MM-YYYY")
|
date(formatString: "DD-MM-YYYY")
|
||||||
commit
|
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>
|
||||||
<TableCell>{LinkToTl(node)}</TableCell>
|
<TableCell>{LinkToTl(node)}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{(node.type === 'method' ? 'm_' : 'o_') +
|
{(node.type === 'method'
|
||||||
|
? 'm_'
|
||||||
|
: node.type === 'union'
|
||||||
|
? 'u_'
|
||||||
|
: 'o_') +
|
||||||
(node.prefix === 'mtproto/'
|
(node.prefix === 'mtproto/'
|
||||||
? 'mt_'
|
? '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 { LinkToTl } from '../components/objects/link-to-tl'
|
||||||
import { TableOfContentsItem } from '../components/table-of-contents'
|
import { TableOfContentsItem } from '../components/table-of-contents'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
import { ObjectParameters } from '../components/objects/object-parameters'
|
||||||
|
import { ObjectTsCode } from '../components/objects/object-ts-code'
|
||||||
|
|
||||||
interface GraphqlResult {
|
interface GraphqlResult {
|
||||||
self: ExtendedTlObject
|
self: ExtendedTlObject
|
||||||
|
@ -46,38 +48,6 @@ const useStyles = makeStyles((theme) =>
|
||||||
fontSize: 15,
|
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 obj = data.self
|
||||||
const toc = useToc(obj)
|
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 (
|
return (
|
||||||
<Page toc={toc}>
|
<Page toc={toc}>
|
||||||
<Helmet>
|
<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>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<Description
|
<Description
|
||||||
|
@ -230,41 +176,7 @@ export default function TlObject({ data }: { data: GraphqlResult }) {
|
||||||
/>
|
/>
|
||||||
{obj.type !== 'union' && (
|
{obj.type !== 'union' && (
|
||||||
<Section id="parameters" title="Parameters">
|
<Section id="parameters" title="Parameters">
|
||||||
<Table className={classes.table}>
|
<ObjectParameters obj={obj} />
|
||||||
<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>
|
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
{obj.type === 'union' && (
|
{obj.type === 'union' && (
|
||||||
|
@ -343,61 +255,9 @@ export default function TlObject({ data }: { data: GraphqlResult }) {
|
||||||
</Table>
|
</Table>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
<Typography
|
<Section id="typescript" title="TypeScript declaration">
|
||||||
variant="h4"
|
<ObjectTsCode obj={obj} children={data.children?.nodes} />
|
||||||
id="typescript"
|
</Section>
|
||||||
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}'
|
|
||||||
)}
|
|
||||||
</Page>
|
</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
|
type: string
|
||||||
predicate: string
|
predicate: string
|
||||||
description: string | null
|
description: string | null
|
||||||
|
|
||||||
|
changed?: 'added' | 'modified' | 'removed'
|
||||||
|
className?: string
|
||||||
}[]
|
}[]
|
||||||
throws: {
|
throws: {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
@ -18,3 +18,7 @@ export const isTouchDevice = function (): boolean {
|
||||||
const query = prefixes.map(i => `(${i}touch-enabled)`).join(',')
|
const query = prefixes.map(i => `(${i}touch-enabled)`).join(',')
|
||||||
return mq(query)
|
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)
|
return normalizeGenerics(typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function convertTlToJson(tlText, tlType, silent = false) {
|
function convertTlToJson(tlText, tlType, silent = false) {
|
||||||
let lines = tlText.split('\n')
|
let lines = tlText.split('\n')
|
||||||
let pos = 0
|
let pos = 0
|
||||||
let line = lines[0].trim()
|
let line = lines[0].trim()
|
||||||
|
@ -470,9 +470,9 @@ function convertJsonToTl(json) {
|
||||||
json.methods = json.methods.filter((it) => it.method !== 'http_wait')
|
json.methods = json.methods.filter((it) => it.method !== 'http_wait')
|
||||||
json.constructors.push(httpWait)
|
json.constructors.push(httpWait)
|
||||||
|
|
||||||
json.constructors.forEach(objectToLine)
|
json.constructors.filter(Boolean).forEach(objectToLine)
|
||||||
lines.push('---functions---')
|
lines.push('---functions---')
|
||||||
json.methods.forEach(objectToLine)
|
json.methods.filter(Boolean).forEach(objectToLine)
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,14 +494,14 @@ async function main() {
|
||||||
.then((json) => convertJsonToTl(json))
|
.then((json) => convertJsonToTl(json))
|
||||||
let ret = {}
|
let ret = {}
|
||||||
|
|
||||||
ret.mtproto = await convertTlToJson(mtprotoTl, 'mtproto')
|
ret.mtproto = convertTlToJson(mtprotoTl, 'mtproto')
|
||||||
|
|
||||||
console.log('[i] Fetching api.tl')
|
console.log('[i] Fetching api.tl')
|
||||||
let apiTl = await fetch(
|
let apiTl = await fetch(
|
||||||
'https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/api.tl'
|
'https://raw.githubusercontent.com/telegramdesktop/tdesktop/dev/Telegram/Resources/tl/api.tl'
|
||||||
).then((i) => i.text())
|
).then((i) => i.text())
|
||||||
ret.apiLayer = apiTl.match(/^\/\/ LAYER (\d+)/m)[1]
|
ret.apiLayer = apiTl.match(/^\/\/ LAYER (\d+)/m)[1]
|
||||||
ret.api = await convertTlToJson(apiTl, 'api')
|
ret.api = convertTlToJson(apiTl, 'api')
|
||||||
await addDocumentation(ret.api)
|
await addDocumentation(ret.api)
|
||||||
|
|
||||||
await applyDescriptionsFile(ret, descriptionsYaml)
|
await applyDescriptionsFile(ret, descriptionsYaml)
|
||||||
|
@ -526,7 +526,8 @@ async function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
convertTlToJson
|
convertTlToJson,
|
||||||
|
convertJsonToTl
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
|
Loading…
Reference in a new issue