generated from polymech/site-template
fucking around, pro
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { filterMarkdownLinks } from './markdown.js';
|
||||
|
||||
describe('filterMarkdownLinks', () => {
|
||||
it('should filter out exact URLs', () => {
|
||||
const markdown = 'Check out [link1](https://example.com) and [link2](https://other.com)';
|
||||
const filters = [{ pattern: 'https://example.com' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('Check out link1 and [link2](https://other.com)');
|
||||
});
|
||||
|
||||
it('should replace URLs with custom text', () => {
|
||||
const markdown = 'Check out [link1](https://example.com) and [link2](https://other.com)';
|
||||
const filters = [{ pattern: 'https://example.com', replacement: 'REPLACED' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('Check out REPLACED and [link2](https://other.com)');
|
||||
});
|
||||
|
||||
it('should filter out URLs matching regex patterns', () => {
|
||||
const markdown = 'Visit [spam](https://spam.com) and [ads](https://advertisement.com)';
|
||||
const filters = [
|
||||
{ pattern: /spam\.com/ },
|
||||
{ pattern: /advertisement/ }
|
||||
];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('Visit spam and ads');
|
||||
});
|
||||
|
||||
it('should replace URLs matching regex patterns with custom text', () => {
|
||||
const markdown = 'Visit [spam](https://spam.com) and [ads](https://advertisement.com)';
|
||||
const filters = [
|
||||
{ pattern: /spam\.com/, replacement: 'SPAM' },
|
||||
{ pattern: /advertisement/, replacement: 'ADS' }
|
||||
];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('Visit SPAM and ADS');
|
||||
});
|
||||
|
||||
it('should handle nested markdown elements', () => {
|
||||
const markdown = '**Bold text with [link](https://example.com) inside**';
|
||||
const filters = [{ pattern: 'https://example.com' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('**Bold text with link inside**');
|
||||
});
|
||||
|
||||
it('should handle nested markdown elements with replacement', () => {
|
||||
const markdown = '**Bold text with [link](https://example.com) inside**';
|
||||
const filters = [{ pattern: 'https://example.com', replacement: 'REPLACED' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('**Bold text with REPLACED inside**');
|
||||
});
|
||||
|
||||
it('should preserve non-matching links', () => {
|
||||
const markdown = '[keep](https://keep.com) and [remove](https://remove.com)';
|
||||
const filters = [{ pattern: 'https://remove.com' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('[keep](https://keep.com) and remove');
|
||||
});
|
||||
|
||||
it('should handle empty filters array', () => {
|
||||
const markdown = '[link](https://example.com)';
|
||||
const filters: { pattern: string | RegExp; replacement?: string }[] = [];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('[link](https://example.com)');
|
||||
});
|
||||
|
||||
it('should handle complex markdown with multiple links', () => {
|
||||
const markdown = '# Title\n\nSome text with [link1](https://example.com) and [link2](https://spam.com).\n\n## Subtitle\n\nMore text with [link3](https://advertisement.com) and [link4](https://good.com)';
|
||||
const filters = [
|
||||
{ pattern: 'https://example.com' },
|
||||
{ pattern: /spam\.com/ },
|
||||
{ pattern: /advertisement/ }
|
||||
];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('# Title\n\nSome text with link1 and link2.\n\n## Subtitle\n\nMore text with link3 and [link4](https://good.com)');
|
||||
});
|
||||
|
||||
it('should handle complex markdown with multiple replacements', () => {
|
||||
const markdown = '# Title\n\nSome text with [link1](https://example.com) and [link2](https://spam.com).\n\n## Subtitle\n\nMore text with [link3](https://advertisement.com) and [link4](https://good.com)';
|
||||
const filters = [
|
||||
{ pattern: 'https://example.com', replacement: 'REPLACED1' },
|
||||
{ pattern: /spam\.com/, replacement: 'REPLACED2' },
|
||||
{ pattern: /advertisement/, replacement: 'REPLACED3' }
|
||||
];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('# Title\n\nSome text with REPLACED1 and REPLACED2.\n\n## Subtitle\n\nMore text with REPLACED3 and [link4](https://good.com)');
|
||||
});
|
||||
|
||||
it('should handle links with special characters', () => {
|
||||
const markdown = '[special](https://example.com/path?param=value#fragment)';
|
||||
const filters = [{ pattern: 'https://example.com' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('special');
|
||||
});
|
||||
|
||||
it('should handle links with special characters and replacement', () => {
|
||||
const markdown = '[special](https://example.com/path?param=value#fragment)';
|
||||
const filters = [{ pattern: 'https://example.com', replacement: 'REPLACED' }];
|
||||
|
||||
const result = filterMarkdownLinks(markdown, filters);
|
||||
expect(result).toBe('REPLACED');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||
import { toMarkdown } from 'mdast-util-to-markdown';
|
||||
import { Root } from 'mdast';
|
||||
|
||||
type LinkFilter = {
|
||||
pattern: string | RegExp;
|
||||
replacement?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filters out or replaces specified links from markdown content
|
||||
* @param markdown - The markdown string to filter
|
||||
* @param filters - Array of link filters with optional replacement text
|
||||
* @returns Filtered markdown string
|
||||
*/
|
||||
export function filterMarkdownLinks(markdown: string, filters: LinkFilter[]): string {
|
||||
// Parse markdown to AST
|
||||
const tree = fromMarkdown(markdown) as Root;
|
||||
|
||||
// Function to check if a URL should be filtered and get replacement text
|
||||
const shouldFilter = (url: string): string | null => {
|
||||
for (const filter of filters) {
|
||||
if (filter.pattern instanceof RegExp) {
|
||||
if (filter.pattern.test(url)) {
|
||||
return filter.replacement || '';
|
||||
}
|
||||
} else if (url.includes(filter.pattern)) {
|
||||
return filter.replacement || '';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Visit all nodes and remove filtered links
|
||||
const visit = (node: any) => {
|
||||
if (node.type === 'link' && node.url) {
|
||||
const replacement = shouldFilter(node.url);
|
||||
if (replacement !== null) {
|
||||
// Replace link with replacement text or its text content
|
||||
node.type = 'text';
|
||||
node.value = replacement || node.children?.[0]?.value || '';
|
||||
delete node.url;
|
||||
delete node.children;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
node.children = node.children.map((child: any) => visit(child));
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
// Process the tree
|
||||
const filteredTree = visit(tree);
|
||||
|
||||
// Convert back to markdown with options to minimize extra newlines
|
||||
return toMarkdown(filteredTree, {
|
||||
bullet: '-',
|
||||
listItemIndent: 'one',
|
||||
tightDefinitions: true,
|
||||
handlers: {
|
||||
text: (node, _, context) => {
|
||||
const value = node.value.replace(/\n+$/, '');
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Example usage:
|
||||
* const filtered = filterMarkdownLinks(markdown, [
|
||||
* { pattern: 'https://example.com', replacement: 'REPLACED' },
|
||||
* { pattern: /spam\.com/, replacement: 'SPAM' },
|
||||
* { pattern: /advertisement/ }
|
||||
* ]);
|
||||
*/
|
||||
+5
-5
@@ -176,7 +176,7 @@ const to_github = async (item: IHowto) => {
|
||||
'',
|
||||
`# ${item.title}`,
|
||||
'',
|
||||
item.cover_image ? `` : '',
|
||||
item.cover_image ? `})` : '',
|
||||
'',
|
||||
item.description,
|
||||
item.user?.geo ? `\nUser Location: ${item.user.geo.city ? `${item.user.geo.city}, ` : ''}${item.user.geo.countryName || ''}` : '',
|
||||
@@ -189,7 +189,7 @@ const to_github = async (item: IHowto) => {
|
||||
step.text,
|
||||
'',
|
||||
// Add step images if any
|
||||
...step.images.map(img => `\n\n`)
|
||||
...step.images.map(img => `\n})\n`)
|
||||
].join('\n')),
|
||||
'',
|
||||
'## Resources',
|
||||
@@ -222,7 +222,7 @@ const to_mdx = async (item: IHowto) => {
|
||||
'',
|
||||
`# ${item.title}`,
|
||||
'',
|
||||
item.cover_image ? `<Image src={import('./${item.cover_image.name}')} alt="${item.title}" />` : '',
|
||||
item.cover_image ? `<Image src={import('./${sanitizeFilename(item.cover_image.name)}')} alt="${item.title}" />` : '',
|
||||
'',
|
||||
item.description,
|
||||
item.user?.geo ? `\nUser Location: ${item.user.geo.city ? `${item.user.geo.city}, ` : ''}${item.user.geo.countryName || ''}` : '',
|
||||
@@ -235,7 +235,7 @@ const to_mdx = async (item: IHowto) => {
|
||||
step.text,
|
||||
'',
|
||||
// Add step images if any using Astro's Image component
|
||||
...step.images.map(img => `\n<Image src={import('./${img.name}')} alt="${img.name}" />\n`)
|
||||
...step.images.map(img => `\n<Image src={import('./${sanitizeFilename(img.name)}')} alt="${img.name}" />\n`)
|
||||
].join('\n'))
|
||||
].filter(Boolean).join('\n')
|
||||
write(path.join(itemDir, 'index.mdx'), mdxContent)
|
||||
@@ -337,7 +337,7 @@ const complete = async (item: IHowto) => {
|
||||
}
|
||||
await to_github(item)
|
||||
await to_mdx(item)
|
||||
await to_astro(item)
|
||||
// await to_astro(item)
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user