|
30 | 30 | MessagesSquare |
31 | 31 | } from "@lucide/svelte"; |
32 | 32 | import rehypeShikiFromHighlighter from "@shikijs/rehype/core"; |
| 33 | + import { transformerNotationDiff } from "@shikijs/transformers"; |
33 | 34 | import type { Plugin } from "svelte-exmarkdown"; |
34 | 35 | import type { |
35 | 36 | DiscussionDetails, |
|
60 | 61 | themes: { light: "github-light-default", dark: "github-dark-default" }, |
61 | 62 | transformers: [ |
62 | 63 | { |
| 64 | + preprocess(code, options) { |
| 65 | + if (options.lang === "diff") { |
| 66 | + const cleanedCode = code |
| 67 | + .split("\n") |
| 68 | + .map(line => line.replace(/^[+-]/, "")) |
| 69 | + .join("\n"); |
| 70 | + const detectedLanguage = detectLanguage(cleanedCode); |
| 71 | + if (!detectedLanguage) return; |
| 72 | + options.lang = detectedLanguage; |
| 73 | + return code |
| 74 | + .split("\n") |
| 75 | + .map(line => |
| 76 | + line.replace( |
| 77 | + /^([+-])(.*)/, |
| 78 | + (_, sign, rest) => `${rest} // [!code ${sign}${sign}]` |
| 79 | + ) |
| 80 | + ) |
| 81 | + .join("\n"); |
| 82 | + } |
| 83 | + }, |
63 | 84 | pre(node) { |
64 | 85 | node.properties["data-language"] = this.options.lang |
65 | 86 | .toLowerCase() |
66 | 87 | .replace(/^js$/, "javascript") |
67 | 88 | .replace(/^ts$/, "typescript"); |
68 | 89 | } |
69 | | - } |
| 90 | + }, |
| 91 | + transformerNotationDiff() |
70 | 92 | ] |
71 | 93 | } satisfies Parameters<typeof rehypeShikiFromHighlighter>[1] |
72 | 94 | ] |
73 | 95 | }; |
74 | 96 |
|
75 | 97 | // Utils |
| 98 | + function detectLanguage(code: string): string | undefined { |
| 99 | + const hasHTML = /<\/[a-zA-Z0-9-]+>/.test(code); |
| 100 | + const hasJS = / (let|var|const|=) /.test(code); |
| 101 | +
|
| 102 | + if (hasHTML && hasJS) return "svelte"; |
| 103 | + if (hasHTML) return "html"; |
| 104 | + if (hasJS) return /(: [A-Z]|type |interface )/.test(code) ? "ts" : "js"; |
| 105 | + if (/[a-z-]+: \S+/.test(code)) return "css"; |
| 106 | + return undefined; |
| 107 | + } |
| 108 | +
|
76 | 109 | function formatToDateTime(date: string) { |
77 | 110 | return new Intl.DateTimeFormat("en", { |
78 | 111 | dateStyle: "medium", |
|
584 | 617 | :global(html.dark .shiki), |
585 | 618 | :global(html.dark .shiki span) { |
586 | 619 | color: var(--shiki-dark) !important; |
587 | | - background-color: var(--shiki-dark-bg) !important; |
588 | 620 | font-style: var(--shiki-dark-font-style) !important; |
589 | 621 | font-weight: var(--shiki-dark-font-weight) !important; |
590 | 622 | text-decoration: var(--shiki-dark-text-decoration) !important; |
591 | 623 | } |
592 | 624 |
|
| 625 | + :global(html.dark .shiki), |
| 626 | + :global(html.dark .shiki .line:not(.diff) span) { |
| 627 | + background-color: var(--shiki-dark-bg) !important; |
| 628 | + } |
| 629 | +
|
| 630 | + /* Line numbers, credit to https://github.com/shikijs/shiki/issues/3#issuecomment-830564854 */ |
| 631 | + /* Diff marks */ |
593 | 632 | :global { |
594 | 633 | .shiki { |
595 | | - /* Show line numbers */ |
596 | | - /* Credit to https://github.com/shikijs/shiki/issues/3#issuecomment-830564854 */ |
| 634 | + position: relative; |
| 635 | +
|
597 | 636 | code { |
598 | 637 | counter-reset: step; |
599 | 638 | counter-increment: step 0; |
|
608 | 647 | white-space: nowrap; |
609 | 648 | color: color-mix(in oklab, var(--color-muted-foreground) 70%, transparent); |
610 | 649 | } |
| 650 | +
|
| 651 | + .diff { |
| 652 | + display: inline-block; |
| 653 | + transition: background-color 0.5s; |
| 654 | +
|
| 655 | + &::after { |
| 656 | + content: ""; |
| 657 | + position: absolute; |
| 658 | + left: 0; |
| 659 | + width: 100%; |
| 660 | + height: 1lh; |
| 661 | + } |
| 662 | +
|
| 663 | + &.add { |
| 664 | + &::after { |
| 665 | + background-color: color-mix( |
| 666 | + in oklab, |
| 667 | + var(--color-green-500) 15%, |
| 668 | + transparent |
| 669 | + ) !important; |
| 670 | + } |
| 671 | +
|
| 672 | + span:first-child::before { |
| 673 | + content: "+"; |
| 674 | + color: var(--color-green-500); |
| 675 | + } |
| 676 | + } |
| 677 | +
|
| 678 | + &.remove { |
| 679 | + &::after { |
| 680 | + background-color: color-mix( |
| 681 | + in oklab, |
| 682 | + var(--color-destructive) 15%, |
| 683 | + transparent |
| 684 | + ) !important; |
| 685 | + } |
| 686 | +
|
| 687 | + span { |
| 688 | + opacity: 0.7; |
| 689 | + } |
| 690 | +
|
| 691 | + span:first-child::before { |
| 692 | + content: "-"; |
| 693 | + color: var(--color-destructive); |
| 694 | + } |
| 695 | + } |
| 696 | +
|
| 697 | + span:first-child::before { |
| 698 | + position: absolute; |
| 699 | + left: 2.75rem; |
| 700 | + } |
| 701 | + } |
611 | 702 | } |
612 | 703 |
|
613 | | - /* Add language tag to code blocks */ |
| 704 | + /* Language tag banner */ |
614 | 705 | &:is(pre[data-language]) { |
615 | | - position: relative; |
616 | 706 | padding-top: 3rem; |
617 | 707 | border-radius: var(--radius-xl); |
618 | 708 | border: 1px var(--tw-border-style) var(--color-border); |
|
0 commit comments