@riebeckite/plugin-taxonomy 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +227 -0
- package/README_ja.md +225 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +675 -0
- package/dist/src/collections.d.ts +17 -0
- package/dist/src/feeds.d.ts +10 -0
- package/dist/src/locations.d.ts +14 -0
- package/dist/src/options.d.ts +13 -0
- package/dist/src/pages.d.ts +9 -0
- package/dist/src/seo.d.ts +11 -0
- package/dist/src/slug.d.ts +10 -0
- package/dist/src/types.d.ts +145 -0
- package/dist/src/url.d.ts +7 -0
- package/dist/src/xml.d.ts +1 -0
- package/package.json +60 -0
- package/style.css +107 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ContentLocationInput, type ContentPublicLocation, type Diagnostic } from "@riebeckite/core";
|
|
2
|
+
import type { ResolvedTaxonomyOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Public-location strategy for folder index notes.
|
|
5
|
+
*
|
|
6
|
+
* Core's default resolver collapses only the root `index` slug to `/`. When
|
|
7
|
+
* `folderIndexes` is enabled, a note at `<folder>/index.md` resolves to
|
|
8
|
+
* `/<folder>` instead of `/<folder>/index`, so each folder can own its index
|
|
9
|
+
* page. The chosen permalink is recorded in `metadata["taxonomy.folder"]`.
|
|
10
|
+
*
|
|
11
|
+
* The resolver never overwrites a location that another note already claims as
|
|
12
|
+
* its default permalink; it reports a diagnostic instead.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveFolderIndexLocations(entries: readonly ContentLocationInput[], options: ResolvedTaxonomyOptions, diagnostics: Diagnostic[]): ContentPublicLocation[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ResolvedTaxonomyOptions, TaxonomyOptions } from "./types.js";
|
|
2
|
+
export declare const DEFAULT_TAXONOMY_TAGS_BASE_PATH = "/tags";
|
|
3
|
+
export declare const DEFAULT_TAXONOMY_FOLDERS_BASE_PATH = "/folders";
|
|
4
|
+
export declare const DEFAULT_TAXONOMY_MIN_ENTRIES = 1;
|
|
5
|
+
export declare const DEFAULT_TAXONOMY_RELATED_LIMIT = 8;
|
|
6
|
+
export declare const DEFAULT_TAXONOMY_FEED_LIMIT = 50;
|
|
7
|
+
export declare const DEFAULT_TAXONOMY_CLASS_NAME = "rr-taxonomy";
|
|
8
|
+
export declare const DEFAULT_TAXONOMY_DATA_ENDPOINT = "/taxonomy/index.json";
|
|
9
|
+
/**
|
|
10
|
+
* Applies defaults to `TaxonomyOptions`. Pure and deterministic so the plugin
|
|
11
|
+
* resolves options once and reuses them for data, feeds, SEO, and locations.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveTaxonomyOptions(options?: TaxonomyOptions): ResolvedTaxonomyOptions;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ResolvedTaxonomyOptions, TaxonomyPage, TaxonomyTerm } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Renders a taxonomy listing page fragment. The fragment owns the listing and
|
|
4
|
+
* related-term navigation only; the Site's route and layout wrap it (see the
|
|
5
|
+
* README for the documented app-route wiring).
|
|
6
|
+
*/
|
|
7
|
+
export declare function renderTaxonomyPage(term: TaxonomyTerm, options: ResolvedTaxonomyOptions): TaxonomyPage;
|
|
8
|
+
/** Renders only the related-term navigation for a tag term. */
|
|
9
|
+
export declare function renderRelatedTerms(term: TaxonomyTerm, options: ResolvedTaxonomyOptions): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolvedRiebeckiteConfig, SeoMetadata } from "@riebeckite/core";
|
|
2
|
+
import type { TaxonomyTerm } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds SEO metadata for a taxonomy listing page.
|
|
5
|
+
*
|
|
6
|
+
* When a plugin provides the Core `seo` extension point (for example
|
|
7
|
+
* `@riebeckite/plugin-seo`), the metadata is delegated to it so titles,
|
|
8
|
+
* canonical URLs, and JSON-LD stay consistent with the rest of the site. A
|
|
9
|
+
* minimal fallback keeps the helper usable without an SEO provider.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildTaxonomySeo(config: ResolvedRiebeckiteConfig, term: TaxonomyTerm): SeoMetadata;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugifies a raw taxonomy value into a URL/file path. Nested values keep their
|
|
3
|
+
* `/` separators, so `Guide/Intro` becomes `guide/intro`.
|
|
4
|
+
*
|
|
5
|
+
* ASCII segments are lowercased and reduced to `[a-z0-9]` with `-` separators.
|
|
6
|
+
* A segment that reduces to nothing (for example a Japanese tag, which loses
|
|
7
|
+
* every character under `strict` ASCII slugification) falls back to its
|
|
8
|
+
* percent-encoded text so distinct values never share a URL.
|
|
9
|
+
*/
|
|
10
|
+
export declare function slugifyTaxonomyValue(value: string): string;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ContentManifestEntry, SeoMetadata } from "@riebeckite/core";
|
|
2
|
+
/** Feed formats the taxonomy plugin can generate per term. */
|
|
3
|
+
export type TaxonomyFeedFormat = "rss" | "atom" | "json";
|
|
4
|
+
/** A taxonomy grouping category. */
|
|
5
|
+
export type TaxonomyKind = "tag" | "folder";
|
|
6
|
+
/** Per-format feed output toggles. */
|
|
7
|
+
export type TaxonomyFeedOptions = {
|
|
8
|
+
rss?: boolean;
|
|
9
|
+
atom?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** Options accepted by `taxonomy()`. */
|
|
13
|
+
export type TaxonomyOptions = {
|
|
14
|
+
/** Generate tag terms. Defaults to `true`. */
|
|
15
|
+
tags?: boolean;
|
|
16
|
+
/** Generate folder terms. Defaults to `true`. */
|
|
17
|
+
folders?: boolean;
|
|
18
|
+
/** Site-local path prefix for tag pages. Defaults to `"/tags"`. */
|
|
19
|
+
tagsBasePath?: string;
|
|
20
|
+
/** Site-local path prefix for folder pages. Defaults to `"/folders"`. */
|
|
21
|
+
foldersBasePath?: string;
|
|
22
|
+
/** Folder grouping depth; `0` (or omitted) keeps the full folder path. */
|
|
23
|
+
folderDepth?: number;
|
|
24
|
+
/** Drop terms with fewer entries than this. Defaults to `1`. */
|
|
25
|
+
minEntries?: number;
|
|
26
|
+
/** Include related-term navigation for tag terms. Defaults to `true`. */
|
|
27
|
+
related?: boolean;
|
|
28
|
+
/** Maximum related terms per tag. Defaults to `8`. */
|
|
29
|
+
relatedLimit?: number;
|
|
30
|
+
/** Encode folder index notes (`<folder>/index.md`) as `<folder>`. Defaults to `false`. */
|
|
31
|
+
folderIndexes?: boolean;
|
|
32
|
+
/** Per-format feed generation. Defaults to RSS, Atom, and JSON. */
|
|
33
|
+
feeds?: TaxonomyFeedOptions;
|
|
34
|
+
/** Maximum entries included in a per-term feed. Defaults to `50`. */
|
|
35
|
+
feedLimit?: number;
|
|
36
|
+
/** Display title for a term. Defaults to `#value` for tags and the path for folders. */
|
|
37
|
+
resolveTitle?: (context: TaxonomyTermContext) => string;
|
|
38
|
+
/** Root CSS class used by the rendered page fragments. Defaults to `"rr-taxonomy"`. */
|
|
39
|
+
className?: string;
|
|
40
|
+
/** JSON data endpoint served for app routes and clients. Defaults to `"/taxonomy/index.json"`. */
|
|
41
|
+
dataEndpoint?: string;
|
|
42
|
+
};
|
|
43
|
+
/** `TaxonomyOptions` with every default resolved. */
|
|
44
|
+
export type ResolvedTaxonomyOptions = {
|
|
45
|
+
tags: boolean;
|
|
46
|
+
folders: boolean;
|
|
47
|
+
tagsBasePath: string;
|
|
48
|
+
foldersBasePath: string;
|
|
49
|
+
folderDepth: number;
|
|
50
|
+
minEntries: number;
|
|
51
|
+
related: boolean;
|
|
52
|
+
relatedLimit: number;
|
|
53
|
+
folderIndexes: boolean;
|
|
54
|
+
feeds: Required<TaxonomyFeedOptions>;
|
|
55
|
+
feedLimit: number;
|
|
56
|
+
className: string;
|
|
57
|
+
dataEndpoint: string;
|
|
58
|
+
resolveTitle?: (context: TaxonomyTermContext) => string;
|
|
59
|
+
};
|
|
60
|
+
/** Context passed to a custom `resolveTitle`. */
|
|
61
|
+
export type TaxonomyTermContext = {
|
|
62
|
+
kind: TaxonomyKind;
|
|
63
|
+
value: string;
|
|
64
|
+
basePath: string;
|
|
65
|
+
};
|
|
66
|
+
/** A lightweight entry reference safe to serialize into JSON data. */
|
|
67
|
+
export type TaxonomyEntryReference = {
|
|
68
|
+
slug: string;
|
|
69
|
+
permalink: string;
|
|
70
|
+
title: string;
|
|
71
|
+
updated: string | null;
|
|
72
|
+
summary: string;
|
|
73
|
+
};
|
|
74
|
+
/** A related-term reference used by related-tag navigation. */
|
|
75
|
+
export type TaxonomyRelatedTerm = {
|
|
76
|
+
kind: TaxonomyKind;
|
|
77
|
+
value: string;
|
|
78
|
+
title: string;
|
|
79
|
+
path: string;
|
|
80
|
+
permalink: string;
|
|
81
|
+
count: number;
|
|
82
|
+
};
|
|
83
|
+
/** Resolved feed URLs for one term. */
|
|
84
|
+
export type TaxonomyFeedLinks = {
|
|
85
|
+
rss?: string;
|
|
86
|
+
atom?: string;
|
|
87
|
+
json?: string;
|
|
88
|
+
};
|
|
89
|
+
/** A single tag or folder listing. */
|
|
90
|
+
export type TaxonomyTerm = {
|
|
91
|
+
kind: TaxonomyKind;
|
|
92
|
+
/** Raw grouping key (`"Guide/Intro"`, `"notes/sub"`). */
|
|
93
|
+
value: string;
|
|
94
|
+
/** Display title, `#value` for tags by default. */
|
|
95
|
+
title: string;
|
|
96
|
+
/** Site-local listing path, for example `/tags/guide/intro`. */
|
|
97
|
+
path: string;
|
|
98
|
+
/** Resolved entries, in manifest query order. */
|
|
99
|
+
entries: ContentManifestEntry[];
|
|
100
|
+
/** Related terms (tags only). */
|
|
101
|
+
related: TaxonomyRelatedTerm[];
|
|
102
|
+
/** Generated feed paths (site-local). */
|
|
103
|
+
feeds: TaxonomyFeedLinks;
|
|
104
|
+
/** Site-local feed file paths that the build emits. */
|
|
105
|
+
feedFiles: string[];
|
|
106
|
+
};
|
|
107
|
+
/** The complete tag and folder taxonomy for a manifest. */
|
|
108
|
+
export type TaxonomyIndex = {
|
|
109
|
+
tags: TaxonomyTerm[];
|
|
110
|
+
folders: TaxonomyTerm[];
|
|
111
|
+
};
|
|
112
|
+
/** JSON-safe projection of a {@link TaxonomyTerm}. */
|
|
113
|
+
export type TaxonomyTermData = {
|
|
114
|
+
kind: TaxonomyKind;
|
|
115
|
+
value: string;
|
|
116
|
+
title: string;
|
|
117
|
+
path: string;
|
|
118
|
+
permalink: string;
|
|
119
|
+
count: number;
|
|
120
|
+
entries: TaxonomyEntryReference[];
|
|
121
|
+
related: TaxonomyRelatedTerm[];
|
|
122
|
+
feeds: TaxonomyFeedLinks;
|
|
123
|
+
};
|
|
124
|
+
/** JSON-safe projection of a {@link TaxonomyIndex}. */
|
|
125
|
+
export type TaxonomyIndexData = {
|
|
126
|
+
tags: TaxonomyTermData[];
|
|
127
|
+
folders: TaxonomyTermData[];
|
|
128
|
+
};
|
|
129
|
+
/** A rendered taxonomy listing page fragment. */
|
|
130
|
+
export type TaxonomyPage = {
|
|
131
|
+
title: string;
|
|
132
|
+
/** `PostContent`-compatible frontmatter for the listing page. */
|
|
133
|
+
frontmatter: {
|
|
134
|
+
title: string;
|
|
135
|
+
};
|
|
136
|
+
html: string;
|
|
137
|
+
/** `entry.headTags`-compatible feed discovery links. */
|
|
138
|
+
headTags: TaxonomyHeadTag[];
|
|
139
|
+
};
|
|
140
|
+
/** Minimal head tag shape matching the Core `PluginHeadTag` contract. */
|
|
141
|
+
export type TaxonomyHeadTag = {
|
|
142
|
+
tag: "link";
|
|
143
|
+
attrs: Record<string, string>;
|
|
144
|
+
};
|
|
145
|
+
export type { SeoMetadata };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a site-local path against the configured site `baseUrl`. Mirrors the
|
|
4
|
+
* SEO plugin's absolute-URL convention so taxonomy feeds and metadata match the
|
|
5
|
+
* rest of the site without depending on the SEO package.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildTaxonomyAbsoluteUrl(config: ResolvedRiebeckiteConfig, pathOrUrl: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function escapeTaxonomyXml(value: string): string;
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@riebeckite/plugin-taxonomy",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"source": "./index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./style.css": "./style.css"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@riebeckite/core": "0.0.6"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"tsx": "^4.22.4"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md",
|
|
29
|
+
"README_ja.md",
|
|
30
|
+
"dist",
|
|
31
|
+
"style.css"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Rerurate514/riebeckite.git",
|
|
36
|
+
"directory": "packages/plugins/taxonomy"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/Rerurate514/riebeckite#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/Rerurate514/riebeckite/issues"
|
|
41
|
+
},
|
|
42
|
+
"description": "Build-time tag and folder taxonomy data, per-term feeds, and SEO for Riebeckite.",
|
|
43
|
+
"keywords": [
|
|
44
|
+
"riebeckite",
|
|
45
|
+
"plugin",
|
|
46
|
+
"taxonomy",
|
|
47
|
+
"tags",
|
|
48
|
+
"feeds"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
52
|
+
},
|
|
53
|
+
"sideEffects": [
|
|
54
|
+
"./style.css"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "node ../../../scripts/build_package.mjs",
|
|
58
|
+
"test": "node --import tsx --test \"test/*.test.ts\""
|
|
59
|
+
}
|
|
60
|
+
}
|
package/style.css
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
.rr-taxonomy {
|
|
2
|
+
--rr-taxonomy-ink: var(--rb-color-ink, #1a1a1a);
|
|
3
|
+
--rr-taxonomy-muted: var(--rb-color-muted, #69645c);
|
|
4
|
+
--rr-taxonomy-rule: color-mix(
|
|
5
|
+
in srgb,
|
|
6
|
+
var(--rr-taxonomy-ink) 14%,
|
|
7
|
+
transparent
|
|
8
|
+
);
|
|
9
|
+
--rr-taxonomy-accent: var(--rb-color-accent, #b5460f);
|
|
10
|
+
|
|
11
|
+
color: var(--rr-taxonomy-ink);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.rr-taxonomy__title {
|
|
15
|
+
margin: 0 0 0.5rem;
|
|
16
|
+
font-size: 1.75rem;
|
|
17
|
+
line-height: 1.2;
|
|
18
|
+
letter-spacing: -0.01em;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rr-taxonomy__count {
|
|
22
|
+
margin: 0 0 1.25rem;
|
|
23
|
+
color: var(--rr-taxonomy-muted);
|
|
24
|
+
font-size: 0.875rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.rr-taxonomy__feeds {
|
|
28
|
+
display: flex;
|
|
29
|
+
gap: 0.75rem;
|
|
30
|
+
margin-bottom: 1.25rem;
|
|
31
|
+
font-size: 0.8125rem;
|
|
32
|
+
text-transform: uppercase;
|
|
33
|
+
letter-spacing: 0.06em;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.rr-taxonomy__feed {
|
|
37
|
+
color: var(--rr-taxonomy-accent);
|
|
38
|
+
text-decoration: none;
|
|
39
|
+
border-bottom: 1px solid var(--rr-taxonomy-rule);
|
|
40
|
+
padding-bottom: 0.1em;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.rr-taxonomy__feed:hover,
|
|
44
|
+
.rr-taxonomy__feed:focus-visible {
|
|
45
|
+
border-bottom-color: currentColor;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.rr-taxonomy__list {
|
|
49
|
+
display: grid;
|
|
50
|
+
gap: 0.5rem;
|
|
51
|
+
margin: 0;
|
|
52
|
+
padding: 0;
|
|
53
|
+
list-style: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rr-taxonomy__item {
|
|
57
|
+
margin: 0;
|
|
58
|
+
padding-bottom: 0.5rem;
|
|
59
|
+
border-bottom: 1px solid var(--rr-taxonomy-rule);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.rr-taxonomy__link {
|
|
63
|
+
color: inherit;
|
|
64
|
+
text-decoration: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.rr-taxonomy__link:hover,
|
|
68
|
+
.rr-taxonomy__link:focus-visible {
|
|
69
|
+
color: var(--rr-taxonomy-accent);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.rr-taxonomy__related {
|
|
73
|
+
margin-top: 2rem;
|
|
74
|
+
padding-top: 1rem;
|
|
75
|
+
border-top: 1px solid var(--rr-taxonomy-rule);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.rr-taxonomy__related ul {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-wrap: wrap;
|
|
81
|
+
gap: 0.5rem;
|
|
82
|
+
margin: 0;
|
|
83
|
+
padding: 0;
|
|
84
|
+
list-style: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.rr-taxonomy__related-link {
|
|
88
|
+
display: inline-block;
|
|
89
|
+
padding: 0.15rem 0.5rem;
|
|
90
|
+
color: var(--rr-taxonomy-ink);
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
border: 1px solid var(--rr-taxonomy-rule);
|
|
93
|
+
border-radius: 999px;
|
|
94
|
+
font-size: 0.8125rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.rr-taxonomy__related-link:hover,
|
|
98
|
+
.rr-taxonomy__related-link:focus-visible {
|
|
99
|
+
border-color: var(--rr-taxonomy-accent);
|
|
100
|
+
color: var(--rr-taxonomy-accent);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
html[data-theme="dark"] .rr-taxonomy,
|
|
104
|
+
html.dark .rr-taxonomy {
|
|
105
|
+
--rr-taxonomy-ink: var(--rb-color-paper, #ffffff);
|
|
106
|
+
--rr-taxonomy-muted: var(--rb-color-muted, #a8a29a);
|
|
107
|
+
}
|