diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 937a556949..e1956f36d8 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -10,14 +10,20 @@ import path from 'path'; import pluginContentBlog from '../index'; import {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types'; import {PluginOptionSchema} from '../pluginOptionSchema'; +import {PluginOptions, EditUrlFunction} from '../types'; +import Joi from 'joi'; const DefaultI18N: I18n = { currentLocale: 'en', locales: ['en'], defaultLocale: 'en', + localeConfigs: {}, }; -function validateAndNormalize(schema, options) { +function validateAndNormalize( + schema: Joi.ObjectSchema, + options: Partial, +) { const {value, error} = schema.validate(options); if (error) { throw error; @@ -27,8 +33,14 @@ function validateAndNormalize(schema, options) { } describe('loadBlog', () => { - const pluginPath = 'blog'; - const getBlogPosts = async (siteDir) => { + const PluginPath = 'blog'; + + const BaseEditUrl = 'https://baseEditUrl.com/edit'; + + const getBlogPosts = async ( + siteDir: string, + pluginOptions: Partial = {}, + ) => { const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus'); const siteConfig = { title: 'Hello', @@ -43,12 +55,12 @@ describe('loadBlog', () => { i18n: DefaultI18N, } as LoadContext, validateAndNormalize(PluginOptionSchema, { - path: pluginPath, - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1x', + path: PluginPath, + editUrl: BaseEditUrl, + ...pluginOptions, }), ); - const {blogPosts} = await plugin.loadContent(); + const {blogPosts} = (await plugin.loadContent!())!; return blogPosts; }; @@ -58,14 +70,13 @@ describe('loadBlog', () => { const blogPosts = await getBlogPosts(siteDir); expect({ - ...blogPosts.find((v) => v.metadata.title === 'date-matter').metadata, + ...blogPosts.find((v) => v.metadata.title === 'date-matter')!.metadata, ...{prevItem: undefined}, }).toEqual({ - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1x/blog/date-matter.md', + editUrl: `${BaseEditUrl}/blog/date-matter.md`, permalink: '/blog/date-matter', readingTime: 0.02, - source: path.posix.join('@site', pluginPath, 'date-matter.md'), + source: path.posix.join('@site', PluginPath, 'date-matter.md'), title: 'date-matter', description: `date inside front matter`, date: new Date('2019-01-01'), @@ -81,10 +92,9 @@ describe('loadBlog', () => { expect( blogPosts.find( (v) => v.metadata.title === 'Happy 1st Birthday Slash! (translated)', - ).metadata, + )!.metadata, ).toEqual({ - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1x/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md', + editUrl: `${BaseEditUrl}/blog/2018-12-14-Happy-First-Birthday-Slash.md`, permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash', readingTime: 0.015, source: path.posix.join( @@ -105,14 +115,13 @@ describe('loadBlog', () => { }); expect({ - ...blogPosts.find((v) => v.metadata.title === 'Complex Slug').metadata, + ...blogPosts.find((v) => v.metadata.title === 'Complex Slug')!.metadata, ...{prevItem: undefined}, }).toEqual({ - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1x/blog/complex-slug.md', + editUrl: `${BaseEditUrl}/blog/complex-slug.md`, permalink: '/blog/hey/my super path/héllô', readingTime: 0.015, - source: path.posix.join('@site', pluginPath, 'complex-slug.md'), + source: path.posix.join('@site', PluginPath, 'complex-slug.md'), title: 'Complex Slug', description: `complex url slug`, prevItem: undefined, @@ -126,14 +135,13 @@ describe('loadBlog', () => { }); expect({ - ...blogPosts.find((v) => v.metadata.title === 'Simple Slug').metadata, + ...blogPosts.find((v) => v.metadata.title === 'Simple Slug')!.metadata, ...{prevItem: undefined}, }).toEqual({ - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1x/blog/simple-slug.md', + editUrl: `${BaseEditUrl}/blog/simple-slug.md`, permalink: '/blog/simple/slug', readingTime: 0.015, - source: path.posix.join('@site', pluginPath, 'simple-slug.md'), + source: path.posix.join('@site', PluginPath, 'simple-slug.md'), title: 'Simple Slug', description: `simple url slug`, prevItem: undefined, @@ -147,6 +155,59 @@ describe('loadBlog', () => { }); }); + test('edit url with editLocalizedBlogs true', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true}); + + const localizedBlogPost = blogPosts.find( + (v) => v.metadata.title === 'Happy 1st Birthday Slash! (translated)', + )!; + + expect(localizedBlogPost.metadata.editUrl).toEqual( + `${BaseEditUrl}/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md`, + ); + }); + + test('edit url with editUrl function', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + + const hardcodedEditUrl = 'hardcoded-edit-url'; + const editUrlFunction: EditUrlFunction = jest.fn(() => hardcodedEditUrl); + + const blogPosts = await getBlogPosts(siteDir, {editUrl: editUrlFunction}); + + blogPosts.forEach((blogPost) => { + expect(blogPost.metadata.editUrl).toEqual(hardcodedEditUrl); + }); + + expect(editUrlFunction).toHaveBeenCalledTimes(5); + expect(editUrlFunction).toHaveBeenCalledWith({ + blogDirPath: 'blog', + blogPath: 'date-matter.md', + locale: 'en', + }); + expect(editUrlFunction).toHaveBeenCalledWith({ + blogDirPath: 'blog', + blogPath: 'draft.md', + locale: 'en', + }); + expect(editUrlFunction).toHaveBeenCalledWith({ + blogDirPath: 'blog', + blogPath: 'complex-slug.md', + locale: 'en', + }); + expect(editUrlFunction).toHaveBeenCalledWith({ + blogDirPath: 'blog', + blogPath: 'simple-slug.md', + locale: 'en', + }); + expect(editUrlFunction).toHaveBeenCalledWith({ + blogDirPath: 'i18n/en/docusaurus-plugin-content-blog', + blogPath: '2018-12-14-Happy-First-Birthday-Slash.md', + locale: 'en', + }); + }); + test('draft blog post not exists in production build', async () => { process.env.NODE_ENV = 'production'; const siteDir = path.join(__dirname, '__fixtures__', 'website'); @@ -162,17 +223,16 @@ describe('loadBlog', () => { 'website-blog-without-date', ); const blogPosts = await getBlogPosts(siteDir); - const noDateSource = path.posix.join('@site', pluginPath, 'no date.md'); + const noDateSource = path.posix.join('@site', PluginPath, 'no date.md'); const noDateSourceBirthTime = ( await fs.stat(noDateSource.replace('@site', siteDir)) ).birthtime; expect({ - ...blogPosts.find((v) => v.metadata.title === 'no date').metadata, + ...blogPosts.find((v) => v.metadata.title === 'no date')!.metadata, ...{prevItem: undefined}, }).toEqual({ - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1x/blog/no date.md', + editUrl: `${BaseEditUrl}/blog/no date.md`, permalink: '/blog/no date', readingTime: 0.01, source: noDateSource, diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index 6e30e84e96..07a69803ae 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -26,6 +26,7 @@ import { aliasedSitePath, getEditUrl, getFolderContainingFile, + posixPath, } from '@docusaurus/utils'; import {LoadContext} from '@docusaurus/types'; import {keyBy} from 'lodash'; @@ -99,7 +100,7 @@ export async function generateBlogFeed( export async function generateBlogPosts( contentPaths: BlogContentPaths, - {siteConfig, siteDir}: LoadContext, + {siteConfig, siteDir, i18n}: LoadContext, options: PluginOptions, ): Promise { const { @@ -107,7 +108,7 @@ export async function generateBlogPosts( routeBasePath, truncateMarker, showReadingTime, - editUrl, + editUrl: siteEditUrl, } = options; if (!fs.existsSync(contentPaths.contentPath)) { @@ -124,18 +125,47 @@ export async function generateBlogPosts( await Promise.all( blogSourceFiles.map(async (blogSourceFile: string) => { // Lookup in localized folder in priority - const contentPath = await getFolderContainingFile( + const blogDirPath = await getFolderContainingFile( getContentPathList(contentPaths), blogSourceFile, ); - const source = path.join(contentPath, blogSourceFile); + const source = path.join(blogDirPath, blogSourceFile); + const aliasedSource = aliasedSitePath(source, siteDir); - const relativePath = path.relative(siteDir, source); const blogFileName = path.basename(blogSourceFile); - const editBlogUrl = getEditUrl(relativePath, editUrl); + function getBlogEditUrl() { + const blogPathRelative = path.relative( + blogDirPath, + path.resolve(source), + ); + + if (typeof siteEditUrl === 'function') { + return siteEditUrl({ + blogDirPath: posixPath(path.relative(siteDir, blogDirPath)), + blogPath: posixPath(blogPathRelative), + locale: i18n.currentLocale, + }); + } else if (typeof siteEditUrl === 'string') { + const isLocalized = blogDirPath === contentPaths.contentPathLocalized; + const fileContentPath = + isLocalized && options.editLocalizedFiles + ? contentPaths.contentPathLocalized + : contentPaths.contentPath; + + const contentPathEditUrl = normalizeUrl([ + siteEditUrl, + posixPath(path.relative(siteDir, fileContentPath)), + ]); + + return getEditUrl(blogPathRelative, contentPathEditUrl); + } else { + return undefined; + } + } + const editBlogUrl = getBlogEditUrl(); const {frontMatter, content, excerpt} = await parseMarkdownFile(source); diff --git a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts index fe396f180b..2e66c7d8cb 100644 --- a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts @@ -34,6 +34,7 @@ export const DEFAULT_OPTIONS = { include: ['*.md', '*.mdx'], routeBasePath: 'blog', path: 'blog', + editLocalizedFiles: false, }; export const PluginOptionSchema = Joi.object({ @@ -67,7 +68,8 @@ export const PluginOptionSchema = Joi.object({ remarkPlugins: RemarkPluginsSchema.default(DEFAULT_OPTIONS.remarkPlugins), rehypePlugins: RehypePluginsSchema.default(DEFAULT_OPTIONS.rehypePlugins), admonitions: AdmonitionsSchema.default(DEFAULT_OPTIONS.admonitions), - editUrl: URISchema, + editUrl: Joi.alternatives().try(URISchema, Joi.function()), + editLocalizedFiles: Joi.boolean().default(DEFAULT_OPTIONS.editLocalizedFiles), truncateMarker: Joi.object().default(DEFAULT_OPTIONS.truncateMarker), beforeDefaultRemarkPlugins: RemarkPluginsSchema.default( DEFAULT_OPTIONS.beforeDefaultRemarkPlugins, diff --git a/packages/docusaurus-plugin-content-blog/src/types.ts b/packages/docusaurus-plugin-content-blog/src/types.ts index 4d1864b4ac..bd09792add 100644 --- a/packages/docusaurus-plugin-content-blog/src/types.ts +++ b/packages/docusaurus-plugin-content-blog/src/types.ts @@ -24,6 +24,12 @@ export interface DateLink { export type FeedType = 'rss' | 'atom'; +export type EditUrlFunction = (editUrlParams: { + blogDirPath: string; + blogPath: string; + locale: string; +}) => string | undefined; + export interface PluginOptions { id?: string; path: string; @@ -57,7 +63,8 @@ export interface PluginOptions { copyright: string; language?: string; }; - editUrl?: string; + editUrl?: string | EditUrlFunction; + editLocalizedFiles?: boolean; admonitions: Record; } diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts index 5872e12826..1991569ff0 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -15,6 +15,7 @@ import { MetadataOptions, VersionMetadata, PluginOptions, + EditUrlFunction, } from '../types'; import {LoadContext} from '@docusaurus/types'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants'; @@ -285,6 +286,45 @@ describe('simple site', () => { }); }); + test('docs with function editUrl', async () => { + const hardcodedEditUrl = 'hardcoded-edit-url'; + + const editUrlFunction: EditUrlFunction = jest.fn(() => hardcodedEditUrl); + + const {siteDir, context, options, currentVersion} = await loadSite({ + options: { + editUrl: editUrlFunction, + }, + }); + + const testUtilsLocal = createTestUtils({ + siteDir, + context, + options, + versionMetadata: currentVersion, + }); + + await testUtilsLocal.testMeta(path.join('foo', 'baz.md'), { + version: 'current', + id: 'foo/baz', + unversionedId: 'foo/baz', + isDocsHomePage: false, + permalink: '/docs/foo/bazSlug.html', + slug: '/foo/bazSlug.html', + title: 'baz', + editUrl: hardcodedEditUrl, + description: 'Images', + }); + + expect(editUrlFunction).toHaveBeenCalledTimes(1); + expect(editUrlFunction).toHaveBeenCalledWith({ + version: 'current', + versionDocsDirPath: 'docs', + docPath: path.posix.join('foo', 'baz.md'), + locale: 'en', + }); + }); + test('docs with last update time and author', async () => { const {siteDir, context, options, currentVersion} = await loadSite({ options: { @@ -595,6 +635,47 @@ describe('versioned site', () => { ); }); + test('doc with editUrl function', async () => { + const hardcodedEditUrl = 'hardcoded-edit-url'; + + const editUrlFunction: EditUrlFunction = jest.fn(() => hardcodedEditUrl); + + const {siteDir, context, options, version100} = await loadSite({ + options: { + editUrl: editUrlFunction, + }, + }); + + const testUtilsLocal = createTestUtils({ + siteDir, + context, + options, + versionMetadata: version100, + }); + + await testUtilsLocal.testMeta(path.join('hello.md'), { + id: 'version-1.0.0/hello', + unversionedId: 'hello', + isDocsHomePage: false, + permalink: '/docs/1.0.0/hello', + slug: '/hello', + title: 'hello', + description: 'Hello 1.0.0 ! (translated en)', + version: '1.0.0', + source: + '@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md', + editUrl: hardcodedEditUrl, + }); + + expect(editUrlFunction).toHaveBeenCalledTimes(1); + expect(editUrlFunction).toHaveBeenCalledWith({ + version: '1.0.0', + versionDocsDirPath: 'versioned_docs/version-1.0.0', + docPath: path.join('hello.md'), + locale: 'en', + }); + }); + test('translated doc with editUrl', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { @@ -657,11 +738,11 @@ describe('versioned site', () => { }); }); - test('translated fr doc with editUrl and editLocalizedDocs=true', async () => { + test('translated fr doc with editUrl and editLocalizedFiles=true', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/master/website', - editLocalizedDocs: true, + editLocalizedFiles: true, }, locale: 'fr', }); @@ -689,12 +770,12 @@ describe('versioned site', () => { }); }); - test('translated fr doc with editUrl and editLocalizedDocs=true + editCurrentVersion=true', async () => { + test('translated fr doc with editUrl and editLocalizedFiles=true + editCurrentVersion=true', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/master/website', editCurrentVersion: true, - editLocalizedDocs: true, + editLocalizedFiles: true, }, locale: 'fr', }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts index 94a1a3d46b..fd1c61ddf0 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts @@ -39,7 +39,7 @@ describe('normalizeDocsPluginOptions', () => { includeCurrentVersion: false, disableVersioning: true, editCurrentVersion: true, - editLocalizedDocs: true, + editLocalizedFiles: true, versions: { current: { path: 'next', diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index bf6dfc0cad..a818ba8054 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -13,6 +13,7 @@ import { getFolderContainingFile, normalizeUrl, parseMarkdownString, + posixPath, } from '@docusaurus/utils'; import {LoadContext} from '@docusaurus/types'; @@ -120,14 +121,29 @@ export function processDocMetadata({ const relativeFilePath = path.relative(docsDirPath, filePath); - const isLocalized = docsDirPath === versionMetadata.docsDirPathLocalized; + function getDocEditUrl() { + if (typeof options.editUrl === 'function') { + return options.editUrl({ + version: versionMetadata.versionName, + versionDocsDirPath: posixPath( + path.relative(siteDir, versionMetadata.docsDirPath), + ), + docPath: posixPath(relativeFilePath), + locale: context.i18n.currentLocale, + }); + } else if (typeof options.editUrl === 'string') { + const isLocalized = docsDirPath === versionMetadata.docsDirPathLocalized; + const baseVersionEditUrl = + isLocalized && options.editLocalizedFiles + ? versionMetadata.versionEditUrlLocalized + : versionMetadata.versionEditUrl; + return getEditUrl(relativeFilePath, baseVersionEditUrl); + } else { + return undefined; + } + } - const versionEditUrl = - isLocalized && options.editLocalizedDocs - ? versionMetadata.versionEditUrlLocalized - : versionMetadata.versionEditUrl; - - const docsEditUrl = getEditUrl(relativeFilePath, versionEditUrl); + const docsEditUrl = getDocEditUrl(); const {frontMatter = {}, excerpt} = parseMarkdownString(content); const {sidebar_label, custom_edit_url} = frontMatter; diff --git a/packages/docusaurus-plugin-content-docs/src/options.ts b/packages/docusaurus-plugin-content-docs/src/options.ts index 9af7993fb2..f53ded796d 100644 --- a/packages/docusaurus-plugin-content-docs/src/options.ts +++ b/packages/docusaurus-plugin-content-docs/src/options.ts @@ -38,7 +38,7 @@ export const DEFAULT_OPTIONS: Omit = { lastVersion: undefined, versions: {}, editCurrentVersion: false, - editLocalizedDocs: false, + editLocalizedFiles: false, }; const VersionOptionsSchema = Joi.object({ @@ -52,9 +52,9 @@ const VersionsOptionsSchema = Joi.object() export const OptionsSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), - editUrl: URISchema, + editUrl: Joi.alternatives().try(URISchema, Joi.function()), editCurrentVersion: Joi.boolean().default(DEFAULT_OPTIONS.editCurrentVersion), - editLocalizedDocs: Joi.boolean().default(DEFAULT_OPTIONS.editLocalizedDocs), + editLocalizedFiles: Joi.boolean().default(DEFAULT_OPTIONS.editLocalizedFiles), routeBasePath: Joi.string() // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374 // .allow('') "" diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index ca5729a0e1..c905a1dc4b 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -31,12 +31,19 @@ export type VersionMetadata = { routePriority: number | undefined; // -1 for the latest docs }; +export type EditUrlFunction = (editUrlParams: { + version: string; + versionDocsDirPath: string; + docPath: string; + locale: string; +}) => string | undefined; + export type MetadataOptions = { routeBasePath: string; homePageId?: string; - editUrl?: string; + editUrl?: string | EditUrlFunction; editCurrentVersion: boolean; - editLocalizedDocs: boolean; + editLocalizedFiles: boolean; showLastUpdateTime?: boolean; showLastUpdateAuthor?: boolean; }; diff --git a/packages/docusaurus-plugin-content-docs/src/versions.ts b/packages/docusaurus-plugin-content-docs/src/versions.ts index 34ffd1409e..2cc9a13a56 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions.ts @@ -211,6 +211,12 @@ function getVersionEditUrls({ return undefined; } + // if the user is using the functional form of editUrl, + // he has total freedom and we can't compute a "version edit url" + if (typeof editUrl === 'function') { + return undefined; + } + const editDirPath = editCurrentVersion ? currentVersionPath : docsDirPath; const editDirPathLocalized = editCurrentVersion ? getDocsDirPathLocalized({ diff --git a/website/docs/api/plugins/plugin-content-blog.md b/website/docs/api/plugins/plugin-content-blog.md index f5bc687c36..89c56444e2 100644 --- a/website/docs/api/plugins/plugin-content-blog.md +++ b/website/docs/api/plugins/plugin-content-blog.md @@ -31,11 +31,23 @@ module.exports = { */ path: 'blog', /** - * URL for editing a blog post. - * Example: 'https://github.com/facebook/docusaurus/edit/master/website/blog/' + * Base url to edit your site. + * Docusaurus will compute the final editUrl with "editUrl + relativeDocPath" */ - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website/blog/', + editUrl: 'https://github.com/facebook/docusaurus/edit/master/website/', + /** + * For advanced cases, compute the edit url for each markdown file yourself. + */ + editUrl: ({locale, blogDirPath, blogPath}) => { + return `https://github.com/facebook/docusaurus/edit/master/website/${blogDirPath}/${blogPath}`; + }, + /** + * Useful if you commit localized files to git. + * When markdown files are localized, the edit url will target the localized file, + * instead of the original unlocalized file. + * Note: this option is ignored when editUrl is a function + */ + editLocalizedFiles: false, /** * Blog page title for better SEO */ diff --git a/website/docs/api/plugins/plugin-content-docs.md b/website/docs/api/plugins/plugin-content-docs.md index cb24ef678a..e7d0cd7f1a 100644 --- a/website/docs/api/plugins/plugin-content-docs.md +++ b/website/docs/api/plugins/plugin-content-docs.md @@ -31,22 +31,30 @@ module.exports = { */ path: 'docs', /** - * URL for editing a doc in the website repo. - * Example: 'https://github.com/facebook/docusaurus/edit/master/website/' + * Base url to edit your site. + * Docusaurus will compute the final editUrl with "editUrl + relativeDocPath" */ editUrl: 'https://github.com/facebook/docusaurus/edit/master/website/', /** + * For advanced cases, compute the edit url for each markdown file yourself. + */ + editUrl: function ({locale, version, versionDocsDirPath, docPath}) { + return `https://github.com/facebook/docusaurus/edit/master/website/${versionDocsDirPath}/${docPath}`; + }, + /** + * Useful if you commit localized files to git. + * When markdown files are localized, the edit url will target the localized file, + * instead of the original unlocalized file. + * Note: this option is ignored when editUrl is a function + */ + editLocalizedFiles: false, + /** + * Useful if you don't want users to submit doc pull-requests to older versions. * When docs are versioned, the edit url will link to the doc * in current version, instead of the versioned doc. - * Useful if you don't want users to submit doc pull-requests to older versions. + * Note: this option is ignored when editUrl is a function */ editCurrentVersion: false, - /** - * When docs are localized, the edit url will target the localized doc, - * instead of the original unlocalized doc. - * Useful if you commit localized docs to git, instead of using a translation service. - */ - editLocalizedDocs: false, /** * URL route for the docs section of your site. * *DO NOT* include a trailing slash. diff --git a/website/docs/i18n/i18n-crowdin.mdx b/website/docs/i18n/i18n-crowdin.mdx index 193ce745bb..497a5bf772 100644 --- a/website/docs/i18n/i18n-crowdin.mdx +++ b/website/docs/i18n/i18n-crowdin.mdx @@ -409,6 +409,54 @@ Crowdin replaces markdown strings with technical ids such as `crowdin:id12345`, ::: +### Localize edit urls + +When the user is browsing a page at `/fr/doc1`, the edit button will link by default to the unlocalized doc at `website/docs/doc1.md`. + +You may prefer the edit button to link to the Crowdin interface instead, and can use the `editUrl` function to customize the edit urls on a per-locale basis. + +```js title="docusaurus.config.js" +const DefaultLocale = 'en'; + +module.exports = { + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + // highlight-start + editUrl: ({locale, versionDocsDirPath, docPath}) => { + // Link to Crowdin for French docs + if (locale !== DefaultLocale) { + return `https://crowdin.com/project/docusaurus-v2/${locale}`; + } + // Link to Github for English docs + return `https://github.com/facebook/docusaurus/edit/master/website/${versionDocsDirPath}/${docPath}`; + }, + // highlight-end + }, + blog: { + // highlight-start + editUrl: ({locale, blogDirPath, blogPath}) => { + if (locale !== DefaultLocale) { + return `https://crowdin.com/project/docusaurus-v2/${locale}`; + } + return `https://github.com/facebook/docusaurus/edit/master/website/${blogDirPath}/${blogPath}`; + }, + // highlight-start + }, + }, + ], + ], +}; +``` + +:::note + +It is currently **not possible to link to a specific file** in Crowdin. + +::: + ### Example configuration The **Docusaurus v2 configuration file** is a good example of using versioning and multi-instance: diff --git a/website/docs/i18n/i18n-git.md b/website/docs/i18n/i18n-git.md index 40f92c2d20..e28809c921 100644 --- a/website/docs/i18n/i18n-git.md +++ b/website/docs/i18n/i18n-git.md @@ -171,3 +171,11 @@ New translation will be appended, and existing ones will not be overridden. Reset your translations with the `--override` option. ::: + +### Localize edit urls + +When the user is browsing a page at `/fr/doc1`, the edit button will link by default to the unlocalized doc at `website/docs/doc1.md`. + +Your translations are on Git, and you can use the `editLocalizedFiles: true` option of the docs and blog plugins. + +The edit button will link to the localized doc at `i18n/fr/docusaurus-plugin-content-docs/current/doc1.md`. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index b6c85b86f1..1eaac7b084 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -41,6 +41,7 @@ const isBootstrapPreset = process.env.DOCUSAURUS_PRESET === 'bootstrap'; const isVersioningDisabled = !!process.env.DISABLE_VERSIONING; +/** @type {import('@docusaurus/types').DocusaurusConfig} */ module.exports = { title: 'Docusaurus', tagline: 'Build optimized websites quickly, focus on your content', @@ -76,7 +77,12 @@ module.exports = { { id: 'community', path: 'community', - editUrl: 'https://github.com/facebook/docusaurus/edit/master/website/', + editUrl: ({locale, versionDocsDirPath, docPath}) => { + if (locale !== 'en') { + return `https://crowdin.com/project/docusaurus-v2/${locale}`; + } + return `https://github.com/facebook/docusaurus/edit/master/website/${versionDocsDirPath}/${docPath}`; + }, editCurrentVersion: true, routeBasePath: 'community', sidebarPath: require.resolve('./sidebarsCommunity.js'), @@ -204,8 +210,12 @@ module.exports = { // routeBasePath: '/', path: 'docs', sidebarPath: require.resolve('./sidebars.js'), - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website/', + editUrl: ({locale, versionDocsDirPath, docPath}) => { + if (locale !== 'en') { + return `https://crowdin.com/project/docusaurus-v2/${locale}`; + } + return `https://github.com/facebook/docusaurus/edit/master/website/${versionDocsDirPath}/${docPath}`; + }, editCurrentVersion: true, showLastUpdateAuthor: true, showLastUpdateTime: true, @@ -227,8 +237,12 @@ module.exports = { blog: { // routeBasePath: '/', path: '../website-1.x/blog', - editUrl: - 'https://github.com/facebook/docusaurus/edit/master/website-1.x/', + editUrl: ({locale, blogDirPath, blogPath}) => { + if (locale !== 'en') { + return `https://crowdin.com/project/docusaurus-v2/${locale}`; + } + return `https://github.com/facebook/docusaurus/edit/master/website/${blogDirPath}/${blogPath}`; + }, postsPerPage: 3, feedOptions: { type: 'all',