Skip to content

Instantly share code, notes, and snippets.

@suin
Created October 28, 2025 09:23
Show Gist options
  • Select an option

  • Save suin/ead99a9e25e9d1bd43e420c62595bf5b to your computer and use it in GitHub Desktop.

Select an option

Save suin/ead99a9e25e9d1bd43e420c62595bf5b to your computer and use it in GitHub Desktop.
LinearのURLをきれいにするやつ
import {fromMarkdown} from 'mdast-util-from-markdown';
import {toMarkdown} from 'mdast-util-to-markdown';
import {visitParents} from 'unist-util-visit-parents';
import type {Link} from 'mdast';
import {gfmFromMarkdown, gfmToMarkdown} from 'mdast-util-gfm';
import {gfm as micromarkGfm} from 'micromark-extension-gfm';
import {frontmatterFromMarkdown, frontmatterToMarkdown} from 'mdast-util-frontmatter';
import {frontmatter as micromarkFrontmatter} from 'micromark-extension-frontmatter';
import {directiveFromMarkdown, directiveToMarkdown} from 'mdast-util-directive';
import {directive as micromarkDirective} from 'micromark-extension-directive';
export function linearCleanLinks(src: string): string {
const tree = fromMarkdown(src, {
extensions: [
micromarkGfm(),
micromarkFrontmatter(['yaml', 'toml']),
micromarkDirective()
],
mdastExtensions: [
gfmFromMarkdown(),
frontmatterFromMarkdown(['yaml', 'toml']),
directiveFromMarkdown()
]
});
function cleanLinearUrl(url: string): string {
// Only handle absolute https? urls to linear.app
let u: URL | undefined;
try {
u = new URL(url);
} catch {
return url;
}
if (u.hostname !== 'linear.app' && u.hostname !== 'www.linear.app') return url;
const path = u.pathname; // e.g. /org/issue/ABC-123/some-title
const m = path.match(/^\/(?:([^\/]+))\/(?:issue|issues)\/([A-Z]+-\d+)(?:\/.*)?$/);
if (!m) return url;
const org = m[1];
const key = m[2];
const cleaned = `https://linear.app/${org}/issue/${key}`;
return cleaned;
}
visitParents(tree as any, 'link', (node) => {
const link = node as Link;
if (typeof link.url !== 'string') return;
const cleaned = cleanLinearUrl(link.url);
if (cleaned !== link.url) {
link.url = cleaned;
}
});
const markdown = toMarkdown(tree, {
bullet: '-',
fences: true,
emphasis: '_',
rule: '-',
unsafe: [],
extensions: [
gfmToMarkdown(),
frontmatterToMarkdown(['yaml', 'toml']),
directiveToMarkdown()
]
});
return markdown.trimEnd();
}
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"test": "bun test"
},
"dependencies": {
"@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.548.0",
"mdast-util-directive": "^3.1.0",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-frontmatter": "^2.0.1",
"mdast-util-gfm": "^3.1.0",
"mdast-util-to-markdown": "^2.1.2",
"micromark-extension-directive": "^4.0.0",
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"next": "16.0.0",
"react": "19.2.0",
"react-dom": "19.2.0",
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"tailwind-merge": "^3.3.1",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.0.0",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment