-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathutils.ts
More file actions
144 lines (119 loc) · 3.37 KB
/
Copy pathutils.ts
File metadata and controls
144 lines (119 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import slug from "slug";
export function slugify(text: string, locale: string = "en"): string {
return slug(text, { lower: true, locale });
}
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Simple, dependency-free throttle with cancel/flush helpers
// Behavior: leading and trailing enabled by default
export function throttle<Args extends unknown[]>(
callback: (...args: Args) => void,
waitMs: number,
) {
let timerId: ReturnType<typeof setTimeout> | null = null;
let lastInvokeTime = 0;
let trailingArgs: Args | null = null;
const invoke = (args: Args) => {
lastInvokeTime = Date.now();
callback(...args);
};
const throttled = (...args: Args) => {
const now = Date.now();
const remaining = waitMs - (now - lastInvokeTime);
// Leading edge
if (lastInvokeTime === 0) {
invoke(args);
return;
}
if (remaining <= 0 || remaining > waitMs) {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
invoke(args);
} else {
// Schedule trailing edge
trailingArgs = args;
if (!timerId) {
timerId = setTimeout(() => {
timerId = null;
if (trailingArgs) {
invoke(trailingArgs);
trailingArgs = null;
}
}, remaining);
}
}
};
throttled.cancel = () => {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
trailingArgs = null;
lastInvokeTime = 0;
};
throttled.flush = () => {
if (timerId && trailingArgs) {
clearTimeout(timerId);
timerId = null;
invoke(trailingArgs);
trailingArgs = null;
}
};
return throttled as ((...args: Args) => void) & {
cancel: () => void;
flush: () => void;
};
}
export function stripHtml(html: string): string {
// Remove HTML tags
let text = html.replace(/<[^>]*>/g, "");
// Decode common HTML entities
text = text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(///g, "/")
.replace(/ /g, " ")
.replace(/…/g, "...");
// Clean up extra whitespace and newlines
return text.replace(/\s+/g, " ").trim();
}
export function stripMarkdown(markdown: string): string {
let text = markdown;
// Remove headers (# ## ### etc.)
text = text.replace(/^#{1,6}\s+/gm, "");
// Remove bold and italic (**text**, *text*, __text__, _text_)
text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
text = text.replace(/\*([^*]+)\*/g, "$1");
text = text.replace(/__([^_]+)__/g, "$1");
text = text.replace(/_([^_]+)_/g, "$1");
// Remove strikethrough (~~text~~)
text = text.replace(/~~([^~]+)~~/g, "$1");
// Remove inline code (`code`)
text = text.replace(/`([^`]+)`/g, "$1");
// Remove code blocks (```code```)
text = text.replace(/```[\s\S]*?```/g, "");
// Remove links [text](url) -> text
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
// Remove images 
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
// Remove blockquotes (> text)
text = text.replace(/^>\s+/gm, "");
// Remove horizontal rules (--- or ***)
text = text.replace(/^[-*]{3,}$/gm, "");
// Remove list markers (- * + and numbered lists)
text = text.replace(/^[\s]*[-*+]\s+/gm, "");
text = text.replace(/^[\s]*\d+\.\s+/gm, "");
// Clean up extra whitespace and newlines
return text
.replace(/\n\s*\n/g, "\n")
.replace(/\s+/g, " ")
.trim();
}