diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js
index 8c6cc8356..b5ab0dafd 100644
--- a/src/core/fetch/index.js
+++ b/src/core/fetch/index.js
@@ -150,7 +150,7 @@ export function Fetch(Base) {
}
}
- _fetchCover() {
+ _fetchCover(cb = noop) {
const { coverpage, requestHeaders } = this.config;
const query = this.route.query;
const root = getParentPath(this.route.path);
@@ -170,17 +170,26 @@ export function Fetch(Base) {
}
const coverOnly = Boolean(path) && this.config.onlyCover;
+ const next = () => cb(coverOnly);
if (path) {
path = this.router.getFile(root + path);
this.coverIsHTML = /\.html$/g.test(path);
get(path + stringifyQuery(query, ['id']), false, requestHeaders).then(
- text => this._renderCover(text, coverOnly),
+ text => this._renderCover(text, coverOnly, next),
+ (event, response) => {
+ this.coverIsHTML = false;
+ this._renderCover(
+ `# ${response.status} - ${response.statusText}`,
+ coverOnly,
+ next,
+ );
+ },
);
} else {
- this._renderCover(null, coverOnly);
+ this._renderCover(null, coverOnly, next);
}
-
- return coverOnly;
+ } else {
+ cb(false);
}
}
@@ -190,16 +199,16 @@ export function Fetch(Base) {
cb();
};
- const onlyCover = this._fetchCover();
-
- if (onlyCover) {
- done();
- } else {
- this._fetch(() => {
- onNavigate();
+ this._fetchCover(onlyCover => {
+ if (onlyCover) {
done();
- });
- }
+ } else {
+ this._fetch(() => {
+ onNavigate();
+ done();
+ });
+ }
+ });
}
_fetchFallbackPage(path, qs, cb = noop) {
diff --git a/src/core/render/index.js b/src/core/render/index.js
index bf2f859d6..d3faeff85 100644
--- a/src/core/render/index.js
+++ b/src/core/render/index.js
@@ -382,7 +382,7 @@ export function Render(Base) {
});
}
- _renderCover(text, coverOnly) {
+ _renderCover(text, coverOnly, next) {
const el = dom.getNode('.cover');
dom.toggleClass(
@@ -392,37 +392,56 @@ export function Render(Base) {
);
if (!text) {
dom.toggleClass(el, 'remove', 'show');
+ next();
return;
}
dom.toggleClass(el, 'add', 'show');
- let html = this.coverIsHTML ? text : this.compiler.cover(text);
+ const callback = html => {
+ const m = html
+ .trim()
+ .match('
([^<]*?)
$');
- const m = html
- .trim()
- .match('([^<]*?)
$');
+ if (m) {
+ if (m[2] === 'color') {
+ el.style.background = m[1] + (m[3] || '');
+ } else {
+ let path = m[1];
- if (m) {
- if (m[2] === 'color') {
- el.style.background = m[1] + (m[3] || '');
- } else {
- let path = m[1];
+ dom.toggleClass(el, 'add', 'has-mask');
+ if (!isAbsolutePath(m[1])) {
+ path = getPath(this.router.getBasePath(), m[1]);
+ }
- dom.toggleClass(el, 'add', 'has-mask');
- if (!isAbsolutePath(m[1])) {
- path = getPath(this.router.getBasePath(), m[1]);
+ el.style.backgroundImage = `url(${path})`;
+ el.style.backgroundSize = 'cover';
+ el.style.backgroundPosition = 'center center';
}
- el.style.backgroundImage = `url(${path})`;
- el.style.backgroundSize = 'cover';
- el.style.backgroundPosition = 'center center';
+ html = html.replace(m[0], '');
}
- html = html.replace(m[0], '');
- }
+ this._renderTo('.cover-main', html);
+ next();
+ };
- this._renderTo('.cover-main', html);
+ // TODO: Call the 'beforeEach' and 'afterEach' hooks.
+ // However, when the cover and the home page are on the same page,
+ // the 'beforeEach' and 'afterEach' hooks are called multiple times.
+ // It is difficult to determine the target of the hook within the
+ // hook functions. We might need to make some changes.
+ if (this.coverIsHTML) {
+ callback(text);
+ } else {
+ prerenderEmbed(
+ {
+ compiler: this.compiler,
+ raw: text,
+ },
+ tokens => callback(this.compiler.cover(tokens)),
+ );
+ }
}
_updateRender() {
diff --git a/test/e2e/embed-files.test.js b/test/e2e/embed-files.test.js
new file mode 100644
index 000000000..46ae1d1c3
--- /dev/null
+++ b/test/e2e/embed-files.test.js
@@ -0,0 +1,33 @@
+import docsifyInit from '../helpers/docsify-init.js';
+import { test, expect } from './fixtures/docsify-init-fixture.js';
+
+test.describe('Embed files', () => {
+ const routes = {
+ 'fragment.md': '## Fragment',
+ };
+
+ test('embed into homepage', async ({ page }) => {
+ await docsifyInit({
+ routes,
+ markdown: {
+ homepage: "# Hello World\n\n[fragment](fragment.md ':include')",
+ },
+ // _logHTML: {},
+ });
+
+ await expect(page.locator('#main')).toContainText('Fragment');
+ });
+
+ test('embed into cover', async ({ page }) => {
+ await docsifyInit({
+ routes,
+ markdown: {
+ homepage: '# Hello World',
+ coverpage: "# Cover\n\n[fragment](fragment.md ':include')",
+ },
+ // _logHTML: {},
+ });
+
+ await expect(page.locator('.cover-main')).toContainText('Fragment');
+ });
+});
diff --git a/test/e2e/plugins.test.js b/test/e2e/plugins.test.js
index 68534830d..00f31b0ad 100644
--- a/test/e2e/plugins.test.js
+++ b/test/e2e/plugins.test.js
@@ -158,6 +158,74 @@ test.describe('Plugins', () => {
});
});
+ test.describe('doneEach()', () => {
+ test('callback after cover loads', async ({ page }) => {
+ const consoleMessages = [];
+
+ page.on('console', msg => consoleMessages.push(msg.text()));
+
+ await docsifyInit({
+ config: {
+ plugins: [
+ function (hook) {
+ hook.doneEach(() => {
+ const homepageTitle = document.querySelector('#homepage-title');
+ const coverTitle = document.querySelector('#cover-title');
+ console.log(homepageTitle?.textContent);
+ console.log(coverTitle?.textContent);
+ });
+ },
+ ],
+ },
+ markdown: {
+ homepage: '# Hello World :id=homepage-title',
+ coverpage: () => {
+ return new Promise(resolve => {
+ setTimeout(() => resolve('# Cover Page :id=cover-title'), 500);
+ });
+ },
+ },
+ // _logHTML: {},
+ });
+
+ await expect(consoleMessages).toEqual(['Hello World', 'Cover Page']);
+ });
+
+ test('only cover', async ({ page }) => {
+ const consoleMessages = [];
+
+ page.on('console', msg => consoleMessages.push(msg.text()));
+
+ await docsifyInit({
+ config: {
+ onlyCover: true,
+ plugins: [
+ function (hook) {
+ hook.doneEach(() => {
+ const homepageTitle = document.querySelector('#homepage-title');
+ const coverTitle = document.querySelector('#cover-title');
+ console.log(homepageTitle?.textContent);
+ console.log(coverTitle?.textContent);
+ });
+ },
+ ],
+ },
+ markdown: {
+ homepage: '# Hello World :id=homepage-title',
+ coverpage: () => {
+ return new Promise(resolve => {
+ setTimeout(() => resolve('# Cover Page :id=cover-title'), 500);
+ });
+ },
+ },
+ waitForSelector: '.cover-main > *:first-child',
+ // _logHTML: {},
+ });
+
+ await expect(consoleMessages).toEqual(['undefined', 'Cover Page']);
+ });
+ });
+
test.describe('route data accessible to plugins', () => {
let routeData = null;
diff --git a/test/helpers/docsify-init.js b/test/helpers/docsify-init.js
index bbc357022..6fd4f5d0c 100644
--- a/test/helpers/docsify-init.js
+++ b/test/helpers/docsify-init.js
@@ -17,11 +17,11 @@ const docsifyURL = '/dist/docsify.js'; // Playwright
* @param {Function|Object} [options.config] docsify configuration (merged with default)
* @param {String} [options.html] HTML content to use for docsify `index.html` page
* @param {Object} [options.markdown] Docsify markdown content
- * @param {String} [options.markdown.coverpage] coverpage markdown
- * @param {String} [options.markdown.homepage] homepage markdown
- * @param {String} [options.markdown.navbar] navbar markdown
- * @param {String} [options.markdown.sidebar] sidebar markdown
- * @param {Object} [options.routes] custom routes defined as `{ pathOrGlob: responseText }`
+ * @param {String|(()=>Promise|String)} [options.markdown.coverpage] coverpage markdown
+ * @param {String|(()=>Promise|String)} [options.markdown.homepage] homepage markdown
+ * @param {String|(()=>Promise|String)} [options.markdown.navbar] navbar markdown
+ * @param {String|(()=>Promise|String)} [options.markdown.sidebar] sidebar markdown
+ * @param {RecordPromise|String)>} [options.routes] custom routes defined as `{ pathOrGlob: response }`
* @param {String} [options.script] JS to inject via