Skip to content

Commit ef5df99

Browse files
Merge pull request #2589 from appwrite/feat-SER-418-revamp-copy-as-markdown
2 parents 1ffe8c6 + d803108 commit ef5df99

File tree

25 files changed

+910
-523
lines changed

25 files changed

+910
-523
lines changed

bun.lock

Lines changed: 348 additions & 220 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,30 @@
2424
"optimize:all": "bun ./scripts/optimize-all.js"
2525
},
2626
"dependencies": {
27-
"@sentry/sveltekit": "^10.22.0",
27+
"@sentry/sveltekit": "^10.28.0",
2828
"h3": "^1.15.4",
29-
"sharp": "^0.34.4"
29+
"sharp": "^0.34.5"
3030
},
3131
"devDependencies": {
3232
"@appwrite.io/console": "^0.6.4",
3333
"@appwrite.io/pink": "~0.26.0",
3434
"@appwrite.io/pink-icons": "~0.26.0",
3535
"@appwrite.io/repo": "github:appwrite/appwrite#aa98fbb2dc0e08def0a7cca7fe0515de643154ca",
36-
"@eslint/compat": "^1.4.0",
37-
"@eslint/js": "^9.36.0",
36+
"@eslint/compat": "^1.4.1",
37+
"@eslint/js": "^9.39.1",
3838
"@fingerprintjs/fingerprintjs": "^4.6.2",
3939
"@internationalized/date": "3.5.0",
4040
"@melt-ui/pp": "^0.3.2",
4141
"@melt-ui/svelte": "^0.86.6",
4242
"@number-flow/svelte": "^0.3.9",
43-
"@playwright/test": "^1.56.1",
43+
"@playwright/test": "^1.57.0",
4444
"@sveltejs/adapter-node": "^5.3.3",
45-
"@sveltejs/enhanced-img": "^0.8.4",
46-
"@sveltejs/kit": "^2.48.0",
45+
"@sveltejs/enhanced-img": "^0.8.5",
46+
"@sveltejs/kit": "^2.49.1",
4747
"@sveltejs/vite-plugin-svelte": "^6.2.1",
48-
"@tailwindcss/postcss": "^4.1.16",
49-
"@turf/boolean-point-in-polygon": "^7.2.0",
50-
"@types/bun": "^1.3.1",
48+
"@tailwindcss/postcss": "^4.1.17",
49+
"@turf/boolean-point-in-polygon": "^7.3.1",
50+
"@types/bun": "^1.3.3",
5151
"@types/compression": "^1.8.1",
5252
"@types/glob": "^8.1.0",
5353
"@types/jsdom": "^21.1.7",
@@ -65,17 +65,17 @@
6565
"embla-carousel-auto-scroll": "^8.6.0",
6666
"embla-carousel-svelte": "^8.6.0",
6767
"embla-carousel-wheel-gestures": "^8.1.0",
68-
"eslint": "^9.36.0",
68+
"eslint": "^9.39.1",
6969
"eslint-config-prettier": "^10.1.8",
70-
"eslint-plugin-svelte": "^3.12.4",
70+
"eslint-plugin-svelte": "^3.13.0",
7171
"fuse.js": "^7.1.0",
72-
"globals": "^16.4.0",
72+
"globals": "^16.5.0",
7373
"highlight.js": "^11.11.1",
7474
"linkedom": "^0.18.12",
7575
"markdown-it": "^14.1.0",
7676
"meilisearch": "^0.37.0",
7777
"melt": "^0.29.3",
78-
"motion": "^12.23.24",
78+
"motion": "^12.23.25",
7979
"motion-legacy": "npm:motion@^10.18.0",
8080
"node-appwrite": "^16.0.0",
8181
"node-fetch": "^3.3.2",
@@ -84,30 +84,30 @@
8484
"oslllo-svg-fixer": "^3.0.0",
8585
"plausible-tracker": "^0.3.9",
8686
"postcss": "^8.5.6",
87-
"posthog-js": "^1.280.1",
87+
"posthog-js": "^1.301.1",
8888
"posthog-node": "^4.18.0",
89-
"prettier": "^3.6.2",
89+
"prettier": "^3.7.4",
9090
"prettier-plugin-svelte": "^3.4.0",
91-
"prettier-plugin-tailwindcss": "^0.7.1",
92-
"proj4": "^2.19.10",
91+
"prettier-plugin-tailwindcss": "^0.7.2",
92+
"proj4": "^2.20.2",
9393
"remeda": "^2.32.0",
9494
"reodotdev": "^1.0.0",
95-
"sass": "^1.93.2",
96-
"svelte": "^5.42.2",
97-
"svelte-check": "^4.3.3",
95+
"sass": "^1.94.2",
96+
"svelte": "^5.45.5",
97+
"svelte-check": "^4.3.4",
9898
"svelte-markdoc-preprocess": "3.0.0",
9999
"svelte-markdown": "^0.4.1",
100100
"svg-dotted-map": "^2.0.1",
101101
"svgtofont": "^4.2.3",
102-
"tailwind-merge": "^3.3.1",
103-
"tailwindcss": "^4.1.16",
102+
"tailwind-merge": "^3.4.0",
103+
"tailwindcss": "^4.1.17",
104104
"tslib": "^2.8.1",
105105
"typescript": "^5.9.3",
106-
"typescript-eslint": "^8.46.2",
106+
"typescript-eslint": "^8.48.1",
107107
"vaul-svelte": "1.0.0-next.7",
108-
"vite": "^7.1.19",
108+
"vite": "^7.2.10",
109109
"vite-plugin-dynamic-import": "^1.6.0",
110-
"vite-plugin-image-optimizer": "^2.0.2",
110+
"vite-plugin-image-optimizer": "^2.0.3",
111111
"vite-plugin-manifest-sri": "^0.2.0",
112112
"vitest": "^3.2.4",
113113
"zod": "^3.25.76"

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Loading

src/icons/svg/external-icon.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 107 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,119 @@
11
<script lang="ts">
2-
import { getContext } from 'svelte';
3-
import { handleCopy } from '$lib/utils/copy';
2+
import { page } from '$app/stores';
3+
import { getPageMarkdown } from '$lib/remote/markdown.remote';
4+
import { copyToClipboard } from '$lib/utils/copy';
45
import { cn } from '$lib/utils/cn';
5-
import { rawContent } from '$routes/docs/+layout.svelte';
6+
import { writable } from 'svelte/store';
7+
import { Button, Icon, SplitButton } from '$lib/components/ui';
8+
import { Tooltip } from '$lib/components';
9+
import { createDropdownMenu, melt } from '@melt-ui/svelte';
610
711
interface CopyAsMarkdownProps {
812
class?: string;
913
}
1014
11-
const { copy, copied } = handleCopy($rawContent ?? '', 2000);
12-
1315
const { class: classNames }: CopyAsMarkdownProps = $props();
16+
17+
const markdown = getPageMarkdown($page.route.id);
18+
const copied = writable(false);
19+
let timeout: ReturnType<typeof setTimeout> | undefined = undefined;
20+
21+
const copy = () => {
22+
if (timeout) clearTimeout(timeout);
23+
copyToClipboard(markdown.current ?? '');
24+
copied.set(true);
25+
timeout = setTimeout(() => copied.set(false), 2000);
26+
};
27+
28+
const {
29+
elements: { trigger, menu },
30+
states: { open }
31+
} = createDropdownMenu({
32+
forceVisible: true,
33+
positioning: { placement: 'bottom-end' }
34+
});
35+
36+
const viewInNewTab = () => {
37+
const { pathname } = window.location;
38+
const url = pathname.endsWith('.md') ? pathname : `${pathname}.md`;
39+
window.open(url, '_blank', 'noopener,noreferrer');
40+
};
1441
</script>
1542

16-
{#if $rawContent}
17-
<button
18-
class={cn(
19-
'text-caption hover:text-accent text-secondary ml-4 flex cursor-pointer items-center gap-2.5 rounded-md p-1.5 transition-colors',
20-
classNames
21-
)}
22-
onclick={copy}
23-
>
24-
<svg xmlns="http://www.w3.org/2000/svg" class="w-6" viewBox="0 0 208 128"
25-
><rect
26-
width="198"
27-
height="118"
28-
x="5"
29-
y="5"
30-
ry="10"
31-
stroke="currentColor"
32-
stroke-width="10"
33-
fill="none"
34-
/><path
35-
d="M30 98V30h20l20 25 20-25h20v68H90V59L70 84 50 59v39zm125 0l-30-33h20V30h20v35h20z"
36-
fill="currentColor"
37-
/>
38-
</svg>
39-
{#if $copied}
40-
Copied
41-
{:else}
42-
Copy page as markdown
43+
{#if !markdown.loading && markdown.current}
44+
<div class={cn('copy-ctl inline-flex items-center', classNames)}>
45+
<SplitButton>
46+
<Tooltip disabled={!$copied}>
47+
<Button
48+
variant="secondary"
49+
onclick={copy}
50+
aria-label="Copy page as Markdown"
51+
splitPosition="first"
52+
class="text-sm"
53+
>
54+
<Icon name="copy" aria-hidden="true" class="text-sm" />
55+
<span>Copy page</span>
56+
</Button>
57+
{#snippet tooltip()}
58+
Copied
59+
{/snippet}
60+
</Tooltip>
61+
62+
<button
63+
class="web-button is-secondary is-split is-split-last text-sm"
64+
use:melt={$trigger}
65+
aria-label="Open options"
66+
>
67+
{#if $open}
68+
<span class="web-icon-chevron-up" aria-hidden="true"></span>
69+
{:else}
70+
<span class="web-icon-chevron-down" aria-hidden="true"></span>
71+
{/if}
72+
</button>
73+
</SplitButton>
74+
75+
{#if $open}
76+
<div class="menu-wrapper web-select-menu is-normal menu z-1" use:melt={$menu}>
77+
<ul class="text-sub-body">
78+
<li>
79+
<button type="button" class="menu-btn text-sm" onclick={copy}>
80+
<Icon name="copy" aria-hidden="true" class="text-sm" />
81+
<span>Copy as Markdown</span>
82+
</button>
83+
</li>
84+
<li>
85+
<button type="button" class="menu-btn text-sm" onclick={viewInNewTab}>
86+
<Icon name="external-icon" aria-hidden="true" class="text-sm" />
87+
<span>View as Markdown</span>
88+
</button>
89+
</li>
90+
</ul>
91+
</div>
4392
{/if}
44-
</button>
93+
</div>
4594
{/if}
95+
96+
<style>
97+
.copy-ctl {
98+
align-items: center;
99+
}
100+
.menu-wrapper {
101+
padding: 4px;
102+
z-index: 100;
103+
}
104+
.menu-btn {
105+
height: 32px;
106+
min-height: 32px;
107+
display: flex;
108+
align-items: center;
109+
gap: 0.5rem;
110+
border-radius: 0.5rem;
111+
padding: 6px 8px;
112+
width: 100%;
113+
text-align: left;
114+
}
115+
.menu-btn:hover {
116+
cursor: pointer;
117+
background-color: hsl(var(--web-color-offset));
118+
}
119+
</style>

src/lib/components/blog/table-of-contents.svelte

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,4 @@
8181
style:transform={`translateY(${position}px)`}
8282
></div>
8383
</div>
84-
<CopyAsMarkdown />
8584
</nav>

src/lib/components/ui/button.svelte

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
});
1717
1818
export type Variant = VariantProps<typeof button>['variant'];
19+
export type SplitPosition = 'first' | 'middle' | 'last' | 'only';
20+
export const BUTTON_SPLIT_CONTEXT = Symbol('button-split-context');
1921
</script>
2022

2123
<script lang="ts">
2224
import type { Snippet } from 'svelte';
2325
import type { Action } from 'svelte/action';
26+
import { getContext } from 'svelte';
2427
import { cn } from '$lib/utils/cn';
2528
import { trackEvent, type TrackEventArgs } from '$lib/actions/analytics';
2629
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
@@ -33,6 +36,7 @@
3336
action?: Action;
3437
children: Snippet;
3538
event?: string | TrackEventArgs;
39+
splitPosition?: SplitPosition;
3640
} & VariantProps<typeof button> &
3741
ButtonOrAnchorProps;
3842
@@ -43,10 +47,17 @@
4347
children,
4448
class: classes,
4549
event,
50+
splitPosition,
4651
...rest
4752
}: Props = $props();
4853
49-
const buttonClasses = cn(button({ variant }), classes);
54+
const isSplit = getContext<boolean>(BUTTON_SPLIT_CONTEXT);
55+
const splitClasses = isSplit
56+
? splitPosition
57+
? `is-split is-split-${splitPosition}`
58+
: 'is-split'
59+
: '';
60+
const buttonClasses = cn(button({ variant }), splitClasses, classes);
5061
</script>
5162

5263
{#if href}

0 commit comments

Comments
 (0)