Skip to content

Commit

Permalink
Merge pull request #222 from appwrite/feat-code-block
Browse files Browse the repository at this point in the history
feat: code block
  • Loading branch information
ArmanNik authored Oct 21, 2024
2 parents e1c564f + 391f887 commit 6d4b4ff
Show file tree
Hide file tree
Showing 11 changed files with 1,066 additions and 3 deletions.
689 changes: 689 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion v2/pink-sb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@
"tslib": "^2.6.3",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.0"
"vite": "^5.4.0",
"node-gyp": "^10.2.0",
"shiki": "^1.18.0",
"sourcemap-codec": "^1.4.8"
},
"svelte": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions v2/pink-sb/src/helpers/copy.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare function copy(value: string): Promise<boolean>;
42 changes: 42 additions & 0 deletions v2/pink-sb/src/helpers/copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
async function securedCopy(value: string) {
try {
await navigator.clipboard.writeText(value);
} catch {
return false;
}

return true;
}

function unsecuredCopy(value: string) {
const textArea = document.createElement('textarea');
textArea.value = value;

// Avoid scrolling to bottom
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';

document.body.appendChild(textArea);
textArea.focus();
textArea.select();

let success = true;
try {
document.execCommand('copy');
} catch {
success = false;
} finally {
document.body.removeChild(textArea);
}

return success;
}

export async function copy(value: string) {
// securedCopy works only in HTTPS environment.
// unsecuredCopy works in HTTP and only runs if securedCopy fails.
const success = (await securedCopy(value)) || unsecuredCopy(value);

return success;
}
184 changes: 184 additions & 0 deletions v2/pink-sb/src/lib/Code.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
<script lang="ts">
import {
createCssVariablesTheme,
createHighlighter,
type BuiltinLanguage,
type PlainTextLanguage,
type HighlighterGeneric,
type BundledLanguage,
type BundledTheme
} from 'shiki';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition';
import { copy } from '../helpers/copy.ts';
import Button from './button/Button.svelte';
import Icon from '$lib/Icon.svelte';
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
import Tooltip from './Tooltip.svelte';
import Spinner from './Spinner.svelte';
import Select from './input/Select.svelte';
type Language = BuiltinLanguage | PlainTextLanguage;
const languages: Language[] = [
'js',
'javascript',
'dart',
'ts',
'typescript',
'xml',
'html',
'sh',
'md',
'json',
'swift',
'php',
'diff',
'python',
'ruby',
'csharp',
'kotlin',
'java',
'cpp',
'bash',
'powershell',
'cmd',
'yaml',
'text',
'graphql',
'http',
'go',
'py',
'rb',
'cs',
'css',
'groovy',
'ini',
'txt',
'dotenv'
];
const theme = createCssVariablesTheme({
name: 'appwrite',
variablePrefix: '--shiki-',
fontStyle: true
});
export let code: string;
export let lang: Language = 'javascript';
export let lineNumbers = true;
let tooltipContent = 'Copy';
let highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
let htmlCode: string;
onMount(async () => {
highlighter = await createHighlighter({
langs: languages,
themes: [theme]
});
htmlCode = highlightCode(code, lang);
});
function highlightCode(code: string, lang: Language) {
return highlighter?.codeToHtml(code.trim(), {
lang,
theme: 'appwrite',
transformers: [
{
code(node) {
if (lineNumbers) this.addClassToHast(node, 'line-numbers');
}
}
]
});
}
async function copyCode() {
const success = await copy(code);
if (success) {
tooltipContent = 'Copied';
} else {
tooltipContent = 'Failed to copy';
}
setTimeout(() => {
tooltipContent = 'Copy';
}, 3000);
}
$: if (lang && highlighter) {
htmlCode = highlightCode(code, lang);
}
</script>

<header role="generic">
<p class="lang">{lang}</p>
<div>
<!-- <Select bind:value={lang} options={languages} /> -->
{#key tooltipContent}
<Tooltip>
<Button variant="text" icon size="small" on:click={copyCode}>
<Icon size="small" icon={IconDuplicate} />
</Button>
<p slot="tooltip">{tooltipContent}</p>
</Tooltip>
{/key}
</div>
</header>
<div class="code-block">
{#if htmlCode}
<div transition:fade={{ duration: 300 }}>
{@html htmlCode}
</div>
{:else}
<div class="loader">
<Spinner />
</div>
{/if}
</div>

<style lang="scss">
header {
display: flex;
padding: var(--space-3) var(--space-4);
justify-content: space-between;
align-items: center;
border-radius: var(--border-radius-s) var(--border-radius-s) var(--border-radius-none)
var(--border-radius-none);
border: var(--border-width-s, 1px) solid var(--color-border-neutral);
border-bottom: none;
background: var(--color-bgcolor-neutral-secondary);
.lang {
min-width: var(--icon-size-m);
padding: var(--space-1) var(--space-2);
border-radius: var(--border-radius-xs);
background: var(--color-overlay-on-neutral);
}
}
.code-block {
border-radius: var(--border-radius-none) var(--border-radius-none) var(--border-radius-s)
var(--border-radius-s);
border: var(--border-width-s) solid var(--color-border-neutral);
background: var(--color-bgcolor-neutral-primary);
}
.loader {
padding: var(--space-4) var(--space-6);
}
:global(.shiki .line-numbers .line) {
counter-increment: line;
}
:global(.shiki :not(.line-numbers) .line) {
padding-inline-start: 1rem;
}
:global(.shiki .line-numbers .line:before) {
content: counter(line);
display: inline-block;
text-align: right;
color: #6c6c71;
padding-inline-end: 1rem;
width: 2.5rem;
}
</style>
19 changes: 19 additions & 0 deletions v2/pink-sb/src/lib/InlineCode.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import Code from './typography/Code.svelte';
export let code: string;
export let size: 'small' | 'medium' = 'medium';
</script>

<pre class="inline-code">
<Code {size}>{code?.trim()}</Code>
</pre>

<style>
.inline-code {
padding: var(--space-0) var(--space-2);
border-radius: var(--border-radius-xxs);
background: var(--color-overlay-on-neutral);
display: inline-flex;
}
</style>
2 changes: 2 additions & 0 deletions v2/pink-sb/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export { default as Empty } from './Empty.svelte';
export { default as Keyboard } from './Keyboard.svelte';
export { default as Icon } from './Icon.svelte';
export { default as Pagination } from './Pagination.svelte';
export { default as Code } from './Code.svelte';
export { default as InlineCode } from './InlineCode.svelte';
export { Toast } from './toast/index.js';
export * as Button from './button/index.js';
export * as Link from './link/index.js';
Expand Down
71 changes: 71 additions & 0 deletions v2/pink-sb/src/stories/Code.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script context="module" lang="ts">
import { Code } from '$lib/index.js';
import type { MetaProps } from '@storybook/addon-svelte-csf';
const codeExample = `
import { writable } from 'svelte/store';
export const count = writable(0);
`;
export const meta: MetaProps = {
title: 'Components/Code',
component: Code,
args: {
code: codeExample,
lineNumbers: true
},
argTypes: {
lang: {
options: [
'js',
'javascript',
'dart',
'ts',
'typescript',
'xml',
'html',
'sh',
'md',
'json',
'swift',
'php',
'diff',
'python',
'ruby',
'csharp',
'kotlin',
'java',
'cpp',
'bash',
'powershell',
'cmd',
'yaml',
'text',
'graphql',
'http',
'go',
'py',
'rb',
'cs',
'css',
'groovy',
'ini',
'txt',
'dotenv'
],
control: { type: 'select' }
}
}
};
</script>

<script>
import { Story, Template } from '@storybook/addon-svelte-csf';
</script>

<Template let:args>
<Code {...args} />
</Template>

<Story name="Default" />
30 changes: 30 additions & 0 deletions v2/pink-sb/src/stories/InlineCode.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script context="module" lang="ts">
import InlineCode from '$lib/InlineCode.svelte';
import type { MetaProps } from '@storybook/addon-svelte-csf';
const code = `npm install appwrite`;
export const meta: MetaProps = {
title: 'Components/InlineCode',
component: InlineCode,
args: {
code,
size: `medium`
},
argTypes: {
size: {
options: ['small', 'medium'],
control: { type: 'select' }
}
}
};
</script>

<script>
import { Story } from '@storybook/addon-svelte-csf';
</script>

<Story name="Default" let:args>
Example of inline code <InlineCode {...args} /> in a sentence.
</Story>
13 changes: 12 additions & 1 deletion v2/pink-sb/src/themes/dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,16 @@
"shadow-offsety-4": "var(--base-4)",
"shadow-offsety-6": "var(--base-6)",
"shadow-offsety-8": "var(--base-8)",
"shadow-offsety-20": "var(--base-20)"
"shadow-offsety-20": "var(--base-20)",
"shiki-background": "transparent",
"shiki-foreground": "var(--color-fgcolor-neutral-primary)",
"shiki-token-constant": "var(--brand-orange-500)",
"shiki-token-string": "var(--brand-mint-500)",
"shiki-token-comment": "var(--color-fgcolor-neutral-tertiary)",
"shiki-token-keyword": "var(--brand-pink-500)",
"shiki-token-string-expression": "var(--brand-mint-500)",
"shiki-token-parameter": "var(--brand-blue-500)",
"shiki-token-function": "var(--brand-blue-500)",
"shiki-token-punctuation": "var(--color-fgcolor-neutral-primary)",
"shiki-token-link": "var(--brand-orange-500)"
}
Loading

0 comments on commit 6d4b4ff

Please sign in to comment.