diff --git a/package.json b/package.json index 2df824f51804..00ed731b47ac 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "check-github-github-links": "node src/links/scripts/check-github-github-links.js", "close-dangling-prs": "tsx src/workflows/close-dangling-prs.ts", "content-changes-table-comment": "tsx src/workflows/content-changes-table-comment.ts", + "convert-liquid-markdown-tables": "tsx src/tools/scripts/convert-liquid-markdown-tables.ts", "copy-fixture-data": "node src/tests/scripts/copy-fixture-data.js", "count-translation-corruptions": "tsx src/languages/scripts/count-translation-corruptions.ts", "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect src/frame/server.ts", @@ -42,6 +43,7 @@ "lint": "eslint '**/*.{js,mjs,ts,tsx}'", "lint-content": "node src/content-linter/scripts/lint-content.js", "lint-translation": "vitest src/content-linter/tests/lint-files.js", + "liquid-markdown-tables": "tsx src/tools/scripts/liquid-markdown-tables/index.ts", "generate-code-scanning-query-list": "tsx src/code-scanning/scripts/generate-code-scanning-query-list.ts", "generate-content-linter-docs": "tsx src/content-linter/scripts/generate-docs.ts", "move-content": "node src/content-render/scripts/move-content.js", diff --git a/src/fixtures/tests/playwright-rendering.spec.ts b/src/fixtures/tests/playwright-rendering.spec.ts index 268779d0deba..20e963a4f4e7 100644 --- a/src/fixtures/tests/playwright-rendering.spec.ts +++ b/src/fixtures/tests/playwright-rendering.spec.ts @@ -615,6 +615,7 @@ test.describe('translations', () => { test('switch to Japanese from English using widget on article', async ({ page }) => { await page.goto('/get-started/start-your-journey/hello-world') + await expect(page).toHaveURL('/en/get-started/start-your-journey/hello-world') await page.getByRole('button', { name: 'Select language: current language is English' }).click() await page.getByRole('menuitemradio', { name: '日本語' }).click() await expect(page).toHaveURL('/ja/get-started/start-your-journey/hello-world') @@ -631,7 +632,6 @@ test.describe('translations', () => { // If you go, with the Japanese cookie, to the English page directly, // it will offer a link to the Japanese URL in a banner. await page.goto('/en/get-started/start-your-journey/hello-world') - await page.getByRole('link', { name: 'Japanese' }).click() await expect(page).toHaveURL('/ja/get-started/start-your-journey/hello-world') }) }) diff --git a/src/frame/components/ClientSideLanguageRedirect.ts b/src/frame/components/ClientSideLanguageRedirect.ts new file mode 100644 index 000000000000..6d75c2fe7705 --- /dev/null +++ b/src/frame/components/ClientSideLanguageRedirect.ts @@ -0,0 +1,21 @@ +import { useEffect } from 'react' +import { useRouter } from 'next/router' + +import { useLanguages } from 'src/languages/components/LanguagesContext' +import Cookies from 'src/frame/components/lib/cookies' +import { USER_LANGUAGE_COOKIE_NAME } from 'src/frame/lib/constants.js' + +export function ClientSideLanguageRedirect() { + const { locale, asPath, replace } = useRouter() + const { languages } = useLanguages() + const availableLanguageKeys = new Set(Object.keys(languages)) + + useEffect(() => { + const cookieValue = Cookies.get(USER_LANGUAGE_COOKIE_NAME) + if (cookieValue && cookieValue !== locale && availableLanguageKeys.has(cookieValue)) { + const newPath = `/${cookieValue}${asPath}` + replace(newPath, undefined, { locale: cookieValue }) + } + }, [locale, availableLanguageKeys, asPath]) + return null +} diff --git a/src/frame/components/DefaultLayout.tsx b/src/frame/components/DefaultLayout.tsx index d6fecb1985e5..57ca23c59120 100644 --- a/src/frame/components/DefaultLayout.tsx +++ b/src/frame/components/DefaultLayout.tsx @@ -12,6 +12,7 @@ import { useMainContext } from 'src/frame/components/context/MainContext' import { useTranslation } from 'src/languages/components/useTranslation' import { Breadcrumbs } from 'src/frame/components/page-header/Breadcrumbs' import { useLanguages } from 'src/languages/components/LanguagesContext' +import { ClientSideLanguageRedirect } from './ClientSideLanguageRedirect' import { DomainNameEditProvider } from 'src/links/components/useEditableDomainContext' const MINIMAL_RENDER = Boolean(JSON.parse(process.env.MINIMAL_RENDER || 'false')) @@ -124,6 +125,7 @@ export const DefaultLayout = (props: Props) => { Skip to main content
+
{isHomepageVersion ? null : } {/* Need to set an explicit height for sticky elements since we also diff --git a/src/tools/scripts/liquid-markdown-tables/convert.ts b/src/tools/scripts/liquid-markdown-tables/convert.ts new file mode 100644 index 000000000000..587c5a40d5fe --- /dev/null +++ b/src/tools/scripts/liquid-markdown-tables/convert.ts @@ -0,0 +1,43 @@ +/** + * See docstring in index.ts for more information about how to use this script. + */ +import fs from 'fs' + +import chalk from 'chalk' + +import { processFile } from './lib' + +type Options = { + dryRun: boolean +} + +export async function convert(files: string[], options: Options) { + if (!files.length) { + console.error(chalk.red('No files specified')) + process.exit(1) + } + + for (const file of files) { + const info = fs.statSync(file) + if (info.isDirectory()) { + console.error(chalk.red('Directories are currently not supported. Only files.')) + process.exit(1) + } + } + + for (const file of files) { + console.log(chalk.grey(`Processing file ${chalk.bold(file)}`)) + const content = fs.readFileSync(file, 'utf8') + const newContent = await processFile(content) + if (content !== newContent) { + if (options.dryRun) { + console.log(chalk.green('Would have written changes to disk')) + } else { + console.log(chalk.green(`Updating ${chalk.bold(file)}`)) + fs.writeFileSync(file, newContent, 'utf-8') + } + } else { + console.log(chalk.yellow('No changes needed')) + } + } +} diff --git a/src/tools/scripts/liquid-markdown-tables/find.ts b/src/tools/scripts/liquid-markdown-tables/find.ts new file mode 100644 index 000000000000..f696bfa30150 --- /dev/null +++ b/src/tools/scripts/liquid-markdown-tables/find.ts @@ -0,0 +1,45 @@ +import fs from 'fs' + +import chalk from 'chalk' +import walk from 'walk-sync' + +import { processFile } from './lib' + +type Options = { + filter?: string[] +} + +export async function find(options: Options) { + const files = [ + ...walk('data', { + includeBasePath: true, + globs: ['**/*.md'], + ignore: ['**/README.md'], + }), + ...walk('content', { + includeBasePath: true, + globs: ['**/*.md'], + ignore: ['**/README.md'], + }), + ].filter((filePath) => { + if (options.filter && options.filter.length) { + return options.filter.some((filter) => filePath.includes(filter)) + } + return true + }) + console.log(chalk.grey(`${chalk.bold(files.length.toLocaleString())} files to search.`)) + + const found: string[] = [] + for (const filePath of files) { + const content = fs.readFileSync(filePath, 'utf8') + const newContent = await processFile(content) + if (content !== newContent) { + console.log(chalk.green(filePath)) + found.push(filePath) + } + } + console.log('\n') + console.log( + chalk.grey(`Found ${chalk.bold(found.length.toLocaleString())} files that can be converted.`), + ) +} diff --git a/src/tools/scripts/liquid-markdown-tables/index.ts b/src/tools/scripts/liquid-markdown-tables/index.ts new file mode 100644 index 000000000000..e904256eb8b8 --- /dev/null +++ b/src/tools/scripts/liquid-markdown-tables/index.ts @@ -0,0 +1,69 @@ +/** + * This script helps you rewrite Markdown files that might contain + * tables with Liquid `ifversion` tags the old/wrong way. + * For example: + * + * | Header | Header 2 | + * |--------|----------| + * | bla | bla |{% ifversion dependency-review-action-licenses %} + * | foo | foo |{% endif %}{% ifversion dependency-review-action-fail-on-scopes %} + * | bar | bar |{% endif %} + * | baz | baz | + * {%- ifversion dependency-review-action-licenses %} + * | qux | qux |{% endif %} + * + * Will become: + * + * | Header | Header 2 | + * |--------|----------| + * | bla | bla | + * | {% ifversion dependency-review-action-licenses %} | + * | foo | foo | + * | {% endif %} | + * | {% ifversion dependency-review-action-fail-on-scopes %} | + * | bar | bar | + * | {% endif %} | + * | baz | baz | + * | {% ifversion dependency-review-action-licenses %} | + * | qux | qux | + * | {% endif %} | + * + * Run the script like this: + * + * npm run liquid-markdown-tables -- convert content/path/to/article.md + * git diff + * + * To *find* files that you *can* convert, use: + * + * npm run liquid-markdown-tables -- find + * # or + * npm run liquid-markdown-tables -- find --filter content/mydocset + * + * This will print out paths to files that most likely contain the old/wrong Liquid `ifversion` tags. + * + */ + +import { program } from 'commander' + +import { convert } from './convert' +import { find } from './find' + +program + .name('liquid-markdown-tables') + .description('CLI for finding and converting Liquid in Markdown tables') + +program + .command('convert') + .description('Clean up Markdown tables that use Liquid `ifversion` tags the old/wrong way') + .option('--dry-run', "Don't actually write changes to disk", false) + // .arguments('[files-or-directories...]', '') + .arguments('[files...]') + .action(convert) + +program + .command('find') + .description('Find Markdown tables that use Liquid `ifversion` tags the old/wrong way') + .option('--filter ', 'Filter by file path') + .action(find) + +program.parse(process.argv) diff --git a/src/tools/scripts/liquid-markdown-tables/lib.ts b/src/tools/scripts/liquid-markdown-tables/lib.ts new file mode 100644 index 000000000000..6c071d055a42 --- /dev/null +++ b/src/tools/scripts/liquid-markdown-tables/lib.ts @@ -0,0 +1,68 @@ +// E.g. `{%- ifversion dependency-review-action-licenses %}\n` +const ifVersionRegex = /^{%-?\s*ifversion\s+([\w- ]+)\s*-?%}\n/ +const ifVersionEndRegex = /\|({%-?\s*ifversion\s+([\w- ]+)\s*-?%})\n/ +// E.g. `... |{% endif %}{% ifversion dependency-review-action-fail-on-scopes %}\n` +const endifIfVersionRegex = /\|({%-?\s*endif\s*%})({%-?\sifversion\s+([\w- ]+)\s*-?%})\n/ +const endifRegex = /\|({%-?\s*endif\s*%})\n/ +const endifAloneRegex = /^({%-?\s*endif\s*%})\n/ + +// Split a string by newlines while keeping the newlines +function splitAndKeepNewlines(str: string) { + const lines = str.split(/(\r\n|\r|\n)/) + const result: string[] = [] + for (let i = 0; i < lines.length; i++) { + if (lines[i].match(/(\r\n|\r|\n)/)) { + result[result.length - 1] += lines[i] + } else { + result.push(lines[i]) + } + } + return result +} + +export async function processFile(content: string) { + let inTable = false + let inFrontmatter = false + let inMarkdown = false + const newLines: string[] = [] + for (let line of splitAndKeepNewlines(content)) { + if (line === '---\n') { + if (!inFrontmatter) { + inFrontmatter = true + } else { + inFrontmatter = false + inMarkdown = true + } + } + if (inMarkdown) { + if (line.startsWith('|') && line.endsWith('|\n')) { + inTable = true + } else if (inTable && line === '\n') { + inTable = false + } + if (inTable) { + // E.g. `{%- ifversion dependency-review-action-licenses %}\n` + if (ifVersionRegex.test(line)) { + const better = line.replace('{%-', '{%').replace('-%}', '%}').trim() + line = `| ${better} |\n` + } else if (ifVersionEndRegex.test(line)) { + line = line + .replace(ifVersionEndRegex, '|\n| $1 |\n') + .replace('{%-', '{%') + .replace('-%}', '%}') + } else if (endifIfVersionRegex.test(line)) { + line = line + .replace(endifIfVersionRegex, '|\n| $1 |\n| $2 |\n') + .replace('{%-', '{%') + .replace('-%}', '%}') + } else if (endifRegex.test(line)) { + line = line.replace(endifRegex, '|\n| $1 |\n') + } else if (endifAloneRegex.test(line)) { + line = line.replace(endifAloneRegex, '| $1 |\n').replace('{%-', '{%') + } + } + } + newLines.push(line) + } + return newLines.join('') +}