Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Making Quartz available offline by making it a PWA #465

Merged
merged 43 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0ae3b31
Adding PWA and chaching for offline aviability
adambrangenberg Sep 10, 2023
6154f6a
renamed workbox config to fit Quartz' scheme
adambrangenberg Sep 10, 2023
3c5486b
Documenting new configuration
adambrangenberg Sep 10, 2023
03198b6
Added missig umami documentation
adambrangenberg Sep 10, 2023
9c3c05e
Fixed formatting so the build passes, thank you prettier :)
adambrangenberg Sep 10, 2023
d4fc581
specified caching strategies to improve performance
adambrangenberg Sep 11, 2023
d8a8635
formatting...
adambrangenberg Sep 11, 2023
5b7fd51
fixing "404 manifest.json not found" on subdirectories by adding a / …
adambrangenberg Sep 11, 2023
b5c8413
turning it into a plugin
adambrangenberg Sep 17, 2023
4cadd09
Removed Workbox-cli and updated @types/node
adambrangenberg Sep 18, 2023
06e3413
Added Serviceworkercode to offline.ts
adambrangenberg Sep 18, 2023
e112398
formatting
adambrangenberg Sep 18, 2023
6375229
Merge pull request #1 from jackyzha0/v4
adambrangenberg Sep 18, 2023
70404aa
Removing workbox from docs
adambrangenberg Sep 18, 2023
6ee800c
applied suggestions
adambrangenberg Sep 18, 2023
b6b9a72
Removed path.join for sw path
adambrangenberg Sep 19, 2023
0378500
Removed path.join for manifest path
adambrangenberg Sep 19, 2023
2aabdff
Removing path module import
adambrangenberg Sep 19, 2023
2a50a94
Added absolute path to manifests start_url and manifest "import" usin…
adambrangenberg Sep 19, 2023
6822944
Adding protocol to baseurl
adambrangenberg Sep 19, 2023
623a6b9
Adding protocol to start_url too then
adambrangenberg Sep 19, 2023
12197c6
formatting...
adambrangenberg Sep 19, 2023
c55a101
Adding fallback page
adambrangenberg Sep 19, 2023
dc113cb
Documenting offline plugin
adambrangenberg Sep 19, 2023
f169f1e
formatting...
adambrangenberg Sep 19, 2023
45c0e96
merge suggestion
adambrangenberg Sep 19, 2023
5cde360
merge suggestion
adambrangenberg Sep 19, 2023
ef222e2
merge suggestion
adambrangenberg Sep 19, 2023
14219a0
merge suggestion
adambrangenberg Sep 19, 2023
36a5493
merge suggestion
adambrangenberg Sep 19, 2023
bbbd706
merge suggestion
adambrangenberg Sep 19, 2023
55c7b8b
merge suggestion
adambrangenberg Sep 19, 2023
5ce4306
merge suggestion
adambrangenberg Sep 19, 2023
e2024b2
merge suggestion
adambrangenberg Sep 19, 2023
2e03a57
merge suggestion
adambrangenberg Sep 19, 2023
5ce138f
merge suggestion
adambrangenberg Sep 19, 2023
14e2509
merge suggestion
adambrangenberg Sep 19, 2023
cdf6e05
formatting...
adambrangenberg Sep 19, 2023
dbef507
Fixing manifest path, all these nits hiding the actual issues .-.
adambrangenberg Sep 19, 2023
199d4c9
Offline fallback page through plugins, most things taken from 404 Plugin
adambrangenberg Sep 19, 2023
5f1d19d
adding Offline Plugin to config
adambrangenberg Sep 19, 2023
c0cc626
formatting...
adambrangenberg Sep 19, 2023
78e15de
Turned offline off as default and removed offline.md
adambrangenberg Sep 20, 2023
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
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ const config: QuartzConfig = {
This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure:

- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
- `description`: description of the site. This will be used when someone installs your site as an App.
- `enableSPA`: whether to enable [[SPA Routing]] on your site.
- `enablePopovers`: whether to enable [[popover previews]] on your site.
- `analytics`: what to use for analytics on your site. Values can be
- `null`: don't use analytics;
- `{ provider: "umami", websiteId: <your-umami-id> }`: easy, privacy-friendly, open source, GDPR Compliant analytics;
- `{ provider: 'plausible' }`: use [Plausible](https://plausible.io/), a privacy-friendly alternative to Google Analytics; or
- `{ provider: 'google', tagId: <your-google-tag> }`: use Google Analytics
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
Expand Down
31 changes: 31 additions & 0 deletions docs/features/offline access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
adambrangenberg marked this conversation as resolved.
Show resolved Hide resolved
title: "Offline Access (PWA)"
tags:
- plugin/emitter
---

This plugin allows your website to be accessible offline and be installed as an app. You can use it by adding `Plugin.Offline(),` to the `emitters` in `quartz.config.ts`

## Offline Capability

Whenever you visit a page it gets cached for offline use. Depending on the kind of content, the process for caching is diffent:

- **Pages** (HTML, your converted Markdown files): Quartz first tries to get them over the Network. If that fails, your browser attempts to fetch it from the cache.
- **Static Resources** (Fonts, CSS Styling, JavaScript): Quartz uses cached resources by default and updates the cache over the network in the background.
- **Images**: Images are saved once and then served from cache. Quartz uses a limited cache of 60 images and images remain in the cache for 30 days

You can edit the fallback page by changing the `offline.md` file in the root of your `content` directory

## Progressive Web App (PWA)

Progressive Web Apps can have [many properties](https://developer.mozilla.org/en-US/docs/Web/Manifest). We're only going to mention the ones Quartz supports by default, however you can edit the offline plugins file to add more in case required.

- **icons**: the `icon.svg` file in the `quartz/static` directory is used for all the icons. This makes it easier to scale the image since you don't need to provide an png for every size
- **name**, **short_name**: Uses the `pageTitle` configured in `quartz.config.ts`
- **description**: Uses the `description` configured in `quartz.config.ts`
- **background_color**, **theme_color**: Uses the `lightMode.light` color configured in `quartz.config.ts`.
- **start_url**: Uses the `baseUrl` configured in `quartz.config.ts`

### Default values

- **display**: this is set to `minimal-ui`
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ This will guide you through initializing your Quartz with content. Once you've d

## 🔧 Features

- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], and [many more](./features) right out of the box
- [[Obsidian compatibility]], [[full-text search]], [[graph view]], note transclusion, [[wikilinks]], [[backlinks]], [[Latex]], [[syntax highlighting]], [[popover previews]], [[offline access]] and [many more](./features) right out of the box
- Hot-reload for both configuration and content
- Simple JSX layouts and [[creating components|page components]]
- [[SPA Routing|Ridiculously fast page loads]] and tiny bundle sizes
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"@types/flexsearch": "^0.7.3",
"@types/hast": "^2.3.4",
"@types/js-yaml": "^4.0.5",
"@types/node": "^20.1.2",
"@types/node": "^20.6.2",
"@types/pretty-time": "^1.1.2",
"@types/source-map-support": "^0.5.6",
"@types/workerpool": "^6.4.0",
Expand Down
1 change: 1 addition & 0 deletions quartz.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Plugin from "./quartz/plugins"
const config: QuartzConfig = {
configuration: {
pageTitle: "🪴 Quartz 4.0",
description: "Quartz Documentation Page and Demo",
enableSPA: true,
enablePopovers: true,
analytics: {
Expand Down
1 change: 1 addition & 0 deletions quartz/cfg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type Analytics =

export interface GlobalConfiguration {
pageTitle: string
description: string
/** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */
enableSPA: boolean
/** Whether to display Wikipedia-style popovers when hovering over links */
Expand Down
4 changes: 4 additions & 0 deletions quartz/components/Head.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default (() => {

const iconPath = joinSegments(baseDir, "static/icon.png")
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png`
const manifest =
cfg.baseUrl == undefined ? "/manifest.json" : `https://${cfg.baseUrl}/manifest.json`

return (
<head>
Expand All @@ -25,7 +27,9 @@ export default (() => {
{cfg.baseUrl && <meta property="og:image" content={ogImagePath} />}
<meta property="og:width" content="1200" />
<meta property="og:height" content="675" />
<meta name="theme-color" content="#faf8f8" />
<link rel="icon" href={iconPath} />
<link rel="manifest" href={manifest} />
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
Expand Down
12 changes: 12 additions & 0 deletions quartz/components/pages/OfflineFallbackPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { QuartzComponentConstructor } from "../types"

function OfflineFallbackPage() {
return (
<article class="popover-hint">
<h1>Offline</h1>
<p>This page isn't offline available yet.</p>
</article>
)
}

export default (() => OfflineFallbackPage) satisfies QuartzComponentConstructor
5 changes: 5 additions & 0 deletions quartz/plugins/emitters/componentResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ function addGlobalPageResources(
document.dispatchEvent(event)`)
}

componentResources.afterDOMLoaded.push(`
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}`)

let wsUrl = `ws://localhost:${ctx.argv.wsPort}`

if (ctx.argv.remoteDevHost) {
Expand Down
1 change: 1 addition & 0 deletions quartz/plugins/emitters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { Assets } from "./assets"
export { Static } from "./static"
export { ComponentResources } from "./componentResources"
export { NotFoundPage } from "./404"
export { Offline } from "./offline"
97 changes: 97 additions & 0 deletions quartz/plugins/emitters/offline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { QuartzEmitterPlugin } from "../types"
import { FilePath, FullSlug } from "../../util/path"
import { FullPageLayout } from "../../cfg"
import { sharedPageComponents } from "../../../quartz.layout"
import OfflineFallbackPage from "../../components/pages/OfflineFallbackPage"
import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import { defaultProcessedContent } from "../vfile"
import { QuartzComponentProps } from "../../components/types"

export const Offline: QuartzEmitterPlugin = () => {
const opts: FullPageLayout = {
...sharedPageComponents,
pageBody: OfflineFallbackPage(),
beforeBody: [],
left: [],
right: [],
}

const { head: Head, pageBody, footer: Footer } = opts
const Body = BodyConstructor()

return {
name: "OfflineSupport",
getQuartzComponents() {
return [Head, Body, pageBody, Footer]
},
async emit({ cfg }, _content, resources, emit): Promise<FilePath[]> {
const manifest = {
short_name: cfg.configuration.pageTitle,
name: cfg.configuration.pageTitle,
description: cfg.configuration.description,
background_color: cfg.configuration.theme.colors.lightMode.light,
theme_color: cfg.configuration.theme.colors.lightMode.light,
display: "minimal-ui",
icons: [
{
src: "static/icon.svg",
sizes: "any",
purpose: "maskable",
},
{
src: "static/icon.svg",
sizes: "any",
purpose: "any",
},
],
start_url:
cfg.configuration.baseUrl == undefined ? "/" : `https://${cfg.configuration.baseUrl}/`,
}

const serviceWorker =
"importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-sw.js');" +
"const {pageCache, imageCache, staticResourceCache, googleFontsCache, offlineFallback} = workbox.recipes;" +
"pageCache(); googleFontsCache(); staticResourceCache(); imageCache(); offlineFallback();"

const slug = "offline" as FullSlug

const url = new URL(`https://${cfg.configuration.baseUrl ?? "example.com"}`)
const path = url.pathname as FullSlug
const externalResources = pageResources(path, resources)
const [tree, vfile] = defaultProcessedContent({
slug,
text: "Offline",
description: "This page isn't offline available yet.",
frontmatter: { title: "Offline", tags: [] },
})

const componentData: QuartzComponentProps = {
fileData: vfile.data,
externalResources,
cfg: cfg.configuration,
children: [],
tree,
allFiles: [],
}

return Promise.all([
emit({
content: JSON.stringify(manifest),
slug: "manifest" as FullSlug,
ext: ".json",
}),
emit({
content: serviceWorker,
slug: "sw" as FullSlug,
ext: ".js",
}),
emit({
content: renderPage(slug, componentData, opts, externalResources),
slug,
ext: ".html",
}),
])
},
}
}
74 changes: 74 additions & 0 deletions quartz/static/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.