Convert 10 JavaScript files to TypeScript (#57642)
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
// used below to remove extra newlines in TOC lists
|
||||
const endLine = '</a>\r?\n'
|
||||
const blankLine = '\\s*?[\r\n]*'
|
||||
const startNextLine = '[^\\S\r\n]*?[-\\*] <a'
|
||||
const blankLineInList = new RegExp(`(${endLine})${blankLine}(${startNextLine})`, 'mg')
|
||||
const endLine: string = '</a>\r?\n'
|
||||
const blankLine: string = '\\s*?[\r\n]*'
|
||||
const startNextLine: string = '[^\\S\r\n]*?[-\\*] <a'
|
||||
const blankLineInList: RegExp = new RegExp(`(${endLine})${blankLine}(${startNextLine})`, 'mg')
|
||||
|
||||
export function processLiquidPost(template) {
|
||||
export function processLiquidPost(template: string): string {
|
||||
template = cleanUpListEmptyLines(template)
|
||||
template = cleanUpExtraEmptyLines(template)
|
||||
return template
|
||||
}
|
||||
|
||||
function cleanUpListEmptyLines(template) {
|
||||
function cleanUpListEmptyLines(template: string): string {
|
||||
// clean up empty lines in TOC lists left by unrendered list items (due to productVersions)
|
||||
// for example, remove the blank line here:
|
||||
// - <a>foo</a>
|
||||
@@ -22,7 +22,7 @@ function cleanUpListEmptyLines(template) {
|
||||
return template
|
||||
}
|
||||
|
||||
function cleanUpExtraEmptyLines(template) {
|
||||
function cleanUpExtraEmptyLines(template: string): string {
|
||||
// this removes any extra newlines left by (now resolved) liquid
|
||||
// statements so that extra space doesn't mess with list numbering
|
||||
template = template.replace(/(\r?\n){3}/g, '\n\n')
|
||||
@@ -1,17 +1,26 @@
|
||||
// src/content-render/liquid/prompt.js
|
||||
// src/content-render/liquid/prompt.ts
|
||||
// Defines {% prompt %}…{% endprompt %} to wrap its content in <code> and append the Copilot icon.
|
||||
|
||||
// @ts-ignore - @primer/octicons doesn't provide TypeScript declarations
|
||||
import octicons from '@primer/octicons'
|
||||
|
||||
export const Prompt = {
|
||||
interface LiquidTag {
|
||||
type: 'block'
|
||||
templates?: any[] // Note: Using 'any' because liquidjs doesn't provide proper types for template objects
|
||||
// Note: Using 'any' for liquid-related parameters because liquidjs doesn't provide comprehensive TypeScript definitions
|
||||
parse(tagToken: any, remainTokens: any): void
|
||||
render(scope: any): Generator<any, string, unknown>
|
||||
}
|
||||
|
||||
export const Prompt: LiquidTag = {
|
||||
type: 'block',
|
||||
|
||||
// Collect everything until {% endprompt %}
|
||||
parse(tagToken, remainTokens) {
|
||||
parse(tagToken: any, remainTokens: any): void {
|
||||
this.templates = []
|
||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||
stream
|
||||
.on('template', (tpl) => this.templates.push(tpl))
|
||||
.on('template', (tpl: any) => this.templates.push(tpl))
|
||||
.on('tag:endprompt', () => stream.stop())
|
||||
.on('end', () => {
|
||||
throw new Error(`{% prompt %} tag not closed`)
|
||||
@@ -20,12 +29,12 @@ export const Prompt = {
|
||||
},
|
||||
|
||||
// Render the inner Markdown, wrap in <code>, then append the SVG
|
||||
render: function* (scope) {
|
||||
render: function* (scope: any): Generator<any, string, unknown> {
|
||||
const content = yield this.liquid.renderer.renderTemplates(this.templates, scope)
|
||||
|
||||
// build a URL with the prompt text encoded as query parameter
|
||||
const promptParam = encodeURIComponent(content)
|
||||
const href = `https://github.com/copilot?prompt=${promptParam}`
|
||||
const promptParam: string = encodeURIComponent(content as string)
|
||||
const href: string = `https://github.com/copilot?prompt=${promptParam}`
|
||||
return `<code>${content}</code><a href="${href}" target="_blank" class="tooltipped tooltipped-nw ml-1" aria-label="Run this prompt in Copilot Chat" style="text-decoration:none;">${octicons.copilot.toSVG()}</a>`
|
||||
},
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import { renderContent } from '@/content-render/index'
|
||||
|
||||
describe('prompt tag', () => {
|
||||
test('wraps content in <code> and appends svg', async () => {
|
||||
const input = 'Here is your prompt: {% prompt %}example prompt text{% endprompt %}.'
|
||||
const output = await renderContent(input)
|
||||
const input: string = 'Here is your prompt: {% prompt %}example prompt text{% endprompt %}.'
|
||||
const output: string = await renderContent(input)
|
||||
expect(output).toContain('<code>example prompt text</code><a')
|
||||
expect(output).toContain('<svg')
|
||||
})
|
||||
@@ -1,32 +0,0 @@
|
||||
// Based on https://spec.commonmark.org/0.30/#info-string
|
||||
// Parse out info strings on fenced code blocks, example:
|
||||
// ```javascript lineNumbers:left copy:all annotate
|
||||
// becomes...
|
||||
// node.lang = javascript
|
||||
// node.meta = { lineNumbers: 'left', copy: 'all', annotate: true }
|
||||
// Also parse equals signs, where id=some-id becomes { id: 'some-id' }
|
||||
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
const matcher = (node) => node.type === 'code' && node.lang
|
||||
|
||||
export default function parseInfoString() {
|
||||
return (tree) => {
|
||||
visit(tree, matcher, (node) => {
|
||||
node.meta = strToObj(node.meta)
|
||||
|
||||
// Temporary, remove {:copy} to avoid highlight parse error in translations.
|
||||
node.lang = node.lang.replace('{:copy}', '')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function strToObj(str) {
|
||||
if (!str) return {}
|
||||
return Object.fromEntries(
|
||||
str
|
||||
.split(/\s+/g)
|
||||
.map((k) => k.split(/[:=]/)) // split by colon or equals sign
|
||||
.map(([k, ...v]) => [k, v.length ? v.join(':') : true]),
|
||||
)
|
||||
}
|
||||
45
src/content-render/unified/parse-info-string.ts
Normal file
45
src/content-render/unified/parse-info-string.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
// Based on https://spec.commonmark.org/0.30/#info-string
|
||||
// Parse out info strings on fenced code blocks, example:
|
||||
// ```javascript lineNumbers:left copy:all annotate
|
||||
// becomes...
|
||||
// node.lang = javascript
|
||||
// node.meta = { lineNumbers: 'left', copy: 'all', annotate: true }
|
||||
// Also parse equals signs, where id=some-id becomes { id: 'some-id' }
|
||||
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
interface CodeNode {
|
||||
type: 'code'
|
||||
lang?: string
|
||||
meta?: string | Record<string, string | boolean>
|
||||
value: string
|
||||
}
|
||||
|
||||
// Note: Using 'any' for node because unist-util-visit's type constraints
|
||||
// don't easily allow for proper code node typing without complex generics
|
||||
const matcher = (node: any): node is CodeNode => node.type === 'code' && node.lang
|
||||
|
||||
export default function parseInfoString() {
|
||||
// Note: Using 'any' for tree because unified's AST types are complex and
|
||||
// this function works with different tree types depending on the processor
|
||||
return (tree: any) => {
|
||||
visit(tree, matcher, (node: CodeNode) => {
|
||||
node.meta = strToObj(node.meta as string)
|
||||
|
||||
// Temporary, remove {:copy} to avoid highlight parse error in translations.
|
||||
if (node.lang) {
|
||||
node.lang = node.lang.replace('{:copy}', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function strToObj(str?: string): Record<string, string | boolean> {
|
||||
if (!str) return {}
|
||||
return Object.fromEntries(
|
||||
str
|
||||
.split(/\s+/g)
|
||||
.map((k: string) => k.split(/[:=]/)) // split by colon or equals sign
|
||||
.map(([k, ...v]: string[]) => [k, v.length ? v.join(':') : true]),
|
||||
)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import GithubSlugger from 'github-slugger'
|
||||
import { encode } from 'html-entities'
|
||||
import { toString } from 'hast-util-to-string'
|
||||
import { visit } from 'unist-util-visit'
|
||||
const slugger = new GithubSlugger()
|
||||
|
||||
const matcher = (node) => node.type === 'element' && ['h2', 'h3', 'h4'].includes(node.tagName)
|
||||
|
||||
// replace translated IDs and links in headings with English
|
||||
export default function useEnglishHeadings({ englishHeadings }) {
|
||||
if (!englishHeadings) return
|
||||
return (tree) => {
|
||||
visit(tree, matcher, (node) => {
|
||||
slugger.reset()
|
||||
// Get the plain text content of the heading node
|
||||
const text = toString(node)
|
||||
// find English heading in the collection
|
||||
const englishHeading = englishHeadings[encode(text)]
|
||||
// get English slug
|
||||
const englishSlug = slugger.slug(englishHeading)
|
||||
// use English slug for heading ID and link
|
||||
if (englishSlug) {
|
||||
// only use English slug if there is one, otherwise we'll end up with
|
||||
// empty IDs
|
||||
node.properties.id = englishSlug
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
41
src/content-render/unified/use-english-headings.ts
Normal file
41
src/content-render/unified/use-english-headings.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import GithubSlugger from 'github-slugger'
|
||||
import { encode } from 'html-entities'
|
||||
import { toString } from 'hast-util-to-string'
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
const slugger = new GithubSlugger()
|
||||
|
||||
// Note: Using 'any' for node because the unist/hast type system is complex and
|
||||
// the visit function's type constraints don't easily allow for proper element typing
|
||||
// without extensive type gymnastics. The runtime check ensures type safety.
|
||||
const matcher = (node: any) => node.type === 'element' && ['h2', 'h3', 'h4'].includes(node.tagName)
|
||||
|
||||
interface UseEnglishHeadingsOptions {
|
||||
englishHeadings?: Record<string, string>
|
||||
}
|
||||
|
||||
// replace translated IDs and links in headings with English
|
||||
export default function useEnglishHeadings({ englishHeadings }: UseEnglishHeadingsOptions) {
|
||||
if (!englishHeadings) return
|
||||
// Note: Using 'any' for tree because unified's AST types are complex and
|
||||
// this function works with different tree types depending on the processor
|
||||
return (tree: any) => {
|
||||
// Note: Using 'any' for node because visit() callback typing is restrictive
|
||||
// and doesn't easily allow for proper element typing without complex generics
|
||||
visit(tree, matcher, (node: any) => {
|
||||
slugger.reset()
|
||||
// Get the plain text content of the heading node
|
||||
const text: string = toString(node)
|
||||
// find English heading in the collection
|
||||
const englishHeading: string = englishHeadings[encode(text)]
|
||||
// get English slug
|
||||
const englishSlug: string = slugger.slug(englishHeading)
|
||||
// use English slug for heading ID and link
|
||||
if (englishSlug) {
|
||||
// only use English slug if there is one, otherwise we'll end up with
|
||||
// empty IDs
|
||||
node.properties.id = englishSlug
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user