Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<username>.github.io/<repo-name>/`.

### 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.
Comment on lines +84 to +107
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to add these.

31 changes: 31 additions & 0 deletions src/core/render/renders.js
Original file line number Diff line number Diff line change
@@ -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" });
}
}
};
}
10 changes: 9 additions & 1 deletion src/core/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
Expand Down
66 changes: 66 additions & 0 deletions src/core/router/routers.js
Original file line number Diff line number Diff line change
@@ -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" });
}
}
}
}
34 changes: 33 additions & 1 deletion src/core/util/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}