vimd 0.1.0
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 +21 -0
- package/README.md +270 -0
- package/dist/cli/commands/build.d.ts +7 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +54 -0
- package/dist/cli/commands/config.d.ts +6 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +117 -0
- package/dist/cli/commands/dev.d.ts +8 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +95 -0
- package/dist/cli/commands/theme.d.ts +2 -0
- package/dist/cli/commands/theme.d.ts.map +1 -0
- package/dist/cli/commands/theme.js +46 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +50 -0
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +19 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +78 -0
- package/dist/config/types.d.ts +47 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +4 -0
- package/dist/config/validator.d.ts +11 -0
- package/dist/config/validator.d.ts.map +1 -0
- package/dist/config/validator.js +38 -0
- package/dist/core/converter.d.ts +10 -0
- package/dist/core/converter.d.ts.map +1 -0
- package/dist/core/converter.js +81 -0
- package/dist/core/pandoc-detector.d.ts +8 -0
- package/dist/core/pandoc-detector.d.ts.map +1 -0
- package/dist/core/pandoc-detector.js +70 -0
- package/dist/core/server.d.ts +11 -0
- package/dist/core/server.d.ts.map +1 -0
- package/dist/core/server.js +57 -0
- package/dist/core/watcher.d.ts +16 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +44 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/templates/.gitkeep +0 -0
- package/dist/templates/default.html +22 -0
- package/dist/templates/standalone.html +31 -0
- package/dist/themes/index.d.ts +7 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +29 -0
- package/dist/themes/registry.d.ts +4 -0
- package/dist/themes/registry.d.ts.map +1 -0
- package/dist/themes/registry.js +40 -0
- package/dist/themes/styles/.gitkeep +0 -0
- package/dist/themes/styles/academic.css +68 -0
- package/dist/themes/styles/dark.css +54 -0
- package/dist/themes/styles/github.css +1 -0
- package/dist/themes/styles/minimal.css +35 -0
- package/dist/themes/styles/technical.css +84 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +16 -0
- package/dist/utils/os-detector.d.ts +8 -0
- package/dist/utils/os-detector.d.ts.map +1 -0
- package/dist/utils/os-detector.js +34 -0
- package/dist/utils/path-resolver.d.ts +7 -0
- package/dist/utils/path-resolver.d.ts.map +1 -0
- package/dist/utils/path-resolver.js +26 -0
- package/dist/utils/process-manager.d.ts +12 -0
- package/dist/utils/process-manager.d.ts.map +1 -0
- package/dist/utils/process-manager.js +35 -0
- package/package.json +75 -0
- package/templates/.gitkeep +0 -0
- package/templates/default.html +22 -0
- package/templates/standalone.html +31 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { devCommand } from './commands/dev.js';
|
|
4
|
+
import { buildCommand } from './commands/build.js';
|
|
5
|
+
import { themeCommand } from './commands/theme.js';
|
|
6
|
+
import { configCommand } from './commands/config.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name('vimd')
|
|
10
|
+
.description('Real-time Markdown preview tool (view markdown)')
|
|
11
|
+
.version('0.1.0');
|
|
12
|
+
// vimd dev <file>
|
|
13
|
+
program
|
|
14
|
+
.command('dev <file>')
|
|
15
|
+
.description('Start live preview server')
|
|
16
|
+
.option('-p, --port <port>', 'Port number', '8080')
|
|
17
|
+
.option('-t, --theme <theme>', 'Theme name')
|
|
18
|
+
.option('--no-open', 'Do not open browser automatically')
|
|
19
|
+
.action(devCommand);
|
|
20
|
+
// vimd build <file>
|
|
21
|
+
program
|
|
22
|
+
.command('build <file>')
|
|
23
|
+
.description('Build static HTML file')
|
|
24
|
+
.option('-o, --output <path>', 'Output file path')
|
|
25
|
+
.option('-t, --theme <theme>', 'Theme name')
|
|
26
|
+
.action(buildCommand);
|
|
27
|
+
// vimd theme
|
|
28
|
+
program
|
|
29
|
+
.command('theme')
|
|
30
|
+
.description('Change theme interactively')
|
|
31
|
+
.action(themeCommand);
|
|
32
|
+
// vimd config
|
|
33
|
+
program
|
|
34
|
+
.command('config')
|
|
35
|
+
.description('Edit configuration interactively')
|
|
36
|
+
.option('-l, --list', 'List current configuration')
|
|
37
|
+
.action(configCommand);
|
|
38
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"AASA,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA+ClD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/cli/setup.ts
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { ConfigLoader } from '../config/loader.js';
|
|
4
|
+
import { DEFAULT_CONFIG } from '../config/defaults.js';
|
|
5
|
+
import { ThemeManager } from '../themes/index.js';
|
|
6
|
+
import { Logger } from '../utils/logger.js';
|
|
7
|
+
import { PathResolver } from '../utils/path-resolver.js';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
export async function setupCommand() {
|
|
10
|
+
console.log('\nWelcome to vimd!\n');
|
|
11
|
+
const themes = ThemeManager.list();
|
|
12
|
+
const answers = await inquirer.prompt([
|
|
13
|
+
{
|
|
14
|
+
type: 'list',
|
|
15
|
+
name: 'theme',
|
|
16
|
+
message: 'Select a theme:',
|
|
17
|
+
choices: themes.map((t) => ({
|
|
18
|
+
name: `${t.displayName} - ${t.description}`,
|
|
19
|
+
value: t.name,
|
|
20
|
+
})),
|
|
21
|
+
default: 'github',
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
const config = {
|
|
25
|
+
...DEFAULT_CONFIG,
|
|
26
|
+
theme: answers.theme,
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
const configPath = PathResolver.getConfigPath();
|
|
30
|
+
const configDir = PathResolver.getConfigDir();
|
|
31
|
+
// Create configuration directory
|
|
32
|
+
Logger.info(`Creating configuration directory: ${configDir}`);
|
|
33
|
+
await fs.ensureDir(configDir);
|
|
34
|
+
// Save configuration
|
|
35
|
+
Logger.info(`Saving configuration: ${configPath}`);
|
|
36
|
+
await ConfigLoader.save(config, configPath);
|
|
37
|
+
Logger.success('\nSetup complete!\n');
|
|
38
|
+
console.log('Get started:');
|
|
39
|
+
console.log(' vimd dev README.md - Start preview');
|
|
40
|
+
console.log(' vimd theme - Change theme');
|
|
41
|
+
console.log(' vimd config - Advanced settings\n');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
Logger.error('Setup failed');
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
Logger.error(error.message);
|
|
47
|
+
}
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,eAAO,MAAM,cAAc,EAAE,UAkB5B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DEFAULT_CONFIG = {
|
|
2
|
+
theme: 'github',
|
|
3
|
+
port: 8080,
|
|
4
|
+
host: 'localhost',
|
|
5
|
+
open: true,
|
|
6
|
+
pandoc: {
|
|
7
|
+
standalone: true,
|
|
8
|
+
toc: false,
|
|
9
|
+
tocDepth: 3,
|
|
10
|
+
},
|
|
11
|
+
watch: {
|
|
12
|
+
ignored: ['node_modules/**', '.git/**', 'dist/**'],
|
|
13
|
+
debounce: 500,
|
|
14
|
+
},
|
|
15
|
+
build: {
|
|
16
|
+
inlineCSS: false,
|
|
17
|
+
standalone: true,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { VimdConfig } from './types.js';
|
|
2
|
+
export declare class ConfigLoader {
|
|
3
|
+
static merge(partial: Partial<VimdConfig>): VimdConfig;
|
|
4
|
+
static save(config: VimdConfig, configPath?: string): Promise<void>;
|
|
5
|
+
static loadGlobal(configPath?: string): Promise<VimdConfig>;
|
|
6
|
+
private static generateConfigFile;
|
|
7
|
+
private static parseConfigFile;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxC,qBAAa,YAAY;IACvB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU;WAyBzC,IAAI,CACf,MAAM,EAAE,UAAU,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;WAYH,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBjE,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAOjC,OAAO,CAAC,MAAM,CAAC,eAAe;CAa/B"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { DEFAULT_CONFIG } from './defaults.js';
|
|
2
|
+
import { PathResolver } from '../utils/path-resolver.js';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
export class ConfigLoader {
|
|
6
|
+
static merge(partial) {
|
|
7
|
+
return {
|
|
8
|
+
...DEFAULT_CONFIG,
|
|
9
|
+
...partial,
|
|
10
|
+
pandoc: partial.pandoc
|
|
11
|
+
? {
|
|
12
|
+
...DEFAULT_CONFIG.pandoc,
|
|
13
|
+
...partial.pandoc,
|
|
14
|
+
}
|
|
15
|
+
: DEFAULT_CONFIG.pandoc,
|
|
16
|
+
watch: partial.watch
|
|
17
|
+
? {
|
|
18
|
+
...DEFAULT_CONFIG.watch,
|
|
19
|
+
...partial.watch,
|
|
20
|
+
}
|
|
21
|
+
: DEFAULT_CONFIG.watch,
|
|
22
|
+
build: partial.build
|
|
23
|
+
? {
|
|
24
|
+
...DEFAULT_CONFIG.build,
|
|
25
|
+
...partial.build,
|
|
26
|
+
}
|
|
27
|
+
: DEFAULT_CONFIG.build,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
static async save(config, configPath) {
|
|
31
|
+
const targetPath = configPath || PathResolver.getConfigPath();
|
|
32
|
+
const configDir = path.dirname(targetPath);
|
|
33
|
+
// ディレクトリ作成
|
|
34
|
+
await fs.ensureDir(configDir);
|
|
35
|
+
// TypeScript設定ファイルとして出力
|
|
36
|
+
const content = this.generateConfigFile(config);
|
|
37
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
static async loadGlobal(configPath) {
|
|
40
|
+
const targetPath = configPath || PathResolver.getConfigPath();
|
|
41
|
+
// ファイルが存在しない場合はデフォルト設定を返す
|
|
42
|
+
if (!(await fs.pathExists(targetPath))) {
|
|
43
|
+
return DEFAULT_CONFIG;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
// TypeScript設定ファイルを動的にインポート
|
|
47
|
+
// 注: 実際の実装では、tsx等でTypeScriptファイルを実行する必要がある
|
|
48
|
+
// 今回は簡易的にJSONとしてパース
|
|
49
|
+
const content = await fs.readFile(targetPath, 'utf-8');
|
|
50
|
+
const config = this.parseConfigFile(content);
|
|
51
|
+
return this.merge(config);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('Failed to load config file:', error);
|
|
55
|
+
return DEFAULT_CONFIG;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
static generateConfigFile(config) {
|
|
59
|
+
return `import { defineConfig } from 'vimd';
|
|
60
|
+
|
|
61
|
+
export default defineConfig(${JSON.stringify(config, null, 2)});
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
static parseConfigFile(content) {
|
|
65
|
+
// 簡易的なパース (実際はtsx等でTypeScriptを実行)
|
|
66
|
+
// JSON部分を抽出
|
|
67
|
+
const match = content.match(/defineConfig\(([\s\S]*)\);/);
|
|
68
|
+
if (match) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(match[1]);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface VimdConfig {
|
|
2
|
+
theme: 'github' | 'minimal' | 'dark' | 'academic' | 'technical';
|
|
3
|
+
port: number;
|
|
4
|
+
host: string;
|
|
5
|
+
open: boolean;
|
|
6
|
+
css?: string;
|
|
7
|
+
template?: string;
|
|
8
|
+
pandoc: PandocConfig;
|
|
9
|
+
watch: WatchConfig;
|
|
10
|
+
build?: BuildConfig;
|
|
11
|
+
}
|
|
12
|
+
export interface PandocConfig {
|
|
13
|
+
standalone: boolean;
|
|
14
|
+
toc: boolean;
|
|
15
|
+
tocDepth?: number;
|
|
16
|
+
highlightStyle?: string;
|
|
17
|
+
metadata?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface WatchConfig {
|
|
20
|
+
ignored: string[];
|
|
21
|
+
debounce: number;
|
|
22
|
+
}
|
|
23
|
+
export interface BuildConfig {
|
|
24
|
+
output?: string;
|
|
25
|
+
inlineCSS: boolean;
|
|
26
|
+
standalone: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ThemeInfo {
|
|
29
|
+
name: string;
|
|
30
|
+
displayName: string;
|
|
31
|
+
description: string;
|
|
32
|
+
cssPath: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ServerConfig {
|
|
35
|
+
port: number;
|
|
36
|
+
host: string;
|
|
37
|
+
open: boolean;
|
|
38
|
+
root: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ConverterConfig {
|
|
41
|
+
theme: string;
|
|
42
|
+
pandocOptions: PandocConfig;
|
|
43
|
+
customCSS?: string;
|
|
44
|
+
template?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function defineConfig(config: Partial<VimdConfig>): VimdConfig;
|
|
47
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAEpE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { VimdConfig } from './types.js';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare class ConfigValidator {
|
|
7
|
+
static validatePort(port: number): boolean;
|
|
8
|
+
static validateTheme(theme: string): boolean;
|
|
9
|
+
static validate(config: VimdConfig): ValidationResult;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/config/validator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAID,qBAAa,eAAe;IAC1B,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1C,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI5C,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB;CAuCtD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const VALID_THEMES = ['github', 'minimal', 'dark', 'academic', 'technical'];
|
|
2
|
+
export class ConfigValidator {
|
|
3
|
+
static validatePort(port) {
|
|
4
|
+
return Number.isInteger(port) && port > 0 && port <= 65535;
|
|
5
|
+
}
|
|
6
|
+
static validateTheme(theme) {
|
|
7
|
+
return VALID_THEMES.includes(theme);
|
|
8
|
+
}
|
|
9
|
+
static validate(config) {
|
|
10
|
+
const errors = [];
|
|
11
|
+
// Port validation
|
|
12
|
+
if (!this.validatePort(config.port)) {
|
|
13
|
+
errors.push(`Invalid port number: ${config.port}`);
|
|
14
|
+
}
|
|
15
|
+
// Theme validation
|
|
16
|
+
if (!this.validateTheme(config.theme)) {
|
|
17
|
+
errors.push(`Invalid theme: ${config.theme}`);
|
|
18
|
+
}
|
|
19
|
+
// Host validation
|
|
20
|
+
if (!config.host || config.host.trim() === '') {
|
|
21
|
+
errors.push('Host cannot be empty');
|
|
22
|
+
}
|
|
23
|
+
// Pandoc validation
|
|
24
|
+
if (config.pandoc.tocDepth !== undefined) {
|
|
25
|
+
if (config.pandoc.tocDepth < 1 || config.pandoc.tocDepth > 6) {
|
|
26
|
+
errors.push(`Invalid tocDepth: ${config.pandoc.tocDepth} (must be 1-6)`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Watch debounce validation
|
|
30
|
+
if (config.watch.debounce < 0) {
|
|
31
|
+
errors.push(`Invalid debounce: ${config.watch.debounce} (must be >= 0)`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
valid: errors.length === 0,
|
|
35
|
+
errors,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConverterConfig } from '../config/types.js';
|
|
2
|
+
export declare class MarkdownConverter {
|
|
3
|
+
private config;
|
|
4
|
+
constructor(config: ConverterConfig);
|
|
5
|
+
convert(markdownPath: string): Promise<string>;
|
|
6
|
+
convertWithTemplate(markdownPath: string): Promise<string>;
|
|
7
|
+
writeHTML(html: string, outputPath: string): Promise<void>;
|
|
8
|
+
private buildPandocArgs;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=converter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../src/core/converter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AASrD,qBAAa,iBAAiB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IAErC,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB9C,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8B1D,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhE,OAAO,CAAC,eAAe;CA+BxB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/core/converter.ts
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { ThemeManager } from '../themes/index.js';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
export class MarkdownConverter {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
async convert(markdownPath) {
|
|
14
|
+
const pandocArgs = this.buildPandocArgs();
|
|
15
|
+
const command = `pandoc ${pandocArgs.join(' ')} "${markdownPath}"`;
|
|
16
|
+
try {
|
|
17
|
+
const html = execSync(command, {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
20
|
+
});
|
|
21
|
+
return html;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
25
|
+
throw new Error(`Failed to convert markdown: ${errorMessage}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async convertWithTemplate(markdownPath) {
|
|
29
|
+
const contentHtml = await this.convert(markdownPath);
|
|
30
|
+
const themeCSS = await ThemeManager.getCSS(this.config.theme);
|
|
31
|
+
let customCSS = '';
|
|
32
|
+
if (this.config.customCSS) {
|
|
33
|
+
customCSS = await ThemeManager.loadCustomCSS(this.config.customCSS);
|
|
34
|
+
}
|
|
35
|
+
const templatePath = this.config.template
|
|
36
|
+
? this.config.template
|
|
37
|
+
: path.join(__dirname, '../../templates/default.html');
|
|
38
|
+
const template = await fs.readFile(templatePath, 'utf-8');
|
|
39
|
+
// Simple template replacement
|
|
40
|
+
let html = template
|
|
41
|
+
.replace('{{title}}', path.basename(markdownPath, '.md'))
|
|
42
|
+
.replace('{{theme_css}}', themeCSS)
|
|
43
|
+
.replace('{{content}}', contentHtml);
|
|
44
|
+
if (customCSS) {
|
|
45
|
+
html = html.replace('{{custom_css}}', customCSS);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
html = html.replace(/\{\{#if custom_css\}\}[\s\S]*?\{\{\/if\}\}/g, '');
|
|
49
|
+
}
|
|
50
|
+
return html;
|
|
51
|
+
}
|
|
52
|
+
async writeHTML(html, outputPath) {
|
|
53
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
54
|
+
await fs.writeFile(outputPath, html, 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
buildPandocArgs() {
|
|
57
|
+
const args = [];
|
|
58
|
+
// Basic options
|
|
59
|
+
args.push('--from=markdown');
|
|
60
|
+
args.push('--to=html');
|
|
61
|
+
if (this.config.pandocOptions.standalone) {
|
|
62
|
+
args.push('--standalone');
|
|
63
|
+
}
|
|
64
|
+
if (this.config.pandocOptions.toc) {
|
|
65
|
+
args.push('--toc');
|
|
66
|
+
if (this.config.pandocOptions.tocDepth) {
|
|
67
|
+
args.push(`--toc-depth=${this.config.pandocOptions.tocDepth}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (this.config.pandocOptions.highlightStyle) {
|
|
71
|
+
args.push(`--highlight-style=${this.config.pandocOptions.highlightStyle}`);
|
|
72
|
+
}
|
|
73
|
+
// Metadata
|
|
74
|
+
if (this.config.pandocOptions.metadata) {
|
|
75
|
+
Object.entries(this.config.pandocOptions.metadata).forEach(([key, value]) => {
|
|
76
|
+
args.push(`--metadata=${key}:"${value}"`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return args;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { OSType } from '../utils/os-detector.js';
|
|
2
|
+
export declare class PandocDetector {
|
|
3
|
+
static check(): boolean;
|
|
4
|
+
static detectOS(): OSType;
|
|
5
|
+
static showInstallGuide(os: OSType): void;
|
|
6
|
+
static ensureInstalled(): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=pandoc-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pandoc-detector.d.ts","sourceRoot":"","sources":["../../src/core/pandoc-detector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEjD,qBAAa,cAAc;IACzB,MAAM,CAAC,KAAK,IAAI,OAAO;IASvB,MAAM,CAAC,QAAQ,IAAI,MAAM;IAczB,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IA4CzC,MAAM,CAAC,eAAe,IAAI,IAAI;CAQ/B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/core/pandoc-detector.ts
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
export class PandocDetector {
|
|
4
|
+
static check() {
|
|
5
|
+
try {
|
|
6
|
+
execSync('pandoc --version', { stdio: 'pipe' });
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
static detectOS() {
|
|
14
|
+
switch (process.platform) {
|
|
15
|
+
case 'darwin':
|
|
16
|
+
return 'macos';
|
|
17
|
+
case 'win32':
|
|
18
|
+
return 'windows';
|
|
19
|
+
case 'linux':
|
|
20
|
+
// Simplified: default to debian-based
|
|
21
|
+
return 'linux-debian';
|
|
22
|
+
default:
|
|
23
|
+
return 'linux-debian';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static showInstallGuide(os) {
|
|
27
|
+
console.error('⚠️ pandoc not found');
|
|
28
|
+
console.error('');
|
|
29
|
+
console.error('vimd requires pandoc to convert Markdown to HTML.');
|
|
30
|
+
console.error('Please install pandoc manually:');
|
|
31
|
+
console.error('');
|
|
32
|
+
switch (os) {
|
|
33
|
+
case 'macos':
|
|
34
|
+
console.error(' macOS (Homebrew):');
|
|
35
|
+
console.error(' brew install pandoc');
|
|
36
|
+
console.error('');
|
|
37
|
+
console.error(' macOS (Official installer):');
|
|
38
|
+
console.error(' https://github.com/jgm/pandoc/releases');
|
|
39
|
+
break;
|
|
40
|
+
case 'linux-debian':
|
|
41
|
+
console.error(' Debian/Ubuntu:');
|
|
42
|
+
console.error(' sudo apt-get update');
|
|
43
|
+
console.error(' sudo apt-get install pandoc');
|
|
44
|
+
break;
|
|
45
|
+
case 'linux-redhat':
|
|
46
|
+
console.error(' RedHat/CentOS/Fedora:');
|
|
47
|
+
console.error(' sudo yum install pandoc');
|
|
48
|
+
break;
|
|
49
|
+
case 'windows':
|
|
50
|
+
console.error(' Windows (Chocolatey):');
|
|
51
|
+
console.error(' choco install pandoc');
|
|
52
|
+
console.error('');
|
|
53
|
+
console.error(' Windows (Official installer):');
|
|
54
|
+
console.error(' https://github.com/jgm/pandoc/releases');
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
console.error('');
|
|
58
|
+
console.error('For more installation options:');
|
|
59
|
+
console.error(' https://pandoc.org/installing.html');
|
|
60
|
+
console.error('');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
static ensureInstalled() {
|
|
64
|
+
if (this.check()) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const os = this.detectOS();
|
|
68
|
+
this.showInstallGuide(os);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ServerConfig } from '../config/types.js';
|
|
2
|
+
export declare class LiveServer {
|
|
3
|
+
private config;
|
|
4
|
+
private running;
|
|
5
|
+
constructor(config: ServerConfig);
|
|
6
|
+
start(htmlPath: string): Promise<void>;
|
|
7
|
+
stop(): Promise<void>;
|
|
8
|
+
openBrowser(url: string): Promise<void>;
|
|
9
|
+
getURL(): string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,qBAAa,UAAU;IAGT,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,OAAO,CAAS;gBAEJ,MAAM,EAAE,YAAY;IAElC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAUrB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C,MAAM,IAAI,MAAM;CAGjB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/core/server.ts
|
|
2
|
+
import * as liveServer from 'live-server';
|
|
3
|
+
import { Logger } from '../utils/logger.js';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
export class LiveServer {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.running = false;
|
|
10
|
+
}
|
|
11
|
+
async start(htmlPath) {
|
|
12
|
+
const root = path.dirname(htmlPath);
|
|
13
|
+
const file = path.basename(htmlPath);
|
|
14
|
+
const params = {
|
|
15
|
+
port: this.config.port,
|
|
16
|
+
host: this.config.host,
|
|
17
|
+
root: root,
|
|
18
|
+
file: file,
|
|
19
|
+
open: false, // manually open
|
|
20
|
+
wait: 200,
|
|
21
|
+
logLevel: 0, // silent
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
liveServer.start(params);
|
|
25
|
+
this.running = true;
|
|
26
|
+
const url = `http://${this.config.host}:${this.config.port}`;
|
|
27
|
+
Logger.success(`Server started at ${url}`);
|
|
28
|
+
if (this.config.open) {
|
|
29
|
+
await this.openBrowser(url);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
34
|
+
throw new Error(`Failed to start server: ${errorMessage}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async stop() {
|
|
38
|
+
if (!this.running) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
liveServer.shutdown();
|
|
42
|
+
this.running = false;
|
|
43
|
+
Logger.info('Server stopped');
|
|
44
|
+
}
|
|
45
|
+
async openBrowser(url) {
|
|
46
|
+
try {
|
|
47
|
+
await open(url);
|
|
48
|
+
Logger.info('Browser opened');
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
Logger.warn('Failed to open browser automatically');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
getURL() {
|
|
55
|
+
return `http://${this.config.host}:${this.config.port}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { WatchConfig } from '../config/types.js';
|
|
2
|
+
type ChangeCallback = (path: string) => void;
|
|
3
|
+
export declare class FileWatcher {
|
|
4
|
+
private filePath;
|
|
5
|
+
private config;
|
|
6
|
+
private watcher;
|
|
7
|
+
private callbacks;
|
|
8
|
+
private debounceTimer;
|
|
9
|
+
constructor(filePath: string, config: WatchConfig);
|
|
10
|
+
onChange(callback: ChangeCallback): void;
|
|
11
|
+
start(): void;
|
|
12
|
+
stop(): Promise<void>;
|
|
13
|
+
private handleChange;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/core/watcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE7C,qBAAa,WAAW;IAMpB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,aAAa,CAA+B;gBAG1C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW;IAG7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI;IAIxC,KAAK,IAAI,IAAI;IAYP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,OAAO,CAAC,YAAY;CAWrB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/core/watcher.ts
|
|
2
|
+
import chokidar from 'chokidar';
|
|
3
|
+
export class FileWatcher {
|
|
4
|
+
constructor(filePath, config) {
|
|
5
|
+
this.filePath = filePath;
|
|
6
|
+
this.config = config;
|
|
7
|
+
this.watcher = null;
|
|
8
|
+
this.callbacks = [];
|
|
9
|
+
this.debounceTimer = null;
|
|
10
|
+
}
|
|
11
|
+
onChange(callback) {
|
|
12
|
+
this.callbacks.push(callback);
|
|
13
|
+
}
|
|
14
|
+
start() {
|
|
15
|
+
this.watcher = chokidar.watch(this.filePath, {
|
|
16
|
+
ignored: this.config.ignored,
|
|
17
|
+
persistent: true,
|
|
18
|
+
ignoreInitial: true,
|
|
19
|
+
});
|
|
20
|
+
this.watcher.on('change', (path) => {
|
|
21
|
+
this.handleChange(path);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async stop() {
|
|
25
|
+
if (this.debounceTimer) {
|
|
26
|
+
clearTimeout(this.debounceTimer);
|
|
27
|
+
this.debounceTimer = null;
|
|
28
|
+
}
|
|
29
|
+
if (this.watcher) {
|
|
30
|
+
await this.watcher.close();
|
|
31
|
+
this.watcher = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
handleChange(path) {
|
|
35
|
+
// デバウンス処理
|
|
36
|
+
if (this.debounceTimer) {
|
|
37
|
+
clearTimeout(this.debounceTimer);
|
|
38
|
+
}
|
|
39
|
+
this.debounceTimer = setTimeout(() => {
|
|
40
|
+
this.callbacks.forEach((callback) => callback(path));
|
|
41
|
+
this.debounceTimer = null;
|
|
42
|
+
}, this.config.debounce);
|
|
43
|
+
}
|
|
44
|
+
}
|