@docusaurus/plugin-content-pages 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ /// <reference path="../src/plugin-content-pages.d.ts" />
8
+ import type { LoadContext } from '@docusaurus/types';
9
+ import type { PagesContentPaths } from './types';
10
+ import type { PluginOptions, LoadedContent } from '@docusaurus/plugin-content-pages';
11
+ export declare function createPagesContentPaths({ context, options, }: {
12
+ context: LoadContext;
13
+ options: PluginOptions;
14
+ }): PagesContentPaths;
15
+ export declare function getContentPathList(contentPaths: PagesContentPaths): string[];
16
+ type LoadContentParams = {
17
+ context: LoadContext;
18
+ options: PluginOptions;
19
+ contentPaths: PagesContentPaths;
20
+ };
21
+ export declare function loadPagesContent(params: LoadContentParams): Promise<LoadedContent>;
22
+ export {};
package/lib/content.js ADDED
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.loadPagesContent = exports.getContentPathList = exports.createPagesContentPaths = void 0;
10
+ const tslib_1 = require("tslib");
11
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
+ const path_1 = tslib_1.__importDefault(require("path"));
13
+ const utils_1 = require("@docusaurus/utils");
14
+ const frontMatter_1 = require("./frontMatter");
15
+ function createPagesContentPaths({ context, options, }) {
16
+ const { siteDir, localizationDir } = context;
17
+ return {
18
+ contentPath: path_1.default.resolve(siteDir, options.path),
19
+ contentPathLocalized: (0, utils_1.getPluginI18nPath)({
20
+ localizationDir,
21
+ pluginName: 'docusaurus-plugin-content-pages',
22
+ pluginId: options.id,
23
+ }),
24
+ };
25
+ }
26
+ exports.createPagesContentPaths = createPagesContentPaths;
27
+ function getContentPathList(contentPaths) {
28
+ return [contentPaths.contentPathLocalized, contentPaths.contentPath];
29
+ }
30
+ exports.getContentPathList = getContentPathList;
31
+ const isMarkdownSource = (source) => source.endsWith('.md') || source.endsWith('.mdx');
32
+ async function loadPagesContent(params) {
33
+ const { options } = params;
34
+ const pagesFiles = await (0, utils_1.Globby)(params.options.include, {
35
+ cwd: params.contentPaths.contentPath,
36
+ ignore: options.exclude,
37
+ });
38
+ async function doProcessPageSourceFile(relativeSource) {
39
+ try {
40
+ return await processPageSourceFile(relativeSource, params);
41
+ }
42
+ catch (err) {
43
+ throw new Error(`Processing of page source file path=${relativeSource} failed.`, { cause: err });
44
+ }
45
+ }
46
+ return (await Promise.all(pagesFiles.map(doProcessPageSourceFile))).filter((res) => {
47
+ return res !== undefined;
48
+ });
49
+ }
50
+ exports.loadPagesContent = loadPagesContent;
51
+ async function processPageSourceFile(relativeSource, params) {
52
+ const { context, options, contentPaths } = params;
53
+ const { siteConfig, baseUrl, siteDir, i18n } = context;
54
+ const { editUrl } = options;
55
+ // Lookup in localized folder in priority
56
+ const contentPath = await (0, utils_1.getFolderContainingFile)(getContentPathList(contentPaths), relativeSource);
57
+ const source = path_1.default.join(contentPath, relativeSource);
58
+ const aliasedSourcePath = (0, utils_1.aliasedSitePath)(source, siteDir);
59
+ const permalink = (0, utils_1.normalizeUrl)([
60
+ baseUrl,
61
+ options.routeBasePath,
62
+ (0, utils_1.encodePath)((0, utils_1.fileToPath)(relativeSource)),
63
+ ]);
64
+ if (!isMarkdownSource(relativeSource)) {
65
+ return {
66
+ type: 'jsx',
67
+ permalink,
68
+ source: aliasedSourcePath,
69
+ };
70
+ }
71
+ const content = await fs_extra_1.default.readFile(source, 'utf-8');
72
+ const { frontMatter: unsafeFrontMatter, contentTitle, excerpt, } = await (0, utils_1.parseMarkdownFile)({
73
+ filePath: source,
74
+ fileContent: content,
75
+ parseFrontMatter: siteConfig.markdown.parseFrontMatter,
76
+ });
77
+ const frontMatter = (0, frontMatter_1.validatePageFrontMatter)(unsafeFrontMatter);
78
+ const pagesDirPath = await (0, utils_1.getFolderContainingFile)(getContentPathList(contentPaths), relativeSource);
79
+ const pagesSourceAbsolute = path_1.default.join(pagesDirPath, relativeSource);
80
+ function getPagesEditUrl() {
81
+ const pagesPathRelative = path_1.default.relative(pagesDirPath, path_1.default.resolve(pagesSourceAbsolute));
82
+ if (typeof editUrl === 'function') {
83
+ return editUrl({
84
+ pagesDirPath: (0, utils_1.posixPath)(path_1.default.relative(siteDir, pagesDirPath)),
85
+ pagesPath: (0, utils_1.posixPath)(pagesPathRelative),
86
+ permalink,
87
+ locale: i18n.currentLocale,
88
+ });
89
+ }
90
+ else if (typeof editUrl === 'string') {
91
+ const isLocalized = pagesDirPath === contentPaths.contentPathLocalized;
92
+ const fileContentPath = isLocalized && options.editLocalizedFiles
93
+ ? contentPaths.contentPathLocalized
94
+ : contentPaths.contentPath;
95
+ const contentPathEditUrl = (0, utils_1.normalizeUrl)([
96
+ editUrl,
97
+ (0, utils_1.posixPath)(path_1.default.relative(siteDir, fileContentPath)),
98
+ ]);
99
+ return (0, utils_1.getEditUrl)(pagesPathRelative, contentPathEditUrl);
100
+ }
101
+ return undefined;
102
+ }
103
+ const lastUpdatedData = await (0, utils_1.readLastUpdateData)(source, options, frontMatter.last_update);
104
+ if ((0, utils_1.isDraft)({ frontMatter })) {
105
+ return undefined;
106
+ }
107
+ const unlisted = (0, utils_1.isUnlisted)({ frontMatter });
108
+ return {
109
+ type: 'mdx',
110
+ permalink,
111
+ source: aliasedSourcePath,
112
+ title: frontMatter.title ?? contentTitle,
113
+ description: frontMatter.description ?? excerpt,
114
+ frontMatter,
115
+ lastUpdatedBy: lastUpdatedData.lastUpdatedBy,
116
+ lastUpdatedAt: lastUpdatedData.lastUpdatedAt,
117
+ editUrl: getPagesEditUrl(),
118
+ unlisted,
119
+ };
120
+ }
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-pages.d.ts" />
7
8
  import type { PageFrontMatter } from '@docusaurus/plugin-content-pages';
8
9
  export declare function validatePageFrontMatter(frontMatter: {
9
10
  [key: string]: unknown;
@@ -18,6 +18,7 @@ const PageFrontMatterSchema = utils_validation_1.Joi.object({
18
18
  wrapperClassName: utils_validation_1.Joi.string(),
19
19
  hide_table_of_contents: utils_validation_1.Joi.boolean(),
20
20
  ...utils_validation_1.FrontMatterTOCHeadingLevels,
21
+ last_update: utils_validation_1.FrontMatterLastUpdateSchema,
21
22
  }).concat(utils_validation_1.ContentVisibilitySchema);
22
23
  function validatePageFrontMatter(frontMatter) {
23
24
  return (0, utils_validation_1.validateFrontMatter)(frontMatter, PageFrontMatterSchema);
package/lib/index.d.ts CHANGED
@@ -4,9 +4,8 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-pages.d.ts" />
7
8
  import type { LoadContext, Plugin } from '@docusaurus/types';
8
- import type { PagesContentPaths } from './types';
9
9
  import type { PluginOptions, LoadedContent } from '@docusaurus/plugin-content-pages';
10
- export declare function getContentPathList(contentPaths: PagesContentPaths): string[];
11
10
  export default function pluginContentPages(context: LoadContext, options: PluginOptions): Plugin<LoadedContent | null>;
12
11
  export { validateOptions } from './options';
package/lib/index.js CHANGED
@@ -6,147 +6,40 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.validateOptions = exports.getContentPathList = void 0;
9
+ exports.validateOptions = void 0;
10
10
  const tslib_1 = require("tslib");
11
11
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
12
  const path_1 = tslib_1.__importDefault(require("path"));
13
13
  const utils_1 = require("@docusaurus/utils");
14
- const frontMatter_1 = require("./frontMatter");
15
- function getContentPathList(contentPaths) {
16
- return [contentPaths.contentPathLocalized, contentPaths.contentPath];
17
- }
18
- exports.getContentPathList = getContentPathList;
19
- const isMarkdownSource = (source) => source.endsWith('.md') || source.endsWith('.mdx');
14
+ const routes_1 = require("./routes");
15
+ const content_1 = require("./content");
20
16
  function pluginContentPages(context, options) {
21
- const { siteConfig, siteDir, generatedFilesDir, localizationDir } = context;
22
- const contentPaths = {
23
- contentPath: path_1.default.resolve(siteDir, options.path),
24
- contentPathLocalized: (0, utils_1.getPluginI18nPath)({
25
- localizationDir,
26
- pluginName: 'docusaurus-plugin-content-pages',
27
- pluginId: options.id,
28
- }),
29
- };
17
+ const { siteConfig, siteDir, generatedFilesDir } = context;
18
+ const contentPaths = (0, content_1.createPagesContentPaths)({ context, options });
30
19
  const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-pages');
31
20
  const dataDir = path_1.default.join(pluginDataDirRoot, options.id ?? utils_1.DEFAULT_PLUGIN_ID);
32
21
  return {
33
22
  name: 'docusaurus-plugin-content-pages',
34
23
  getPathsToWatch() {
35
24
  const { include } = options;
36
- return getContentPathList(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`));
25
+ return (0, content_1.getContentPathList)(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`));
37
26
  },
38
27
  async loadContent() {
39
- const { include } = options;
40
28
  if (!(await fs_extra_1.default.pathExists(contentPaths.contentPath))) {
41
29
  return null;
42
30
  }
43
- const { baseUrl } = siteConfig;
44
- const pagesFiles = await (0, utils_1.Globby)(include, {
45
- cwd: contentPaths.contentPath,
46
- ignore: options.exclude,
47
- });
48
- async function processPageSourceFile(relativeSource) {
49
- // Lookup in localized folder in priority
50
- const contentPath = await (0, utils_1.getFolderContainingFile)(getContentPathList(contentPaths), relativeSource);
51
- const source = path_1.default.join(contentPath, relativeSource);
52
- const aliasedSourcePath = (0, utils_1.aliasedSitePath)(source, siteDir);
53
- const permalink = (0, utils_1.normalizeUrl)([
54
- baseUrl,
55
- options.routeBasePath,
56
- (0, utils_1.encodePath)((0, utils_1.fileToPath)(relativeSource)),
57
- ]);
58
- if (!isMarkdownSource(relativeSource)) {
59
- return {
60
- type: 'jsx',
61
- permalink,
62
- source: aliasedSourcePath,
63
- };
64
- }
65
- const content = await fs_extra_1.default.readFile(source, 'utf-8');
66
- const { frontMatter: unsafeFrontMatter, contentTitle, excerpt, } = await (0, utils_1.parseMarkdownFile)({
67
- filePath: source,
68
- fileContent: content,
69
- parseFrontMatter: siteConfig.markdown.parseFrontMatter,
70
- });
71
- const frontMatter = (0, frontMatter_1.validatePageFrontMatter)(unsafeFrontMatter);
72
- if ((0, utils_1.isDraft)({ frontMatter })) {
73
- return undefined;
74
- }
75
- const unlisted = (0, utils_1.isUnlisted)({ frontMatter });
76
- return {
77
- type: 'mdx',
78
- permalink,
79
- source: aliasedSourcePath,
80
- title: frontMatter.title ?? contentTitle,
81
- description: frontMatter.description ?? excerpt,
82
- frontMatter,
83
- unlisted,
84
- };
85
- }
86
- async function doProcessPageSourceFile(relativeSource) {
87
- try {
88
- return await processPageSourceFile(relativeSource);
89
- }
90
- catch (err) {
91
- throw new Error(`Processing of page source file path=${relativeSource} failed.`, { cause: err });
92
- }
93
- }
94
- return (await Promise.all(pagesFiles.map(doProcessPageSourceFile))).filter(Boolean);
31
+ return (0, content_1.loadPagesContent)({ context, options, contentPaths });
95
32
  },
96
33
  async contentLoaded({ content, actions }) {
97
34
  if (!content) {
98
35
  return;
99
36
  }
100
- const { addRoute, createData } = actions;
101
- function createPageRouteMetadata(metadata) {
102
- return {
103
- sourceFilePath: (0, utils_1.aliasedSitePathToRelativePath)(metadata.source),
104
- // TODO add support for last updated date in the page plugin
105
- // at least for Markdown files
106
- // lastUpdatedAt: metadata.lastUpdatedAt,
107
- lastUpdatedAt: undefined,
108
- };
109
- }
110
- await Promise.all(content.map(async (metadata) => {
111
- const { permalink, source } = metadata;
112
- const routeMetadata = createPageRouteMetadata(metadata);
113
- if (metadata.type === 'mdx') {
114
- await createData(
115
- // Note that this created data path must be in sync with
116
- // metadataPath provided to mdx-loader.
117
- `${(0, utils_1.docuHash)(metadata.source)}.json`, JSON.stringify(metadata, null, 2));
118
- addRoute({
119
- path: permalink,
120
- component: options.mdxPageComponent,
121
- exact: true,
122
- metadata: routeMetadata,
123
- modules: {
124
- content: source,
125
- },
126
- });
127
- }
128
- else {
129
- addRoute({
130
- path: permalink,
131
- component: source,
132
- exact: true,
133
- metadata: routeMetadata,
134
- modules: {
135
- config: `@generated/docusaurus.config`,
136
- },
137
- });
138
- }
139
- }));
37
+ await (0, routes_1.createAllRoutes)({ content, options, actions });
140
38
  },
141
39
  configureWebpack() {
142
40
  const { admonitions, rehypePlugins, remarkPlugins, beforeDefaultRehypePlugins, beforeDefaultRemarkPlugins, } = options;
143
- const contentDirs = getContentPathList(contentPaths);
41
+ const contentDirs = (0, content_1.getContentPathList)(contentPaths);
144
42
  return {
145
- resolve: {
146
- alias: {
147
- '~pages': pluginDataDirRoot,
148
- },
149
- },
150
43
  module: {
151
44
  rules: [
152
45
  {
package/lib/options.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
+ /// <reference path="../src/plugin-content-pages.d.ts" />
7
8
  import type { OptionValidationContext } from '@docusaurus/types';
8
9
  import type { PluginOptions, Options } from '@docusaurus/plugin-content-pages';
9
10
  export declare const DEFAULT_OPTIONS: PluginOptions;
package/lib/options.js CHANGED
@@ -10,9 +10,9 @@ exports.validateOptions = exports.DEFAULT_OPTIONS = void 0;
10
10
  const utils_validation_1 = require("@docusaurus/utils-validation");
11
11
  const utils_1 = require("@docusaurus/utils");
12
12
  exports.DEFAULT_OPTIONS = {
13
- path: 'src/pages',
14
- routeBasePath: '/',
15
- include: ['**/*.{js,jsx,ts,tsx,md,mdx}'],
13
+ path: 'src/pages', // Path to data on filesystem, relative to site dir.
14
+ routeBasePath: '/', // URL Route.
15
+ include: ['**/*.{js,jsx,ts,tsx,md,mdx}'], // Extensions to include.
16
16
  exclude: utils_1.GlobExcludeDefault,
17
17
  mdxPageComponent: '@theme/MDXPage',
18
18
  remarkPlugins: [],
@@ -20,6 +20,9 @@ exports.DEFAULT_OPTIONS = {
20
20
  beforeDefaultRehypePlugins: [],
21
21
  beforeDefaultRemarkPlugins: [],
22
22
  admonitions: true,
23
+ showLastUpdateTime: false,
24
+ showLastUpdateAuthor: false,
25
+ editLocalizedFiles: false,
23
26
  };
24
27
  const PluginOptionSchema = utils_validation_1.Joi.object({
25
28
  path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
@@ -32,6 +35,10 @@ const PluginOptionSchema = utils_validation_1.Joi.object({
32
35
  beforeDefaultRehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
33
36
  beforeDefaultRemarkPlugins: utils_validation_1.RemarkPluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRemarkPlugins),
34
37
  admonitions: utils_validation_1.AdmonitionsSchema.default(exports.DEFAULT_OPTIONS.admonitions),
38
+ showLastUpdateTime: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showLastUpdateTime),
39
+ showLastUpdateAuthor: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showLastUpdateAuthor),
40
+ editUrl: utils_validation_1.Joi.alternatives().try(utils_validation_1.URISchema, utils_validation_1.Joi.function()),
41
+ editLocalizedFiles: utils_validation_1.Joi.boolean().default(exports.DEFAULT_OPTIONS.editLocalizedFiles),
35
42
  });
36
43
  function validateOptions({ validate, options, }) {
37
44
  const validatedOptions = validate(PluginOptionSchema, options);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ /// <reference path="../src/plugin-content-pages.d.ts" />
8
+ import type { PluginContentLoadedActions, RouteConfig } from '@docusaurus/types';
9
+ import type { PluginOptions, LoadedContent } from '@docusaurus/plugin-content-pages';
10
+ type CreateAllRoutesParam = {
11
+ content: LoadedContent;
12
+ options: PluginOptions;
13
+ actions: PluginContentLoadedActions;
14
+ };
15
+ export declare function createAllRoutes(param: CreateAllRoutesParam): Promise<void>;
16
+ export declare function buildAllRoutes({ content, actions, options, }: CreateAllRoutesParam): Promise<RouteConfig[]>;
17
+ export {};
package/lib/routes.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.buildAllRoutes = exports.createAllRoutes = void 0;
10
+ const utils_1 = require("@docusaurus/utils");
11
+ function createPageRouteMetadata(metadata) {
12
+ const lastUpdatedAt = metadata.type === 'mdx' ? metadata.lastUpdatedAt : undefined;
13
+ return {
14
+ sourceFilePath: (0, utils_1.aliasedSitePathToRelativePath)(metadata.source),
15
+ lastUpdatedAt,
16
+ };
17
+ }
18
+ async function createAllRoutes(param) {
19
+ const routes = await buildAllRoutes(param);
20
+ routes.forEach(param.actions.addRoute);
21
+ }
22
+ exports.createAllRoutes = createAllRoutes;
23
+ async function buildAllRoutes({ content, actions, options, }) {
24
+ const { createData } = actions;
25
+ async function buildMDXPageRoute(metadata) {
26
+ await createData(
27
+ // Note that this created data path must be in sync with
28
+ // metadataPath provided to mdx-loader.
29
+ `${(0, utils_1.docuHash)(metadata.source)}.json`, metadata);
30
+ return {
31
+ path: metadata.permalink,
32
+ component: options.mdxPageComponent,
33
+ exact: true,
34
+ metadata: createPageRouteMetadata(metadata),
35
+ modules: {
36
+ content: metadata.source,
37
+ },
38
+ };
39
+ }
40
+ async function buildJSXRoute(metadata) {
41
+ return {
42
+ path: metadata.permalink,
43
+ component: metadata.source,
44
+ exact: true,
45
+ metadata: createPageRouteMetadata(metadata),
46
+ modules: {
47
+ config: `@generated/docusaurus.config`,
48
+ },
49
+ };
50
+ }
51
+ async function buildPageRoute(metadata) {
52
+ return metadata.type === 'mdx'
53
+ ? buildMDXPageRoute(metadata)
54
+ : buildJSXRoute(metadata);
55
+ }
56
+ return Promise.all(content.map(buildPageRoute));
57
+ }
58
+ exports.buildAllRoutes = buildAllRoutes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-pages",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "Pages plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-pages.d.ts",
@@ -18,11 +18,11 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "@docusaurus/core": "3.2.1",
22
- "@docusaurus/mdx-loader": "3.2.1",
23
- "@docusaurus/types": "3.2.1",
24
- "@docusaurus/utils": "3.2.1",
25
- "@docusaurus/utils-validation": "3.2.1",
21
+ "@docusaurus/core": "3.3.0",
22
+ "@docusaurus/mdx-loader": "3.3.0",
23
+ "@docusaurus/types": "3.3.0",
24
+ "@docusaurus/utils": "3.3.0",
25
+ "@docusaurus/utils-validation": "3.3.0",
26
26
  "fs-extra": "^11.1.1",
27
27
  "tslib": "^2.6.0",
28
28
  "webpack": "^5.88.1"
@@ -34,5 +34,5 @@
34
34
  "engines": {
35
35
  "node": ">=18.0"
36
36
  },
37
- "gitHead": "f268e15264e208e6faf26117258162e988b53773"
37
+ "gitHead": "2ec4e078b5ca0c57f2cc04f2fe564d524bb5e858"
38
38
  }
package/src/content.ts ADDED
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import fs from 'fs-extra';
9
+ import path from 'path';
10
+ import {
11
+ encodePath,
12
+ fileToPath,
13
+ aliasedSitePath,
14
+ getFolderContainingFile,
15
+ Globby,
16
+ normalizeUrl,
17
+ parseMarkdownFile,
18
+ isUnlisted,
19
+ isDraft,
20
+ readLastUpdateData,
21
+ getEditUrl,
22
+ posixPath,
23
+ getPluginI18nPath,
24
+ } from '@docusaurus/utils';
25
+ import {validatePageFrontMatter} from './frontMatter';
26
+ import type {LoadContext} from '@docusaurus/types';
27
+ import type {PagesContentPaths} from './types';
28
+ import type {
29
+ PluginOptions,
30
+ Metadata,
31
+ LoadedContent,
32
+ } from '@docusaurus/plugin-content-pages';
33
+
34
+ export function createPagesContentPaths({
35
+ context,
36
+ options,
37
+ }: {
38
+ context: LoadContext;
39
+ options: PluginOptions;
40
+ }): PagesContentPaths {
41
+ const {siteDir, localizationDir} = context;
42
+ return {
43
+ contentPath: path.resolve(siteDir, options.path),
44
+ contentPathLocalized: getPluginI18nPath({
45
+ localizationDir,
46
+ pluginName: 'docusaurus-plugin-content-pages',
47
+ pluginId: options.id,
48
+ }),
49
+ };
50
+ }
51
+
52
+ export function getContentPathList(contentPaths: PagesContentPaths): string[] {
53
+ return [contentPaths.contentPathLocalized, contentPaths.contentPath];
54
+ }
55
+
56
+ const isMarkdownSource = (source: string) =>
57
+ source.endsWith('.md') || source.endsWith('.mdx');
58
+
59
+ type LoadContentParams = {
60
+ context: LoadContext;
61
+ options: PluginOptions;
62
+ contentPaths: PagesContentPaths;
63
+ };
64
+
65
+ export async function loadPagesContent(
66
+ params: LoadContentParams,
67
+ ): Promise<LoadedContent> {
68
+ const {options} = params;
69
+
70
+ const pagesFiles = await Globby(params.options.include, {
71
+ cwd: params.contentPaths.contentPath,
72
+ ignore: options.exclude,
73
+ });
74
+
75
+ async function doProcessPageSourceFile(relativeSource: string) {
76
+ try {
77
+ return await processPageSourceFile(relativeSource, params);
78
+ } catch (err) {
79
+ throw new Error(
80
+ `Processing of page source file path=${relativeSource} failed.`,
81
+ {cause: err as Error},
82
+ );
83
+ }
84
+ }
85
+
86
+ return (await Promise.all(pagesFiles.map(doProcessPageSourceFile))).filter(
87
+ (res): res is Metadata => {
88
+ return res !== undefined;
89
+ },
90
+ );
91
+ }
92
+
93
+ async function processPageSourceFile(
94
+ relativeSource: string,
95
+ params: LoadContentParams,
96
+ ): Promise<Metadata | undefined> {
97
+ const {context, options, contentPaths} = params;
98
+ const {siteConfig, baseUrl, siteDir, i18n} = context;
99
+ const {editUrl} = options;
100
+
101
+ // Lookup in localized folder in priority
102
+ const contentPath = await getFolderContainingFile(
103
+ getContentPathList(contentPaths),
104
+ relativeSource,
105
+ );
106
+
107
+ const source = path.join(contentPath, relativeSource);
108
+ const aliasedSourcePath = aliasedSitePath(source, siteDir);
109
+ const permalink = normalizeUrl([
110
+ baseUrl,
111
+ options.routeBasePath,
112
+ encodePath(fileToPath(relativeSource)),
113
+ ]);
114
+ if (!isMarkdownSource(relativeSource)) {
115
+ return {
116
+ type: 'jsx',
117
+ permalink,
118
+ source: aliasedSourcePath,
119
+ };
120
+ }
121
+
122
+ const content = await fs.readFile(source, 'utf-8');
123
+ const {
124
+ frontMatter: unsafeFrontMatter,
125
+ contentTitle,
126
+ excerpt,
127
+ } = await parseMarkdownFile({
128
+ filePath: source,
129
+ fileContent: content,
130
+ parseFrontMatter: siteConfig.markdown.parseFrontMatter,
131
+ });
132
+ const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
133
+
134
+ const pagesDirPath = await getFolderContainingFile(
135
+ getContentPathList(contentPaths),
136
+ relativeSource,
137
+ );
138
+
139
+ const pagesSourceAbsolute = path.join(pagesDirPath, relativeSource);
140
+
141
+ function getPagesEditUrl() {
142
+ const pagesPathRelative = path.relative(
143
+ pagesDirPath,
144
+ path.resolve(pagesSourceAbsolute),
145
+ );
146
+
147
+ if (typeof editUrl === 'function') {
148
+ return editUrl({
149
+ pagesDirPath: posixPath(path.relative(siteDir, pagesDirPath)),
150
+ pagesPath: posixPath(pagesPathRelative),
151
+ permalink,
152
+ locale: i18n.currentLocale,
153
+ });
154
+ } else if (typeof editUrl === 'string') {
155
+ const isLocalized = pagesDirPath === contentPaths.contentPathLocalized;
156
+ const fileContentPath =
157
+ isLocalized && options.editLocalizedFiles
158
+ ? contentPaths.contentPathLocalized
159
+ : contentPaths.contentPath;
160
+
161
+ const contentPathEditUrl = normalizeUrl([
162
+ editUrl,
163
+ posixPath(path.relative(siteDir, fileContentPath)),
164
+ ]);
165
+
166
+ return getEditUrl(pagesPathRelative, contentPathEditUrl);
167
+ }
168
+ return undefined;
169
+ }
170
+
171
+ const lastUpdatedData = await readLastUpdateData(
172
+ source,
173
+ options,
174
+ frontMatter.last_update,
175
+ );
176
+
177
+ if (isDraft({frontMatter})) {
178
+ return undefined;
179
+ }
180
+ const unlisted = isUnlisted({frontMatter});
181
+
182
+ return {
183
+ type: 'mdx',
184
+ permalink,
185
+ source: aliasedSourcePath,
186
+ title: frontMatter.title ?? contentTitle,
187
+ description: frontMatter.description ?? excerpt,
188
+ frontMatter,
189
+ lastUpdatedBy: lastUpdatedData.lastUpdatedBy,
190
+ lastUpdatedAt: lastUpdatedData.lastUpdatedAt,
191
+ editUrl: getPagesEditUrl(),
192
+ unlisted,
193
+ };
194
+ }
@@ -11,6 +11,7 @@ import {
11
11
  FrontMatterTOCHeadingLevels,
12
12
  ContentVisibilitySchema,
13
13
  URISchema,
14
+ FrontMatterLastUpdateSchema,
14
15
  } from '@docusaurus/utils-validation';
15
16
  import type {PageFrontMatter} from '@docusaurus/plugin-content-pages';
16
17
 
@@ -24,6 +25,7 @@ const PageFrontMatterSchema = Joi.object<PageFrontMatter>({
24
25
  wrapperClassName: Joi.string(),
25
26
  hide_table_of_contents: Joi.boolean(),
26
27
  ...FrontMatterTOCHeadingLevels,
28
+ last_update: FrontMatterLastUpdateSchema,
27
29
  }).concat(ContentVisibilitySchema);
28
30
 
29
31
  export function validatePageFrontMatter(frontMatter: {
package/src/index.ts CHANGED
@@ -8,53 +8,32 @@
8
8
  import fs from 'fs-extra';
9
9
  import path from 'path';
10
10
  import {
11
- encodePath,
12
- fileToPath,
13
11
  aliasedSitePath,
14
- aliasedSitePathToRelativePath,
15
12
  docuHash,
16
- getPluginI18nPath,
17
- getFolderContainingFile,
18
13
  addTrailingPathSeparator,
19
- Globby,
20
14
  createAbsoluteFilePathMatcher,
21
- normalizeUrl,
22
15
  DEFAULT_PLUGIN_ID,
23
- parseMarkdownFile,
24
- isUnlisted,
25
- isDraft,
26
16
  } from '@docusaurus/utils';
27
- import {validatePageFrontMatter} from './frontMatter';
28
- import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types';
29
- import type {PagesContentPaths} from './types';
17
+ import {createAllRoutes} from './routes';
18
+ import {
19
+ createPagesContentPaths,
20
+ getContentPathList,
21
+ loadPagesContent,
22
+ } from './content';
23
+ import type {LoadContext, Plugin} from '@docusaurus/types';
30
24
  import type {
31
25
  PluginOptions,
32
- Metadata,
33
26
  LoadedContent,
34
27
  PageFrontMatter,
35
28
  } from '@docusaurus/plugin-content-pages';
36
29
 
37
- export function getContentPathList(contentPaths: PagesContentPaths): string[] {
38
- return [contentPaths.contentPathLocalized, contentPaths.contentPath];
39
- }
40
-
41
- const isMarkdownSource = (source: string) =>
42
- source.endsWith('.md') || source.endsWith('.mdx');
43
-
44
30
  export default function pluginContentPages(
45
31
  context: LoadContext,
46
32
  options: PluginOptions,
47
33
  ): Plugin<LoadedContent | null> {
48
- const {siteConfig, siteDir, generatedFilesDir, localizationDir} = context;
34
+ const {siteConfig, siteDir, generatedFilesDir} = context;
49
35
 
50
- const contentPaths: PagesContentPaths = {
51
- contentPath: path.resolve(siteDir, options.path),
52
- contentPathLocalized: getPluginI18nPath({
53
- localizationDir,
54
- pluginName: 'docusaurus-plugin-content-pages',
55
- pluginId: options.id,
56
- }),
57
- };
36
+ const contentPaths = createPagesContentPaths({context, options});
58
37
 
59
38
  const pluginDataDirRoot = path.join(
60
39
  generatedFilesDir,
@@ -73,135 +52,17 @@ export default function pluginContentPages(
73
52
  },
74
53
 
75
54
  async loadContent() {
76
- const {include} = options;
77
-
78
55
  if (!(await fs.pathExists(contentPaths.contentPath))) {
79
56
  return null;
80
57
  }
81
-
82
- const {baseUrl} = siteConfig;
83
- const pagesFiles = await Globby(include, {
84
- cwd: contentPaths.contentPath,
85
- ignore: options.exclude,
86
- });
87
-
88
- async function processPageSourceFile(
89
- relativeSource: string,
90
- ): Promise<Metadata | undefined> {
91
- // Lookup in localized folder in priority
92
- const contentPath = await getFolderContainingFile(
93
- getContentPathList(contentPaths),
94
- relativeSource,
95
- );
96
-
97
- const source = path.join(contentPath, relativeSource);
98
- const aliasedSourcePath = aliasedSitePath(source, siteDir);
99
- const permalink = normalizeUrl([
100
- baseUrl,
101
- options.routeBasePath,
102
- encodePath(fileToPath(relativeSource)),
103
- ]);
104
- if (!isMarkdownSource(relativeSource)) {
105
- return {
106
- type: 'jsx',
107
- permalink,
108
- source: aliasedSourcePath,
109
- };
110
- }
111
- const content = await fs.readFile(source, 'utf-8');
112
- const {
113
- frontMatter: unsafeFrontMatter,
114
- contentTitle,
115
- excerpt,
116
- } = await parseMarkdownFile({
117
- filePath: source,
118
- fileContent: content,
119
- parseFrontMatter: siteConfig.markdown.parseFrontMatter,
120
- });
121
- const frontMatter = validatePageFrontMatter(unsafeFrontMatter);
122
-
123
- if (isDraft({frontMatter})) {
124
- return undefined;
125
- }
126
- const unlisted = isUnlisted({frontMatter});
127
-
128
- return {
129
- type: 'mdx',
130
- permalink,
131
- source: aliasedSourcePath,
132
- title: frontMatter.title ?? contentTitle,
133
- description: frontMatter.description ?? excerpt,
134
- frontMatter,
135
- unlisted,
136
- };
137
- }
138
-
139
- async function doProcessPageSourceFile(relativeSource: string) {
140
- try {
141
- return await processPageSourceFile(relativeSource);
142
- } catch (err) {
143
- throw new Error(
144
- `Processing of page source file path=${relativeSource} failed.`,
145
- {cause: err as Error},
146
- );
147
- }
148
- }
149
-
150
- return (
151
- await Promise.all(pagesFiles.map(doProcessPageSourceFile))
152
- ).filter(Boolean) as Metadata[];
58
+ return loadPagesContent({context, options, contentPaths});
153
59
  },
154
60
 
155
61
  async contentLoaded({content, actions}) {
156
62
  if (!content) {
157
63
  return;
158
64
  }
159
-
160
- const {addRoute, createData} = actions;
161
-
162
- function createPageRouteMetadata(metadata: Metadata): RouteMetadata {
163
- return {
164
- sourceFilePath: aliasedSitePathToRelativePath(metadata.source),
165
- // TODO add support for last updated date in the page plugin
166
- // at least for Markdown files
167
- // lastUpdatedAt: metadata.lastUpdatedAt,
168
- lastUpdatedAt: undefined,
169
- };
170
- }
171
-
172
- await Promise.all(
173
- content.map(async (metadata) => {
174
- const {permalink, source} = metadata;
175
- const routeMetadata = createPageRouteMetadata(metadata);
176
- if (metadata.type === 'mdx') {
177
- await createData(
178
- // Note that this created data path must be in sync with
179
- // metadataPath provided to mdx-loader.
180
- `${docuHash(metadata.source)}.json`,
181
- JSON.stringify(metadata, null, 2),
182
- );
183
- addRoute({
184
- path: permalink,
185
- component: options.mdxPageComponent,
186
- exact: true,
187
- metadata: routeMetadata,
188
- modules: {
189
- content: source,
190
- },
191
- });
192
- } else {
193
- addRoute({
194
- path: permalink,
195
- component: source,
196
- exact: true,
197
- metadata: routeMetadata,
198
- modules: {
199
- config: `@generated/docusaurus.config`,
200
- },
201
- });
202
- }
203
- }),
204
- );
65
+ await createAllRoutes({content, options, actions});
205
66
  },
206
67
 
207
68
  configureWebpack() {
@@ -214,11 +75,6 @@ export default function pluginContentPages(
214
75
  } = options;
215
76
  const contentDirs = getContentPathList(contentPaths);
216
77
  return {
217
- resolve: {
218
- alias: {
219
- '~pages': pluginDataDirRoot,
220
- },
221
- },
222
78
  module: {
223
79
  rules: [
224
80
  {
package/src/options.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  RehypePluginsSchema,
12
12
  AdmonitionsSchema,
13
13
  RouteBasePathSchema,
14
+ URISchema,
14
15
  } from '@docusaurus/utils-validation';
15
16
  import {GlobExcludeDefault} from '@docusaurus/utils';
16
17
  import type {OptionValidationContext} from '@docusaurus/types';
@@ -27,6 +28,9 @@ export const DEFAULT_OPTIONS: PluginOptions = {
27
28
  beforeDefaultRehypePlugins: [],
28
29
  beforeDefaultRemarkPlugins: [],
29
30
  admonitions: true,
31
+ showLastUpdateTime: false,
32
+ showLastUpdateAuthor: false,
33
+ editLocalizedFiles: false,
30
34
  };
31
35
 
32
36
  const PluginOptionSchema = Joi.object<PluginOptions>({
@@ -44,6 +48,12 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
44
48
  DEFAULT_OPTIONS.beforeDefaultRemarkPlugins,
45
49
  ),
46
50
  admonitions: AdmonitionsSchema.default(DEFAULT_OPTIONS.admonitions),
51
+ showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime),
52
+ showLastUpdateAuthor: Joi.bool().default(
53
+ DEFAULT_OPTIONS.showLastUpdateAuthor,
54
+ ),
55
+ editUrl: Joi.alternatives().try(URISchema, Joi.function()),
56
+ editLocalizedFiles: Joi.boolean().default(DEFAULT_OPTIONS.editLocalizedFiles),
47
57
  });
48
58
 
49
59
  export function validateOptions({
@@ -8,6 +8,7 @@
8
8
  declare module '@docusaurus/plugin-content-pages' {
9
9
  import type {MDXOptions} from '@docusaurus/mdx-loader';
10
10
  import type {LoadContext, Plugin} from '@docusaurus/types';
11
+ import type {FrontMatterLastUpdate, LastUpdateData} from '@docusaurus/utils';
11
12
 
12
13
  export type Assets = {
13
14
  image?: string;
@@ -20,6 +21,10 @@ declare module '@docusaurus/plugin-content-pages' {
20
21
  include: string[];
21
22
  exclude: string[];
22
23
  mdxPageComponent: string;
24
+ showLastUpdateTime: boolean;
25
+ showLastUpdateAuthor: boolean;
26
+ editUrl?: string | EditUrlFunction;
27
+ editLocalizedFiles?: boolean;
23
28
  };
24
29
 
25
30
  export type Options = Partial<PluginOptions>;
@@ -35,6 +40,7 @@ declare module '@docusaurus/plugin-content-pages' {
35
40
  readonly toc_max_heading_level?: number;
36
41
  readonly draft?: boolean;
37
42
  readonly unlisted?: boolean;
43
+ readonly last_update?: FrontMatterLastUpdate;
38
44
  };
39
45
 
40
46
  export type JSXPageMetadata = {
@@ -43,16 +49,31 @@ declare module '@docusaurus/plugin-content-pages' {
43
49
  source: string;
44
50
  };
45
51
 
46
- export type MDXPageMetadata = {
52
+ export type MDXPageMetadata = LastUpdateData & {
47
53
  type: 'mdx';
48
54
  permalink: string;
49
55
  source: string;
50
56
  frontMatter: PageFrontMatter & {[key: string]: unknown};
57
+ editUrl?: string;
51
58
  title?: string;
52
59
  description?: string;
53
60
  unlisted: boolean;
54
61
  };
55
62
 
63
+ export type EditUrlFunction = (editUrlParams: {
64
+ /**
65
+ * The root content directory containing this post file, relative to the
66
+ * site path. Usually the same as `options.path` but can be localized
67
+ */
68
+ pagesDirPath: string;
69
+ /** Path to this pages file, relative to `pagesDirPath`. */
70
+ pagesPath: string;
71
+ /** @see {@link PagesPostMetadata.permalink} */
72
+ permalink: string;
73
+ /** Locale name. */
74
+ locale: string;
75
+ }) => string | undefined;
76
+
56
77
  export type Metadata = JSXPageMetadata | MDXPageMetadata;
57
78
 
58
79
  export type LoadedContent = Metadata[];
package/src/routes.ts ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import {aliasedSitePathToRelativePath, docuHash} from '@docusaurus/utils';
9
+ import type {
10
+ PluginContentLoadedActions,
11
+ RouteConfig,
12
+ RouteMetadata,
13
+ } from '@docusaurus/types';
14
+ import type {
15
+ PluginOptions,
16
+ Metadata,
17
+ LoadedContent,
18
+ MDXPageMetadata,
19
+ } from '@docusaurus/plugin-content-pages';
20
+
21
+ type CreateAllRoutesParam = {
22
+ content: LoadedContent;
23
+ options: PluginOptions;
24
+ actions: PluginContentLoadedActions;
25
+ };
26
+
27
+ function createPageRouteMetadata(metadata: Metadata): RouteMetadata {
28
+ const lastUpdatedAt =
29
+ metadata.type === 'mdx' ? metadata.lastUpdatedAt : undefined;
30
+ return {
31
+ sourceFilePath: aliasedSitePathToRelativePath(metadata.source),
32
+ lastUpdatedAt,
33
+ };
34
+ }
35
+
36
+ export async function createAllRoutes(
37
+ param: CreateAllRoutesParam,
38
+ ): Promise<void> {
39
+ const routes = await buildAllRoutes(param);
40
+ routes.forEach(param.actions.addRoute);
41
+ }
42
+
43
+ export async function buildAllRoutes({
44
+ content,
45
+ actions,
46
+ options,
47
+ }: CreateAllRoutesParam): Promise<RouteConfig[]> {
48
+ const {createData} = actions;
49
+
50
+ async function buildMDXPageRoute(
51
+ metadata: MDXPageMetadata,
52
+ ): Promise<RouteConfig> {
53
+ await createData(
54
+ // Note that this created data path must be in sync with
55
+ // metadataPath provided to mdx-loader.
56
+ `${docuHash(metadata.source)}.json`,
57
+ metadata,
58
+ );
59
+ return {
60
+ path: metadata.permalink,
61
+ component: options.mdxPageComponent,
62
+ exact: true,
63
+ metadata: createPageRouteMetadata(metadata),
64
+ modules: {
65
+ content: metadata.source,
66
+ },
67
+ };
68
+ }
69
+
70
+ async function buildJSXRoute(metadata: Metadata): Promise<RouteConfig> {
71
+ return {
72
+ path: metadata.permalink,
73
+ component: metadata.source,
74
+ exact: true,
75
+ metadata: createPageRouteMetadata(metadata),
76
+ modules: {
77
+ config: `@generated/docusaurus.config`,
78
+ },
79
+ };
80
+ }
81
+
82
+ async function buildPageRoute(metadata: Metadata): Promise<RouteConfig> {
83
+ return metadata.type === 'mdx'
84
+ ? buildMDXPageRoute(metadata)
85
+ : buildJSXRoute(metadata);
86
+ }
87
+
88
+ return Promise.all(content.map(buildPageRoute));
89
+ }