mirror of
https://github.com/quartz-community/external-quartz-leaflet-map-plugin.git
synced 2026-07-22 03:00:24 +00:00
Quartz community main (#14)
* Port to Quartz v5 Bases view plugin Rewrite the v4 transformer plugin as a v5 Bases view plugin that registers a 'leaflet-map' view type via the bases-page view registry. - Add v5 plugin structure: package.json, tsup.config.ts, tsconfig.json - Add view renderer (src/view.tsx) that builds server-side marker HTML - Add client-side Leaflet initialization (src/scripts/leaflet-map.inline.ts) - Add full Leaflet + custom marker styles (src/styles/leaflet-map.scss) - Add type definitions for view config and marker data (src/types.ts) - Auto-register view on import via viewRegistry.register() - Mark @quartz-community/bases-page as external peer dependency - ESM + DTS build via tsup with inline-script and SCSS loaders * fix: move bases-page from dependencies to devDependencies bases-page must only be a peerDependency at runtime so Node resolves to the host's installed copy. Keeping it as a regular dependency caused npm to install a private copy in node_modules/, creating a separate ViewRegistry instance that never communicated with the host plugin. Now bases-page is a devDependency (needed for TypeScript compilation) and a peerDependency (resolved at runtime from the host). * feat: resolve image paths via transformLink for correct link resolution - Import transformLink and FullSlug from @quartz-community/bases-page - Use slug, allSlugs, linkResolution props from ViewRendererProps - Remove stale ambient module declaration that shadowed real package types - Add @quartz-community/utils and @quartz-community/types as devDependencies * fix: defer ControlContainer class definition to avoid L is not defined at parse time * fix: add CDN fallback and error handling for dependency loading Switch primary CDN from unpkg.com to cdn.jsdelivr.net with unpkg.com as fallback. Add loadScript() helper that tries multiple URLs sequentially. Add try/catch around loadDependencies() in the nav listener with early return when no map elements found, and user-visible error message when all CDN sources fail. Fixes maps not loading when browser content blocking (e.g. Firefox Enhanced Tracking Protection) blocks unpkg.com. * chore: linting * feat: add render event listener for in-place DOM re-initialization * fix: update to bases-page changes * feat: allow plugin views to pass options * Cleanup typing * Remove quartz v4plugin files * Removal of duplicated imports * Cleanup typing * Removal of duplicated imports * chore: commit dist/ and remove prepare script * Update README.md * fix: update to bases-page changes * chore: rebase * Cleanup typing * Removal of duplicated imports * Cleanup typing * Remove quartz v4plugin files * Removal of duplicated imports --------- Co-authored-by: saberzero1 <github@emilebangma.com>
This commit is contained in:
parent
6b01c731af
commit
74a8afcf1f
13 changed files with 246 additions and 208 deletions
|
|
@ -12,7 +12,14 @@
|
|||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"ignorePatterns": ["dist", "node_modules"],
|
||||
"ignorePatterns": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"leafletMapPlugin.ts",
|
||||
"src/build.ts",
|
||||
"src/plugins",
|
||||
"src/util"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -9,6 +9,34 @@ A live demo can be found [here](https://requiae.github.io/quartz-leaflet-map-plu
|
|||
|
||||
## How to add it to your quartz
|
||||
|
||||
### Quartz v5
|
||||
|
||||
Add the plugin to the `plugins` list in your `quartz.config.yaml`:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
- source: github:quartz-community/external-quartz-leaflet-map-plugin
|
||||
enabled: true
|
||||
```
|
||||
|
||||
This plugin also requires the [bases-page](https://github.com/quartz-community/bases-page) plugin.
|
||||
|
||||
### Plugin Options
|
||||
|
||||
You can pass options to the plugin via `quartz.config.yaml`:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
- source: github:quartz-community/external-quartz-leaflet-map-plugin
|
||||
enabled: true
|
||||
options:
|
||||
enableCopyTool: true
|
||||
```
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| -------------- | ------- | ------- | ------------------------------------------------------------- |
|
||||
| enableCopyTool | boolean | `false` | Enables the copy tool from the Obsidian Leaflet Bases plugin. |
|
||||
|
||||
### Quartz v4
|
||||
|
||||
> Quartz v4 does not have a bases implementation. As such it is highly recommended (read 'basically required') to use the `mapName` feature if your vault has multiple maps.
|
||||
|
|
|
|||
25
dist/index.d.ts
vendored
25
dist/index.d.ts
vendored
|
|
@ -1,6 +1,4 @@
|
|||
import { ViewTypeRegistration } from '@quartz-community/bases-page';
|
||||
|
||||
declare const leafletMapViewRegistration: ViewTypeRegistration;
|
||||
import { ViewTypeRegistration } from "@quartz-community/bases-page";
|
||||
|
||||
interface LeafletMapViewConfig {
|
||||
mapName?: string;
|
||||
|
|
@ -13,6 +11,9 @@ interface LeafletMapViewConfig {
|
|||
scale?: number;
|
||||
unit?: string;
|
||||
}
|
||||
interface LeafletMapPluginOptions {
|
||||
enableCopyTool?: boolean;
|
||||
}
|
||||
interface MarkerData {
|
||||
mapName?: string;
|
||||
coordinates: string;
|
||||
|
|
@ -21,4 +22,20 @@ interface MarkerData {
|
|||
minZoom?: number;
|
||||
}
|
||||
|
||||
export { type LeafletMapViewConfig, type MarkerData, leafletMapViewRegistration };
|
||||
declare const leafletMapViewRegistration: ViewTypeRegistration;
|
||||
|
||||
declare function registerLeafletMap(userOpts?: Partial<LeafletMapPluginOptions>): void;
|
||||
/**
|
||||
* Called by Quartz's config-loader with merged options from
|
||||
* `quartz.config.yaml` and `package.json` defaultOptions.
|
||||
*/
|
||||
declare function init(options?: Record<string, unknown>): void;
|
||||
|
||||
export {
|
||||
type LeafletMapPluginOptions,
|
||||
type LeafletMapViewConfig,
|
||||
type MarkerData,
|
||||
init,
|
||||
leafletMapViewRegistration,
|
||||
registerLeafletMap,
|
||||
};
|
||||
|
|
|
|||
199
dist/index.js
vendored
199
dist/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
114
package-lock.json
generated
114
package-lock.json
generated
|
|
@ -1003,19 +1003,13 @@
|
|||
},
|
||||
"node_modules/@quartz-community/bases-page": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "git+ssh://git@github.com/quartz-community/bases-page.git#67e3971e6d7dc67ee11ba36e7fe4a16dbb1d19ae",
|
||||
"resolved": "git+ssh://git@github.com/quartz-community/bases-page.git#7d40bf8ac23b75cd089bfa29d55f0fbe6920c025",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@quartz-community/types": "github:quartz-community/types",
|
||||
"@quartz-community/utils": "github:quartz-community/utils",
|
||||
"hast-util-from-html": "^2.0.3",
|
||||
"preact-render-to-string": "^6.6.6",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.9.3",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"vfile": "^6.0.3",
|
||||
"yaml": "^2.8.1"
|
||||
"hast-util-from-html": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22",
|
||||
|
|
@ -1023,7 +1017,11 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@jackyzha0/quartz": "^5.0.0",
|
||||
"preact": "^10.0.0"
|
||||
"preact": "^10.0.0",
|
||||
"preact-render-to-string": "^6.6.6",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"vfile": "^6.0.3",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@jackyzha0/quartz": {
|
||||
|
|
@ -1031,6 +1029,18 @@
|
|||
},
|
||||
"preact": {
|
||||
"optional": false
|
||||
},
|
||||
"preact-render-to-string": {
|
||||
"optional": true
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"optional": true
|
||||
},
|
||||
"vfile": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -3198,16 +3208,6 @@
|
|||
"url": "https://opencollective.com/preact"
|
||||
}
|
||||
},
|
||||
"node_modules/preact-render-to-string": {
|
||||
"version": "6.6.6",
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.6.6.tgz",
|
||||
"integrity": "sha512-EfqZJytnjJldV+YaaqhthU2oXsEf5e+6rDv957p+zxAvNfFLQOPfvBOTncscQ+akzu6Wrl7s3Pa0LjUQmWJsGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": ">=10 || >= 11.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
|
@ -4654,20 +4654,6 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-is": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
||||
"integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-stringify-position": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
||||
|
|
@ -4682,37 +4668,6 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-visit": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
|
||||
"integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0",
|
||||
"unist-util-visit-parents": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-visit-parents": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
|
||||
"integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
@ -4759,6 +4714,21 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-location": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
|
||||
"integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile-message": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
||||
|
|
@ -4818,22 +4788,6 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
|
||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -83,7 +83,9 @@
|
|||
"dependencies": [],
|
||||
"defaultOrder": 50,
|
||||
"defaultEnabled": true,
|
||||
"defaultOptions": {}
|
||||
"defaultOptions": {
|
||||
"enableCopyTool": false
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22",
|
||||
|
|
|
|||
6
src/declarations.d.ts
vendored
6
src/declarations.d.ts
vendored
|
|
@ -12,9 +12,3 @@ declare module "*.inline.ts" {
|
|||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
addCleanup(fn: () => void): void;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
src/globals.d.ts
vendored
10
src/globals.d.ts
vendored
|
|
@ -1,9 +1,7 @@
|
|||
export declare global {
|
||||
interface Window {
|
||||
addCleanup(fn: (...args: unknown[]) => void);
|
||||
}
|
||||
export {};
|
||||
|
||||
interface ControlOptions {
|
||||
enableCopyTool: boolean;
|
||||
declare global {
|
||||
interface Window {
|
||||
addCleanup(fn: () => void): void;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/index.ts
20
src/index.ts
|
|
@ -1,7 +1,25 @@
|
|||
import { viewRegistry } from "@quartz-community/bases-page";
|
||||
import { leafletMapViewRegistration } from "./view";
|
||||
import { defaultOptions } from "./types";
|
||||
import type { LeafletMapPluginOptions } from "./types";
|
||||
|
||||
export { leafletMapViewRegistration } from "./view";
|
||||
export type { LeafletMapViewConfig, MarkerData } from "./types";
|
||||
export type { LeafletMapViewConfig, MarkerData, LeafletMapPluginOptions } from "./types";
|
||||
|
||||
export function registerLeafletMap(userOpts?: Partial<LeafletMapPluginOptions>): void {
|
||||
const opts = { ...defaultOptions, ...userOpts };
|
||||
viewRegistry.register({
|
||||
...leafletMapViewRegistration,
|
||||
options: opts,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Quartz's config-loader with merged options from
|
||||
* `quartz.config.yaml` and `package.json` defaultOptions.
|
||||
*/
|
||||
export function init(options?: Record<string, unknown>): void {
|
||||
registerLeafletMap(options as Partial<LeafletMapPluginOptions> | undefined);
|
||||
}
|
||||
|
||||
viewRegistry.register(leafletMapViewRegistration);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
declare const L: typeof import("leaflet");
|
||||
|
||||
type LatLng = import("leaflet").LatLng;
|
||||
|
|
@ -28,6 +26,7 @@ interface CreateIconsOptions {
|
|||
inTemplates?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace lucide {
|
||||
const createElement: (icon: IconNode) => HTMLElement;
|
||||
const createIcons: ({ icons, nameAttr, attrs, root, inTemplates }?: CreateIconsOptions) => void;
|
||||
|
|
@ -492,9 +491,12 @@ interface ControlContainerOptions extends ControlOptions {
|
|||
const DefaultControlContainerOptions: ControlContainerOptions = { enableCopyTool: false };
|
||||
|
||||
// Lazily define ControlContainer after Leaflet is loaded (L.Control is unavailable at parse time)
|
||||
let _ControlContainer: typeof L.Control<ControlContainerOptions> | null = null;
|
||||
type ControlContainerClass = new (options: ControlContainerOptions) => L.Control & {
|
||||
updateSettings(options: MapDataSet): void;
|
||||
};
|
||||
let _ControlContainer: ControlContainerClass | null = null;
|
||||
|
||||
function getControlContainerClass() {
|
||||
function getControlContainerClass(): ControlContainerClass {
|
||||
if (_ControlContainer) return _ControlContainer;
|
||||
|
||||
class ControlContainer extends L.Control {
|
||||
|
|
@ -554,7 +556,7 @@ function getControlContainerClass() {
|
|||
}
|
||||
}
|
||||
|
||||
_ControlContainer = ControlContainer as unknown as typeof L.Control;
|
||||
_ControlContainer = ControlContainer as unknown as ControlContainerClass;
|
||||
return _ControlContainer;
|
||||
}
|
||||
|
||||
|
|
@ -614,9 +616,7 @@ async function initialiseMap(
|
|||
const ControlContainer = getControlContainerClass();
|
||||
const controls = new ControlContainer({
|
||||
enableCopyTool: dataset.enableCopyTool === "true",
|
||||
}) as InstanceType<typeof L.Control<ControlContainerOptions>> & {
|
||||
updateSettings(options: MapDataSet): void;
|
||||
};
|
||||
});
|
||||
controls.addTo(mapItem);
|
||||
controls.updateSettings(dataset);
|
||||
|
||||
|
|
@ -634,7 +634,7 @@ function cleanupMap(mapItem: Map | undefined) {
|
|||
mapItem?.remove();
|
||||
}
|
||||
|
||||
document.addEventListener("nav", async () => {
|
||||
async function initializeLeafletMaps() {
|
||||
const maps: NodeListOf<HTMLElement> = document.querySelectorAll("div.leaflet-map");
|
||||
if (maps.length === 0) return;
|
||||
|
||||
|
|
@ -654,6 +654,9 @@ document.addEventListener("nav", async () => {
|
|||
const mapItem = await initialiseMap(map, markerData);
|
||||
window.addCleanup(() => cleanupMap(mapItem));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("nav", initializeLeafletMaps);
|
||||
document.addEventListener("render", initializeLeafletMaps);
|
||||
|
||||
export default "";
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ export interface LeafletMapViewConfig {
|
|||
unit?: string;
|
||||
}
|
||||
|
||||
export interface LeafletMapPluginOptions {
|
||||
enableCopyTool?: boolean;
|
||||
}
|
||||
|
||||
export const defaultOptions: LeafletMapPluginOptions = {
|
||||
enableCopyTool: false,
|
||||
};
|
||||
|
||||
export interface MarkerData {
|
||||
mapName?: string;
|
||||
coordinates: string;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { FullSlug } from "@quartz-community/bases-page";
|
|||
import leafletMapCss from "./styles/leaflet-map.scss";
|
||||
import leafletMapScript from "./scripts/leaflet-map.inline";
|
||||
import { DEFAULTS } from "./types";
|
||||
import type { MarkerData } from "./types";
|
||||
import type { MarkerData, LeafletMapPluginOptions } from "./types";
|
||||
|
||||
type MarkerWithEntry = MarkerData & {
|
||||
name: string;
|
||||
|
|
@ -36,7 +36,9 @@ const leafletMapRenderer: ViewRenderer = ({
|
|||
slug,
|
||||
allSlugs,
|
||||
linkResolution,
|
||||
options,
|
||||
}: ViewRendererProps) => {
|
||||
const pluginOptions = (options ?? {}) as LeafletMapPluginOptions;
|
||||
const mapName = getString(view.mapName);
|
||||
const rawImage = getString(view.image);
|
||||
|
||||
|
|
@ -96,7 +98,7 @@ const leafletMapRenderer: ViewRenderer = ({
|
|||
data-zoom-delta={zoomDelta}
|
||||
data-scale={scale}
|
||||
data-unit={unit}
|
||||
data-enable-copy-tool="false"
|
||||
data-enable-copy-tool={pluginOptions.enableCopyTool ?? false}
|
||||
>
|
||||
{filteredMarkers.map((marker) => (
|
||||
<div
|
||||
|
|
|
|||
Loading…
Reference in a new issue