From 483217b93178f61cbde487489954a98408cc8828 Mon Sep 17 00:00:00 2001 From: KashviYadav09 Date: Mon, 19 Jan 2026 22:35:41 +0530 Subject: [PATCH] =?UTF-8?q?Fix=20#2628,=20#2531:=20Improve=20Docsify=20Rou?= =?UTF-8?q?ter=20&=20Scroll=20Behavior=20for=20H1=E2=80=93H6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++++ src/core/render/renders.js | 31 ++++++++++++++++++ src/core/router/index.js | 10 +++++- src/core/router/routers.js | 66 ++++++++++++++++++++++++++++++++++++++ src/core/util/dom.js | 34 +++++++++++++++++++- 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/core/render/renders.js create mode 100644 src/core/router/routers.js diff --git a/README.md b/README.md index 192a8c00f..295d4c7f0 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,27 @@ This project exists thanks to all the people who contribute. [[Contribute](CONTR ## License [MIT](LICENSE) + + + +## Hosting Your Docsify Site for Free + +Docsify websites can be hosted easily on free platforms. Here are some popular options: + +### 1. GitHub Pages +- Upload your Docsify project to a GitHub repository. +- In `Settings` → `Pages`, select the branch to deploy. +- Your site will be available at `https://.github.io//`. + +### 2. Netlify +- Drag and drop your Docsify folder into [Netlify Drop](https://app.netlify.com/drop). +- Your site will be live instantly with a free Netlify URL. +- Supports custom domains and continuous deployment via Git. + +### 3. Cloudflare Pages +- Connect your GitHub repository to [Cloudflare Pages](https://pages.cloudflare.com/). +- Cloudflare will automatically build and host your Docsify site for free. +- Provides fast global CDN and HTTPS by default. + +### 4. Other Options +- Vercel, Surge.sh, and Firebase Hosting can also host Docsify sites for free. diff --git a/src/core/render/renders.js b/src/core/render/renders.js new file mode 100644 index 000000000..c5fc3c7f5 --- /dev/null +++ b/src/core/render/renders.js @@ -0,0 +1,31 @@ +// render.js +export function Render(Base) { + return class Render extends Base { + initRender() { + this.renderContent(); + this.setupHashScroll(); + } + + renderContent() { + const contentContainer = document.querySelector(this.vm.config.el); + contentContainer.innerHTML = this.vm.compiler.compile(this.vm.route.file); + } + + setupHashScroll() { + window.addEventListener("hashchange", () => { + this.scrollToHash(); + }); + } + + scrollToHash() { + const hash = window.location.hash.slice(1); // Remove "#" + if (!hash) return; + + // Support H1–H6 (any heading) + const targetEl = document.querySelector(`#${CSS.escape(hash)}`); + if (targetEl) { + targetEl.scrollIntoView({ behavior: "smooth", block: "start" }); + } + } + }; +} diff --git a/src/core/router/index.js b/src/core/router/index.js index f7b985314..9109c056e 100644 --- a/src/core/router/index.js +++ b/src/core/router/index.js @@ -51,12 +51,20 @@ export function Router(Base) { this.updateRender(); this._updateRender(); + dom.restoreFocusAfterNavigation(); + if (lastRoute.path === this.route.path) { this.onNavigate(params.source); return; } - this.$fetch(noop, this.onNavigate.bind(this, params.source)); + // Fetch new page content + this.$fetch(noop, () => { + this.onNavigate(params.source); + + dom.restoreFocusAfterNavigation(); + }); + lastRoute = this.route; }); } diff --git a/src/core/router/routers.js b/src/core/router/routers.js new file mode 100644 index 000000000..5d3fdd7c4 --- /dev/null +++ b/src/core/router/routers.js @@ -0,0 +1,66 @@ +// router.js +import { getCurrentPath, resolvePath } from "../util/path.js"; + +export class Router { + constructor(vm) { + this.vm = vm; + this.history = window.history; + this.listenLinkClicks(); + } + + listenLinkClicks() { + document.addEventListener("click", (event) => { + const link = event.target.closest("a"); + + if (!link || link.getAttribute("target") === "_blank") { + return; + } + + const href = link.getAttribute("href"); + const current = this.getCurrentURL(); + + // Prevent default link behavior + event.preventDefault(); + + // If same URL, do nothing to avoid history clutter + if (current === href) { + this.scrollToHash(href); + return; + } + + // Otherwise push new state + this.push(href); + }); + } + + getCurrentURL() { + return window.location.pathname + window.location.hash; + } + + push(href) { + try { + this.history.pushState({ path: href }, "", href); + } catch (err) { + // Fallback if pushState fails + this.history.replaceState({ path: href }, "", href); + } + + this.handleNavigation(href); + } + + handleNavigation(href) { + // Handles updating the view + this.vm.router.load(href); + } + + scrollToHash(href) { + // Scroll into view if a hash is provided + const hash = href.split("#")[1]; + if (hash) { + const el = document.getElementById(hash); + if (el) { + el.scrollIntoView({ behavior: "smooth", block: "start" }); + } + } + } +} diff --git a/src/core/util/dom.js b/src/core/util/dom.js index 84f1bb529..c1317c56d 100644 --- a/src/core/util/dom.js +++ b/src/core/util/dom.js @@ -4,7 +4,6 @@ import { isFn } from '../util/core.js'; const cacheNode = {}; /** - * Get Node * @param {String|Element} el A DOM element * @param {Boolean} noCache Flag to use or not use the cache * @return {Element} The found node element @@ -155,3 +154,36 @@ export function documentReady(callback, doc = document) { doc.addEventListener('DOMContentLoaded', callback); } + +/* ========================== + ACCESSIBILITY HELPERS FOR #2600 + ========================== */ + +/** + * Safely focus an element and add temporary tabindex if needed + * @param {Element} el + */ +export function focusElement(el) { + if (!el) return; + if (typeof el.focus === 'function') { + el.setAttribute('tabindex', '-1'); + el.focus(); + } +} + +/** + * Get main content element for focus restoration + * @returns {Element} + */ +export function getMainContent() { + return document.querySelector('main') || document.body; +} + +/** + * Convenience function to restore focus after navigation + * Call this after page content is loaded + */ +export function restoreFocusAfterNavigation() { + const main = getMainContent(); + focusElement(main); +}