@fsegurai/marked-extended-comments 17.0.0-beta.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/README.custom.md +600 -0
- package/README.md +682 -0
- package/dist/index.cjs +498 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.esm.js +496 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/renderer.d.ts +27 -0
- package/dist/tokenizer.d.ts +18 -0
- package/dist/types.d.ts +147 -0
- package/dist/utils/constants.d.ts +29 -0
- package/dist/utils/helpers.d.ts +30 -0
- package/dist/utils/inject-styles.d.ts +4 -0
- package/package.json +53 -0
- package/src/global.d.ts +13 -0
- package/src/index.ts +103 -0
- package/src/renderer.ts +126 -0
- package/src/tokenizer.ts +161 -0
- package/src/types.ts +172 -0
- package/src/utils/constants.ts +115 -0
- package/src/utils/helpers.ts +138 -0
- package/src/utils/inject-styles.ts +15 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Token, Tokens } from 'marked';
|
|
2
|
+
/**
|
|
3
|
+
* Comment types
|
|
4
|
+
*/
|
|
5
|
+
export type CommentType = 'note' | 'question' | 'suggestion' | 'issue' | 'internal' | 'review' | 'todo';
|
|
6
|
+
/**
|
|
7
|
+
* Comment visibility levels
|
|
8
|
+
*/
|
|
9
|
+
export type CommentVisibility = 'public' | 'private' | 'dev-only' | 'editors-only';
|
|
10
|
+
/**
|
|
11
|
+
* Comment status
|
|
12
|
+
*/
|
|
13
|
+
export type CommentStatus = 'open' | 'resolved' | 'archived';
|
|
14
|
+
/**
|
|
15
|
+
* Metadata for a comment
|
|
16
|
+
*/
|
|
17
|
+
export interface CommentMeta {
|
|
18
|
+
author?: string;
|
|
19
|
+
date?: string;
|
|
20
|
+
type?: CommentType;
|
|
21
|
+
visibility?: CommentVisibility;
|
|
22
|
+
status?: CommentStatus;
|
|
23
|
+
priority?: 'low' | 'medium' | 'high';
|
|
24
|
+
tags?: string;
|
|
25
|
+
id?: string;
|
|
26
|
+
replyTo?: string;
|
|
27
|
+
resolved?: string;
|
|
28
|
+
className?: string;
|
|
29
|
+
[key: string]: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Token for inline comments
|
|
33
|
+
*/
|
|
34
|
+
export interface InlineCommentToken extends Tokens.Generic {
|
|
35
|
+
type: 'inlineComment';
|
|
36
|
+
raw: string;
|
|
37
|
+
text: string;
|
|
38
|
+
meta: CommentMeta;
|
|
39
|
+
tokens?: Token[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Token for block comments
|
|
43
|
+
*/
|
|
44
|
+
export interface BlockCommentToken extends Tokens.Generic {
|
|
45
|
+
type: 'blockComment';
|
|
46
|
+
raw: string;
|
|
47
|
+
text: string;
|
|
48
|
+
meta: CommentMeta;
|
|
49
|
+
tokens?: Token[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Options for the comment extension
|
|
53
|
+
*/
|
|
54
|
+
export interface CommentOptions {
|
|
55
|
+
/**
|
|
56
|
+
* CSS class prefix for comment elements
|
|
57
|
+
* @default 'marked-extended-comment'
|
|
58
|
+
*/
|
|
59
|
+
className?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Prefix for generating unique IDs
|
|
62
|
+
* @default 'comment'
|
|
63
|
+
*/
|
|
64
|
+
prefixId?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Show comments by default
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
showComments?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Enable inline comments
|
|
72
|
+
* @default true
|
|
73
|
+
*/
|
|
74
|
+
enableInlineComments?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Enable block comments
|
|
77
|
+
* @default true
|
|
78
|
+
*/
|
|
79
|
+
enableBlockComments?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Show comment metadata (author, date, etc.)
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
showMetadata?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Show resolved comments
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
showResolvedComments?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Default comment type
|
|
92
|
+
* @default 'note'
|
|
93
|
+
*/
|
|
94
|
+
defaultType?: CommentType;
|
|
95
|
+
/**
|
|
96
|
+
* Default visibility
|
|
97
|
+
* @default 'public'
|
|
98
|
+
*/
|
|
99
|
+
defaultVisibility?: CommentVisibility;
|
|
100
|
+
/**
|
|
101
|
+
* Filter comments by visibility
|
|
102
|
+
* @default []
|
|
103
|
+
*/
|
|
104
|
+
visibilityFilter?: CommentVisibility[];
|
|
105
|
+
/**
|
|
106
|
+
* Filter comments by type
|
|
107
|
+
* @default []
|
|
108
|
+
*/
|
|
109
|
+
typeFilter?: CommentType[];
|
|
110
|
+
/**
|
|
111
|
+
* Custom template for inline comments
|
|
112
|
+
*/
|
|
113
|
+
inlineTemplate?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Custom template for block comments
|
|
116
|
+
*/
|
|
117
|
+
blockTemplate?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Inject CSS styles
|
|
120
|
+
* @default true
|
|
121
|
+
*/
|
|
122
|
+
injectStyles?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Custom callback to modify comment tokens
|
|
125
|
+
*/
|
|
126
|
+
customizeToken?: (token: InlineCommentToken | BlockCommentToken) => void;
|
|
127
|
+
/**
|
|
128
|
+
* Callback when comment visibility changes
|
|
129
|
+
*/
|
|
130
|
+
onVisibilityChange?: (commentId: string, visible: boolean) => void;
|
|
131
|
+
/**
|
|
132
|
+
* Callback when comment is resolved/unresolved
|
|
133
|
+
*/
|
|
134
|
+
onStatusChange?: (commentId: string, status: CommentStatus) => void;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Renderer options for comments
|
|
138
|
+
*/
|
|
139
|
+
export interface CommentRendererOptions {
|
|
140
|
+
commentId: string;
|
|
141
|
+
meta: CommentMeta;
|
|
142
|
+
content: string;
|
|
143
|
+
isInline: boolean;
|
|
144
|
+
className: string;
|
|
145
|
+
showMetadata: boolean;
|
|
146
|
+
template?: string;
|
|
147
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CommentOptions } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Default options for the comment extension
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_OPTIONS: Required<Omit<CommentOptions, 'customizeToken' | 'onVisibilityChange' | 'onStatusChange' | 'inlineTemplate' | 'blockTemplate' | 'visibilityFilter' | 'typeFilter'>>;
|
|
6
|
+
/**
|
|
7
|
+
* Comment type SVG icons
|
|
8
|
+
*/
|
|
9
|
+
export declare const COMMENT_ICONS: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Comment type labels
|
|
12
|
+
*/
|
|
13
|
+
export declare const COMMENT_LABELS: Record<string, string>;
|
|
14
|
+
/**
|
|
15
|
+
* Default template for inline comments
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_INLINE_TEMPLATE = "\n<span class=\"{className} {className}-inline {className}-type-{type} {className}-status-{status} {visibilityClass}\" \n id=\"{commentId}\"\n data-comment-type=\"{type}\"\n data-comment-status=\"{status}\"\n data-comment-visibility=\"{visibility}\"\n data-comment-author=\"{author}\"\n data-comment-date=\"{date}\"\n role=\"note\"\n aria-label=\"Comment: {text}\"\n title=\"{tooltipText}\">\n <span class=\"{className}-content\">{content}</span>\n <span class=\"{className}-indicator\" aria-hidden=\"true\">{icon}</span>\n</span>\n";
|
|
18
|
+
/**
|
|
19
|
+
* Default template for block comments
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_BLOCK_TEMPLATE = "\n<aside class=\"{className} {className}-block {className}-type-{type} {className}-status-{status} {visibilityClass} {priorityClass}\"\n id=\"{commentId}\"\n data-comment-type=\"{type}\"\n data-comment-status=\"{status}\"\n data-comment-visibility=\"{visibility}\"\n data-comment-author=\"{author}\"\n data-comment-date=\"{date}\"\n role=\"complementary\"\n aria-label=\"Comment by {author}\">\n <div class=\"{className}-header\">\n <span class=\"{className}-icon\" aria-hidden=\"true\">{icon}</span>\n <span class=\"{className}-type-label\">{typeLabel}</span>\n {metadata}\n </div>\n <div class=\"{className}-body\">\n {content}\n </div>\n</aside>\n";
|
|
22
|
+
/**
|
|
23
|
+
* Metadata template
|
|
24
|
+
*/
|
|
25
|
+
export declare const METADATA_TEMPLATE = "\n<div class=\"{className}-metadata\">\n {authorInfo}\n {dateInfo}\n {tagsInfo}\n {priorityInfo}\n</div>\n";
|
|
26
|
+
/**
|
|
27
|
+
* Default structural styles for comments (minimal, layout-only)
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_STYLES = "\n.marked-extended-comment { position: relative; display: inline-block; }\n.marked-extended-comment-inline { display: inline; }\n.marked-extended-comment-indicator { position: absolute; top: -8px; right: -8px; font-size: 12px; line-height: 1; }\n.marked-extended-comment-block { display: block; margin: 1rem 0; }\n.marked-extended-comment-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }\n.marked-extended-comment-icon { line-height: 1; }\n.marked-extended-comment-body { margin: 0.5rem 0; }\n.marked-extended-comment-metadata { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.5rem; }\n.marked-extended-comment-metadata > span { display: flex; align-items: center; gap: 0.25rem; }\n.marked-extended-comment.hidden { display: none; }\n";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare function generateUniqueId(prefix?: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* Reset the comment counter (useful for testing)
|
|
4
|
+
*/
|
|
5
|
+
export declare function resetCommentCounter(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Parse property string like 'author="John" type="note"'
|
|
8
|
+
*/
|
|
9
|
+
export declare function parsePropString(propString: string): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Replace template placeholders with values
|
|
12
|
+
*/
|
|
13
|
+
export declare function replaceTemplatePlaceholders(template: string, values: Record<string, string>): string;
|
|
14
|
+
/**
|
|
15
|
+
* Format date string
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatDate(dateString?: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Check if comment should be shown based on visibility and filters
|
|
20
|
+
*/
|
|
21
|
+
export declare function shouldShowComment(visibility?: string, status?: string, type?: string, visibilityFilter?: string[], typeFilter?: string[], showResolved?: boolean): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Build metadata HTML for comments
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildMetadataHtml(author?: string, date?: string, tags?: string, priority?: string, className?: string): {
|
|
26
|
+
authorInfo: string;
|
|
27
|
+
dateInfo: string;
|
|
28
|
+
tagsInfo: string;
|
|
29
|
+
priorityInfo: string;
|
|
30
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fsegurai/marked-extended-comments",
|
|
3
|
+
"version": "17.0.0-beta.0",
|
|
4
|
+
"description": "Marked extension for collaborative comments and annotations",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.esm.js",
|
|
8
|
+
"browser": "./dist/index.umd.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.esm.js",
|
|
13
|
+
"require": "./dist/index.cjs",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/",
|
|
19
|
+
"src/"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"extensions",
|
|
23
|
+
"javascript",
|
|
24
|
+
"library",
|
|
25
|
+
"markdown",
|
|
26
|
+
"markedjs",
|
|
27
|
+
"marked-extended-comments",
|
|
28
|
+
"parser",
|
|
29
|
+
"syntax",
|
|
30
|
+
"typescript"
|
|
31
|
+
],
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "Fabián Segura",
|
|
34
|
+
"url": "https://www.fsegurai.com/"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"scripts": {
|
|
38
|
+
"prepare": "rollup -c rollup.config.js",
|
|
39
|
+
"test": "bun test",
|
|
40
|
+
"test:coverage": "bun test --coverage"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"marked": ">=17.0.0"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/fsegurai/marked-extensions.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/fsegurai/marked-extensions/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/fsegurai/marked-extensions#readme"
|
|
53
|
+
}
|
package/src/global.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Token } from 'marked';
|
|
2
|
+
import { ensureStyles } from './utils/inject-styles';
|
|
3
|
+
import { DEFAULT_OPTIONS, DEFAULT_STYLES } from './utils/constants';
|
|
4
|
+
import {
|
|
5
|
+
createBlockCommentRenderer,
|
|
6
|
+
createBlockCommentTokenizer,
|
|
7
|
+
createInlineCommentRenderer,
|
|
8
|
+
createInlineCommentTokenizer,
|
|
9
|
+
} from './tokenizer';
|
|
10
|
+
import type { BlockCommentToken, CommentOptions, InlineCommentToken } from './types';
|
|
11
|
+
|
|
12
|
+
// Export types for external use
|
|
13
|
+
export type {
|
|
14
|
+
CommentOptions,
|
|
15
|
+
InlineCommentToken,
|
|
16
|
+
BlockCommentToken,
|
|
17
|
+
CommentMeta,
|
|
18
|
+
CommentType,
|
|
19
|
+
CommentVisibility,
|
|
20
|
+
CommentStatus,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Marked Extended Comments extension
|
|
25
|
+
* Adds support for inline and block comments with rich metadata
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```markdown
|
|
29
|
+
* // Inline comment
|
|
30
|
+
* This text has :::comment{author="Alice" type="note"}a comment::: here.
|
|
31
|
+
*
|
|
32
|
+
* // Block comment
|
|
33
|
+
* ::::comment{author="Bob" type="review" status="open"}
|
|
34
|
+
* This section needs revision.
|
|
35
|
+
* ::::commentend
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param options - Configuration options
|
|
39
|
+
* @returns Marked extension object
|
|
40
|
+
*/
|
|
41
|
+
export default function markedExtendedComments(options: CommentOptions = {}) {
|
|
42
|
+
// Merge options with defaults
|
|
43
|
+
const config = { ...DEFAULT_OPTIONS, ...options } as Required<CommentOptions>;
|
|
44
|
+
|
|
45
|
+
// Inject styles if needed
|
|
46
|
+
if (config.injectStyles) {
|
|
47
|
+
ensureStyles('marked-extended-comments-styles', DEFAULT_STYLES);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Return the extension
|
|
51
|
+
return {
|
|
52
|
+
walkTokens(token: Token) {
|
|
53
|
+
// Handle inline comments
|
|
54
|
+
if (token.type === 'inlineComment') {
|
|
55
|
+
const commentToken = token as InlineCommentToken;
|
|
56
|
+
|
|
57
|
+
// Apply custom token modifications if configured
|
|
58
|
+
if (config.customizeToken && typeof config.customizeToken === 'function') {
|
|
59
|
+
config.customizeToken(commentToken);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Trigger visibility change callback
|
|
63
|
+
if (config.onVisibilityChange && typeof config.onVisibilityChange === 'function') {
|
|
64
|
+
const commentId = commentToken.meta.id || `${config.prefixId}-${Date.now()}`;
|
|
65
|
+
const visible = config.showComments
|
|
66
|
+
&& (commentToken.meta.status !== 'resolved' || config.showResolvedComments);
|
|
67
|
+
config.onVisibilityChange(commentId, visible);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle block comments
|
|
72
|
+
if (token.type === 'blockComment') {
|
|
73
|
+
const commentToken = token as BlockCommentToken;
|
|
74
|
+
|
|
75
|
+
// Apply custom token modifications if configured
|
|
76
|
+
if (config.customizeToken && typeof config.customizeToken === 'function') {
|
|
77
|
+
config.customizeToken(commentToken);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Trigger visibility change callback
|
|
81
|
+
if (config.onVisibilityChange && typeof config.onVisibilityChange === 'function') {
|
|
82
|
+
const commentId = commentToken.meta.id || `${config.prefixId}-${Date.now()}`;
|
|
83
|
+
const visible = config.showComments
|
|
84
|
+
&& (commentToken.meta.status !== 'resolved' || config.showResolvedComments);
|
|
85
|
+
config.onVisibilityChange(commentId, visible);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Trigger status change callback
|
|
89
|
+
if (config.onStatusChange && typeof config.onStatusChange === 'function' && commentToken.meta.status) {
|
|
90
|
+
const commentId = commentToken.meta.id || `${config.prefixId}-${Date.now()}`;
|
|
91
|
+
config.onStatusChange(commentId, commentToken.meta.status);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
extensions: [
|
|
96
|
+
createInlineCommentTokenizer(config),
|
|
97
|
+
createBlockCommentTokenizer(config),
|
|
98
|
+
createInlineCommentRenderer(config),
|
|
99
|
+
createBlockCommentRenderer(config),
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Token } from 'marked';
|
|
2
|
+
import {
|
|
3
|
+
COMMENT_ICONS,
|
|
4
|
+
COMMENT_LABELS,
|
|
5
|
+
DEFAULT_BLOCK_TEMPLATE,
|
|
6
|
+
DEFAULT_INLINE_TEMPLATE,
|
|
7
|
+
METADATA_TEMPLATE,
|
|
8
|
+
} from './utils/constants';
|
|
9
|
+
import { buildMetadataHtml, replaceTemplatePlaceholders } from './utils/helpers';
|
|
10
|
+
import type { CommentMeta } from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render options for comments
|
|
14
|
+
*/
|
|
15
|
+
interface RenderOptions {
|
|
16
|
+
commentId: string;
|
|
17
|
+
meta: CommentMeta;
|
|
18
|
+
text: string;
|
|
19
|
+
tokens: Token[];
|
|
20
|
+
parser: { parse: (tokens: Token[]) => string; parseInline: (tokens: Token[]) => string };
|
|
21
|
+
className: string;
|
|
22
|
+
template?: string;
|
|
23
|
+
showMetadata: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Render an inline comment
|
|
28
|
+
*/
|
|
29
|
+
export function renderInlineComment(options: RenderOptions): string {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
31
|
+
const { commentId, meta, text, className, template, showMetadata } = options;
|
|
32
|
+
|
|
33
|
+
const type = meta.type || 'note';
|
|
34
|
+
const status = meta.status || 'open';
|
|
35
|
+
const visibility = meta.visibility || 'public';
|
|
36
|
+
const icon = COMMENT_ICONS[type] || '📝';
|
|
37
|
+
|
|
38
|
+
// Build visibility class
|
|
39
|
+
const visibilityClass = visibility !== 'public' ? `${className}-visibility-${visibility}` : '';
|
|
40
|
+
|
|
41
|
+
// Build tooltip text
|
|
42
|
+
const tooltipParts: string[] = [];
|
|
43
|
+
if (meta.author) tooltipParts.push(`Author: ${meta.author}`);
|
|
44
|
+
if (meta.type) tooltipParts.push(`Type: ${COMMENT_LABELS[meta.type] || meta.type}`);
|
|
45
|
+
if (meta.date) tooltipParts.push(`Date: ${meta.date}`);
|
|
46
|
+
const tooltipText = tooltipParts.join(' | ');
|
|
47
|
+
|
|
48
|
+
const templateToUse = template || DEFAULT_INLINE_TEMPLATE;
|
|
49
|
+
|
|
50
|
+
return replaceTemplatePlaceholders(templateToUse, {
|
|
51
|
+
className,
|
|
52
|
+
commentId,
|
|
53
|
+
type,
|
|
54
|
+
status,
|
|
55
|
+
visibility,
|
|
56
|
+
visibilityClass,
|
|
57
|
+
author: meta.author || '',
|
|
58
|
+
date: meta.date || '',
|
|
59
|
+
text,
|
|
60
|
+
content: text,
|
|
61
|
+
icon,
|
|
62
|
+
tooltipText,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Render a block comment
|
|
68
|
+
*/
|
|
69
|
+
export function renderBlockComment(options: RenderOptions): string {
|
|
70
|
+
const { commentId, meta, tokens, parser, className, template, showMetadata } = options;
|
|
71
|
+
|
|
72
|
+
const type = meta.type || 'note';
|
|
73
|
+
const status = meta.status || 'open';
|
|
74
|
+
const visibility = meta.visibility || 'public';
|
|
75
|
+
const priority = meta.priority || '';
|
|
76
|
+
const icon = COMMENT_ICONS[type] || '📝';
|
|
77
|
+
const typeLabel = COMMENT_LABELS[type] || type;
|
|
78
|
+
|
|
79
|
+
// Build visibility class
|
|
80
|
+
const visibilityClass = visibility !== 'public' ? `${className}-visibility-${visibility}` : '';
|
|
81
|
+
|
|
82
|
+
// Build priority class
|
|
83
|
+
const priorityClass = priority ? `${className}-priority-${priority}` : '';
|
|
84
|
+
|
|
85
|
+
// Parse comment content (nested markdown)
|
|
86
|
+
const content = parser.parse(tokens);
|
|
87
|
+
|
|
88
|
+
// Build metadata HTML
|
|
89
|
+
let metadata = '';
|
|
90
|
+
if (showMetadata) {
|
|
91
|
+
const { authorInfo, dateInfo, tagsInfo, priorityInfo } = buildMetadataHtml(
|
|
92
|
+
meta.author,
|
|
93
|
+
meta.date,
|
|
94
|
+
meta.tags,
|
|
95
|
+
meta.priority,
|
|
96
|
+
className,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
metadata = replaceTemplatePlaceholders(METADATA_TEMPLATE, {
|
|
100
|
+
className,
|
|
101
|
+
authorInfo,
|
|
102
|
+
dateInfo,
|
|
103
|
+
tagsInfo,
|
|
104
|
+
priorityInfo,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const templateToUse = template || DEFAULT_BLOCK_TEMPLATE;
|
|
109
|
+
|
|
110
|
+
return replaceTemplatePlaceholders(templateToUse, {
|
|
111
|
+
className,
|
|
112
|
+
commentId,
|
|
113
|
+
type,
|
|
114
|
+
status,
|
|
115
|
+
visibility,
|
|
116
|
+
visibilityClass,
|
|
117
|
+
priorityClass,
|
|
118
|
+
author: meta.author || '',
|
|
119
|
+
date: meta.date || '',
|
|
120
|
+
icon,
|
|
121
|
+
typeLabel,
|
|
122
|
+
content,
|
|
123
|
+
metadata,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
package/src/tokenizer.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { RendererExtension, TokenizerExtension } from 'marked';
|
|
2
|
+
import { generateUniqueId, parsePropString } from './utils/helpers';
|
|
3
|
+
import { renderBlockComment, renderInlineComment } from './renderer';
|
|
4
|
+
import type { BlockCommentToken, CommentMeta, CommentOptions, InlineCommentToken } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create an inline comment tokenizer
|
|
8
|
+
*/
|
|
9
|
+
export function createInlineCommentTokenizer(options: Required<Omit<CommentOptions, 'customizeToken' | 'onVisibilityChange' | 'onStatusChange' | 'inlineTemplate' | 'blockTemplate' | 'visibilityFilter' | 'typeFilter'>>): TokenizerExtension {
|
|
10
|
+
return {
|
|
11
|
+
name: 'inlineComment',
|
|
12
|
+
level: 'inline',
|
|
13
|
+
start(src: string) {
|
|
14
|
+
return src.indexOf(':::comment');
|
|
15
|
+
},
|
|
16
|
+
tokenizer(src: string): InlineCommentToken | undefined {
|
|
17
|
+
// Regex for inline comments: :::comment{props}text:::
|
|
18
|
+
const match = src.match(/^:::comment(?:\{([^}]*)\})?([^:]+):::/);
|
|
19
|
+
|
|
20
|
+
if (!match) return undefined;
|
|
21
|
+
|
|
22
|
+
const [raw, propString = '', text] = match;
|
|
23
|
+
|
|
24
|
+
// Parse properties
|
|
25
|
+
const props = parsePropString(propString);
|
|
26
|
+
|
|
27
|
+
const meta: CommentMeta = {
|
|
28
|
+
author: props['author'],
|
|
29
|
+
date: props['date'],
|
|
30
|
+
type: (props['type'] as CommentMeta['type']) || options.defaultType,
|
|
31
|
+
visibility: (props['visibility'] as CommentMeta['visibility']) || options.defaultVisibility,
|
|
32
|
+
status: (props['status'] as CommentMeta['status']) || 'open',
|
|
33
|
+
priority: props['priority'] as CommentMeta['priority'],
|
|
34
|
+
tags: props['tags'],
|
|
35
|
+
id: props['id'],
|
|
36
|
+
className: props['className'],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
type: 'inlineComment',
|
|
41
|
+
raw,
|
|
42
|
+
text: text.trim(),
|
|
43
|
+
meta,
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create block comment tokenizer
|
|
51
|
+
*/
|
|
52
|
+
export function createBlockCommentTokenizer(options: Required<Omit<CommentOptions, 'customizeToken' | 'onVisibilityChange' | 'onStatusChange' | 'inlineTemplate' | 'blockTemplate' | 'visibilityFilter' | 'typeFilter'>>): TokenizerExtension {
|
|
53
|
+
return {
|
|
54
|
+
name: 'blockComment',
|
|
55
|
+
level: 'block',
|
|
56
|
+
tokenizer(src: string): BlockCommentToken | undefined {
|
|
57
|
+
// Regex for block comments: ::::comment{props}\ncontent\n::::commentend
|
|
58
|
+
const match = src.match(/^::::comment(?:\{([^}]*)\})?\s*\n([\s\S]*?)::::commentend/);
|
|
59
|
+
|
|
60
|
+
if (!match) return undefined;
|
|
61
|
+
|
|
62
|
+
const [raw, propString = '', content] = match;
|
|
63
|
+
|
|
64
|
+
// Parse properties
|
|
65
|
+
const props = parsePropString(propString);
|
|
66
|
+
|
|
67
|
+
const meta: CommentMeta = {
|
|
68
|
+
author: props['author'],
|
|
69
|
+
date: props['date'],
|
|
70
|
+
type: (props['type'] as CommentMeta['type']) || options.defaultType,
|
|
71
|
+
visibility: (props['visibility'] as CommentMeta['visibility']) || options.defaultVisibility,
|
|
72
|
+
status: (props['status'] as CommentMeta['status']) || 'open',
|
|
73
|
+
priority: props['priority'] as CommentMeta['priority'],
|
|
74
|
+
tags: props['tags'],
|
|
75
|
+
id: props['id'],
|
|
76
|
+
replyTo: props['replyTo'],
|
|
77
|
+
className: props['className'],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Tokenize the content
|
|
81
|
+
const tokens = this.lexer.blockTokens(content.trim());
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
type: 'blockComment',
|
|
85
|
+
raw,
|
|
86
|
+
text: content.trim(),
|
|
87
|
+
meta,
|
|
88
|
+
tokens,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create renderer for inline comments
|
|
96
|
+
*/
|
|
97
|
+
export function createInlineCommentRenderer(
|
|
98
|
+
options: Required<Omit<CommentOptions, 'customizeToken' | 'onVisibilityChange' | 'onStatusChange' | 'visibilityFilter' | 'typeFilter'>>,
|
|
99
|
+
): RendererExtension {
|
|
100
|
+
return {
|
|
101
|
+
name: 'inlineComment',
|
|
102
|
+
renderer(token): string {
|
|
103
|
+
const commentToken = token as InlineCommentToken;
|
|
104
|
+
const meta = commentToken.meta;
|
|
105
|
+
|
|
106
|
+
// Check if comment should be shown
|
|
107
|
+
if (!options.showComments) return commentToken.text;
|
|
108
|
+
if (!options.enableInlineComments) return commentToken.text;
|
|
109
|
+
if (meta.status === 'resolved' && !options.showResolvedComments) return commentToken.text;
|
|
110
|
+
|
|
111
|
+
// Generate unique ID
|
|
112
|
+
const commentId = meta.id || generateUniqueId(options.prefixId);
|
|
113
|
+
|
|
114
|
+
return renderInlineComment({
|
|
115
|
+
commentId,
|
|
116
|
+
meta,
|
|
117
|
+
text: commentToken.text,
|
|
118
|
+
tokens: [],
|
|
119
|
+
parser: this.parser,
|
|
120
|
+
className: meta.className || options.className,
|
|
121
|
+
template: options.inlineTemplate,
|
|
122
|
+
showMetadata: options.showMetadata,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create renderer for block comments
|
|
130
|
+
*/
|
|
131
|
+
export function createBlockCommentRenderer(
|
|
132
|
+
options: Required<Omit<CommentOptions, 'customizeToken' | 'onVisibilityChange' | 'onStatusChange' | 'visibilityFilter' | 'typeFilter'>>,
|
|
133
|
+
): RendererExtension {
|
|
134
|
+
return {
|
|
135
|
+
name: 'blockComment',
|
|
136
|
+
renderer(token): string {
|
|
137
|
+
const commentToken = token as BlockCommentToken;
|
|
138
|
+
const meta = commentToken.meta;
|
|
139
|
+
|
|
140
|
+
// Check if comment should be shown
|
|
141
|
+
if (!options.showComments) return '';
|
|
142
|
+
if (!options.enableBlockComments) return '';
|
|
143
|
+
if (meta.status === 'resolved' && !options.showResolvedComments) return '';
|
|
144
|
+
|
|
145
|
+
// Generate unique ID
|
|
146
|
+
const commentId = meta.id || generateUniqueId(options.prefixId);
|
|
147
|
+
|
|
148
|
+
return renderBlockComment({
|
|
149
|
+
commentId,
|
|
150
|
+
meta,
|
|
151
|
+
text: commentToken.text,
|
|
152
|
+
tokens: commentToken.tokens || [],
|
|
153
|
+
parser: this.parser,
|
|
154
|
+
className: meta.className || options.className,
|
|
155
|
+
template: options.blockTemplate,
|
|
156
|
+
showMetadata: options.showMetadata,
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|