mdorigin 0.4.1 → 0.5.1

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.
@@ -0,0 +1,45 @@
1
+ export interface SiteMessages {
2
+ 'search.toggle': string;
3
+ 'search.label': string;
4
+ 'search.placeholder': string;
5
+ 'search.go': string;
6
+ 'search.hint': string;
7
+ 'search.resultsEmpty': string;
8
+ 'search.searching': string;
9
+ 'search.failed': string;
10
+ 'search.failedWithStatus': string;
11
+ 'search.enterQuery': string;
12
+ 'search.initialHint': string;
13
+ 'search.untitled': string;
14
+ 'footer.editPage': string;
15
+ 'footer.markdownView': string;
16
+ 'footer.markdownViewAria': string;
17
+ 'languages.ariaLabel': string;
18
+ 'listing.browseSection': string;
19
+ 'listing.loadMore': string;
20
+ 'listing.loading': string;
21
+ 'listing.ariaLabel': string;
22
+ 'listing.emptyDirectory': string;
23
+ 'error.notFoundTitle': string;
24
+ 'error.notFoundBody': string;
25
+ }
26
+ export type SiteMessageKey = keyof SiteMessages;
27
+ export declare const DEFAULT_SITE_LOCALE = "en";
28
+ export declare function isKnownSiteLocale(locale: string): boolean;
29
+ /**
30
+ * Resolves the effective message set for a locale: built-in English catalog,
31
+ * overlaid with the built-in catalog for the locale (when available, unknown
32
+ * locales fall back to English), overlaid with user overrides.
33
+ */
34
+ export declare function resolveSiteMessages(locale: string | undefined, overrides?: Partial<SiteMessages>): SiteMessages;
35
+ export declare function formatSiteMessage(message: string, params: Record<string, string>): string;
36
+ /**
37
+ * Serializes messages for embedding inside an inline `<script>` element. `<`
38
+ * is escaped so that a message containing `</script>` cannot terminate the
39
+ * script block early.
40
+ */
41
+ export declare function serializeMessagesForScript(value: Record<string, unknown>): string;
42
+ export declare function validateSiteMessages(value: unknown): {
43
+ messages: Partial<SiteMessages>;
44
+ unknownKeys: string[];
45
+ };
@@ -0,0 +1,104 @@
1
+ export const DEFAULT_SITE_LOCALE = 'en';
2
+ /**
3
+ * Built-in message catalogs. Messages are trusted template fragments: they may
4
+ * contain inline HTML markup such as `<code>` and `{placeholder}` parameters.
5
+ */
6
+ const enMessages = {
7
+ 'search.toggle': 'Search',
8
+ 'search.label': 'Search site',
9
+ 'search.placeholder': 'Search docs and skills',
10
+ 'search.go': 'Go',
11
+ 'search.hint': 'Search is powered by <code>/api/search</code>.',
12
+ 'search.resultsEmpty': 'No results.',
13
+ 'search.searching': 'Searching...',
14
+ 'search.failed': 'Search failed.',
15
+ 'search.failedWithStatus': 'Search failed ({status}).',
16
+ 'search.enterQuery': 'Enter a search query.',
17
+ 'search.initialHint': 'Search docs, guides, and skills.',
18
+ 'search.untitled': 'Untitled',
19
+ 'footer.editPage': 'Edit this page',
20
+ 'footer.markdownView': 'MD View',
21
+ 'footer.markdownViewAria': 'View Markdown source',
22
+ 'languages.ariaLabel': 'Languages',
23
+ 'listing.browseSection': 'Browse this section.',
24
+ 'listing.loadMore': 'Load more',
25
+ 'listing.loading': 'Loading...',
26
+ 'listing.ariaLabel': 'Content listing',
27
+ 'listing.emptyDirectory': 'This directory is empty.',
28
+ 'error.notFoundTitle': 'Not Found',
29
+ 'error.notFoundBody': 'No page was published at <code>{path}</code>.',
30
+ };
31
+ const zhCnMessages = {
32
+ 'search.toggle': '搜索',
33
+ 'search.label': '搜索站点',
34
+ 'search.placeholder': '搜索文档与技能',
35
+ 'search.go': '搜索',
36
+ 'search.hint': '搜索由 <code>/api/search</code> 提供支持。',
37
+ 'search.resultsEmpty': '没有找到结果。',
38
+ 'search.searching': '搜索中...',
39
+ 'search.failed': '搜索失败。',
40
+ 'search.failedWithStatus': '搜索失败({status})。',
41
+ 'search.enterQuery': '请输入搜索关键词。',
42
+ 'search.initialHint': '搜索文档、指南与技能。',
43
+ 'search.untitled': '无标题',
44
+ 'footer.editPage': '编辑此页',
45
+ 'footer.markdownView': 'MD 视图',
46
+ 'footer.markdownViewAria': '查看 Markdown 源文件',
47
+ 'languages.ariaLabel': '语言',
48
+ 'listing.browseSection': '浏览此章节。',
49
+ 'listing.loadMore': '加载更多',
50
+ 'listing.loading': '加载中...',
51
+ 'listing.ariaLabel': '内容列表',
52
+ 'listing.emptyDirectory': '此目录为空。',
53
+ 'error.notFoundTitle': '未找到',
54
+ 'error.notFoundBody': '没有页面发布在 <code>{path}</code>。',
55
+ };
56
+ const messageCatalogs = {
57
+ en: enMessages,
58
+ 'zh-CN': zhCnMessages,
59
+ };
60
+ const catalogKeys = new Map(Object.keys(messageCatalogs).map((key) => [key.toLowerCase(), key]));
61
+ export function isKnownSiteLocale(locale) {
62
+ return messageCatalogs[locale] !== undefined;
63
+ }
64
+ /**
65
+ * Resolves the effective message set for a locale: built-in English catalog,
66
+ * overlaid with the built-in catalog for the locale (when available, unknown
67
+ * locales fall back to English), overlaid with user overrides.
68
+ */
69
+ export function resolveSiteMessages(locale, overrides) {
70
+ const catalogKey = locale === undefined ? undefined : catalogKeys.get(locale.toLowerCase());
71
+ const catalog = catalogKey === undefined ? undefined : messageCatalogs[catalogKey];
72
+ if (catalog === undefined || catalogKey === 'en') {
73
+ return { ...enMessages, ...(overrides ?? {}) };
74
+ }
75
+ return { ...enMessages, ...catalog, ...(overrides ?? {}) };
76
+ }
77
+ export function formatSiteMessage(message, params) {
78
+ return Object.entries(params).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, value), message);
79
+ }
80
+ /**
81
+ * Serializes messages for embedding inside an inline `<script>` element. `<`
82
+ * is escaped so that a message containing `</script>` cannot terminate the
83
+ * script block early.
84
+ */
85
+ export function serializeMessagesForScript(value) {
86
+ return JSON.stringify(value).replaceAll('<', '\\u003c');
87
+ }
88
+ export function validateSiteMessages(value) {
89
+ const messages = {};
90
+ const unknownKeys = [];
91
+ if (typeof value !== 'object' || value === null) {
92
+ return { messages, unknownKeys };
93
+ }
94
+ for (const [key, entry] of Object.entries(value)) {
95
+ if (!(key in enMessages)) {
96
+ unknownKeys.push(key);
97
+ continue;
98
+ }
99
+ if (typeof entry === 'string' && entry !== '') {
100
+ messages[key] = entry;
101
+ }
102
+ }
103
+ return { messages, unknownKeys };
104
+ }
@@ -3,13 +3,15 @@ export interface BuildIndexOptions {
3
3
  rootDir?: string;
4
4
  dir?: string;
5
5
  plugins?: MdoPlugin[];
6
+ /** Top-level directory names (locale content bases) kept out of managed indexes. */
7
+ excludedDirectories?: string[];
6
8
  }
7
9
  export interface BuildIndexResult {
8
10
  updatedFiles: string[];
9
11
  skippedDirectories: string[];
10
12
  }
11
13
  export declare function buildDirectoryIndexes(options: BuildIndexOptions): Promise<BuildIndexResult>;
12
- export declare function buildManagedIndexBlock(directoryPath: string, plugins?: MdoPlugin[]): Promise<string>;
14
+ export declare function buildManagedIndexBlock(directoryPath: string, plugins?: MdoPlugin[], excludedDirectories?: string[]): Promise<string>;
13
15
  export declare function upsertManagedIndexBlock(source: string, block: string, options?: {
14
16
  directoryPath?: string;
15
17
  }): string;
@@ -19,6 +19,7 @@ export async function buildDirectoryIndexes(options) {
19
19
  const updatedFile = await updateSingleDirectoryIndex(directoryPath, {
20
20
  createIfMissing: false,
21
21
  plugins: options.plugins ?? [],
22
+ excludedDirectories: options.excludedDirectories,
22
23
  });
23
24
  return {
24
25
  updatedFiles: updatedFile ? [updatedFile] : [],
@@ -33,6 +34,7 @@ export async function buildDirectoryIndexes(options) {
33
34
  const updatedFile = await updateSingleDirectoryIndex(directoryPath, {
34
35
  createIfMissing: false,
35
36
  plugins: options.plugins ?? [],
37
+ excludedDirectories: options.excludedDirectories,
36
38
  });
37
39
  if (updatedFile) {
38
40
  updatedFiles.push(updatedFile);
@@ -59,7 +61,7 @@ async function updateSingleDirectoryIndex(directoryPath, options) {
59
61
  const existingContent = indexFilePath
60
62
  ? await readFile(indexFilePath, 'utf8')
61
63
  : '';
62
- const block = await buildManagedIndexBlock(directoryPath, options.plugins);
64
+ const block = await buildManagedIndexBlock(directoryPath, options.plugins, options.excludedDirectories);
63
65
  const nextContent = upsertManagedIndexBlock(existingContent, block, {
64
66
  directoryPath,
65
67
  });
@@ -68,10 +70,11 @@ async function updateSingleDirectoryIndex(directoryPath, options) {
68
70
  }
69
71
  return targetFilePath;
70
72
  }
71
- export async function buildManagedIndexBlock(directoryPath, plugins = []) {
73
+ export async function buildManagedIndexBlock(directoryPath, plugins = [], excludedDirectories) {
72
74
  const entries = await readdir(directoryPath, { withFileTypes: true });
73
75
  const directories = [];
74
76
  const articles = [];
77
+ const excluded = new Set(excludedDirectories ?? []);
75
78
  for (const entry of entries) {
76
79
  if (isIgnoredContentName(entry.name)) {
77
80
  continue;
@@ -79,6 +82,9 @@ export async function buildManagedIndexBlock(directoryPath, plugins = []) {
79
82
  const fullPath = path.join(directoryPath, entry.name);
80
83
  const entryStats = await stat(fullPath);
81
84
  if (entryStats.isDirectory()) {
85
+ if (excluded.has(entry.name)) {
86
+ continue;
87
+ }
82
88
  if (!(await hasMeaningfulDirectoryContent(fullPath))) {
83
89
  continue;
84
90
  }
package/dist/search.js CHANGED
@@ -4,6 +4,7 @@ import { inferDirectoryContentType } from './core/content-type.js';
4
4
  import { getDirectoryIndexCandidates } from './core/directory-index.js';
5
5
  import { getDocumentSummary, getDocumentTitle, parseMarkdownDocument, stripMachineOnlyMarkdownComments, stripManagedIndexBlock, } from './core/markdown.js';
6
6
  import { isIgnoredContentName } from './core/content-store.js';
7
+ import { ensureTrailingSlash, trimLeadingSlash } from './core/site-url.js';
7
8
  const OVERVIEW_CONTENT_FILENAMES = new Set(['readme.md', 'index.md', 'skill.md']);
8
9
  export async function buildSearchBundle(options) {
9
10
  const buildModule = await loadIndexbindBuildModule();
@@ -276,12 +277,6 @@ function getCanonicalHtmlPathForContentPath(contentPath) {
276
277
  }
277
278
  return `/${contentPath.slice(0, -'.md'.length)}`;
278
279
  }
279
- function trimLeadingSlash(value) {
280
- return value.startsWith('/') ? value.slice(1) : value;
281
- }
282
- function ensureTrailingSlash(value) {
283
- return value.endsWith('/') ? value : `${value}/`;
284
- }
285
280
  async function pathExists(filePath) {
286
281
  try {
287
282
  await stat(filePath);
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "mdorigin",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "Markdown-first publishing for humans and agents.",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/jolestar/mdorigin.git"
8
+ "url": "git+https://github.com/holon-run/mdorigin.git"
9
9
  },
10
- "homepage": "https://mdorigin.jolestar.workers.dev",
10
+ "homepage": "https://mdorigin.holon.run",
11
11
  "bugs": {
12
- "url": "https://github.com/jolestar/mdorigin/issues"
12
+ "url": "https://github.com/holon-run/mdorigin/issues"
13
13
  },
14
14
  "keywords": [
15
15
  "markdown",