Merge pull request #1776 from mermaid-js/release-promotion

Release live editor
This commit is contained in:
Sidharth Vinod
2025-08-11 20:41:02 +05:30
committed by GitHub
8 changed files with 126 additions and 89 deletions
+5 -1
View File
@@ -47,5 +47,9 @@
"tailwindCSS.experimental.classRegex": [
["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
"tailwindCSS.emmetCompletions": true
"tailwindCSS.emmetCompletions": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
}
}
+11 -8
View File
@@ -2,12 +2,13 @@
import Card from '$/components/Card/Card.svelte';
import CopyButton from '$/components/CopyButton.svelte';
import CopyInput from '$/components/CopyInput.svelte';
import ExternalLinkWrapper from '$/components/ExternalLinkWrapper.svelte';
import { Button } from '$/components/ui/button';
import { Input } from '$/components/ui/input';
import { Separator } from '$/components/ui/separator';
import * as ToggleGroup from '$/components/ui/toggle-group';
import { TID } from '$/constants';
import { env } from '$/util/env';
import { getDomain } from '$/util/util';
import { browser } from '$app/environment';
import { waitForRender } from '$lib/util/autoSync';
import { inputStateStore, stateStore, urlsStore } from '$lib/util/state';
@@ -204,11 +205,11 @@ ${svgString}`);
<DownloadIcon />
{text}
</Button>
{#if url}
<ExternalLinkWrapper domain={getDomain(url)} isVisible={!!url}>
<Button class="rounded-l-none" href={url} target="_blank" rel="noreferrer noopener">
<ExternalLinkIcon />
</Button>
{/if}
</ExternalLinkWrapper>
</div>
{/snippet}
@@ -235,22 +236,24 @@ ${svgString}`);
<div class="flex gap-2">
{@render dualActionButton('PNG', onDownloadPNG, $urlsStore.png)}
{@render dualActionButton('SVG', onDownloadSVG, $urlsStore.svg)}
{#if env.krokiRendererUrl}
<ExternalLinkWrapper domain={getDomain($urlsStore.kroki)} isVisible={!!$urlsStore.kroki}>
<a target="_blank" rel="noreferrer" class="flex-grow" href={$urlsStore.kroki}>
<Button class="action-btn flex w-full items-center gap-2">
<ExternalLinkIcon /> Kroki
</Button>
</a>
{/if}
</ExternalLinkWrapper>
</div>
<Separator />
{#if isClipboardAvailable()}
<CopyButton onclick={onCopyClipboard} label="Copy Image" />
{/if}
{#if $urlsStore.mdCode}
<ExternalLinkWrapper
labelPrefix="Thumbnail generated by"
domain={getDomain($urlsStore.png)}
isVisible={!!$urlsStore.mdCode}>
<CopyInput value={$urlsStore.mdCode} label="Copy Markdown" testID={TID.copyMarkdown} />
{/if}
</ExternalLinkWrapper>
<div class="flex w-full items-center gap-2">
<Input type="url" bind:value={gistURL} placeholder="Enter Gist URL" />
<Button onclick={loadGist}>Load Gist</Button>
@@ -0,0 +1,59 @@
<script lang="ts">
import { stateStore } from '$/util/state';
import * as Tooltip from '$lib/components/ui/tooltip';
import type { ComponentProps, Snippet } from 'svelte';
import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded';
let {
children,
domain,
shouldCheckDiagramType = true,
side = 'bottom',
labelPrefix = 'Opens your diagram in',
isVisible = true,
sharesData = true
}: {
children: Snippet;
domain: string;
shouldCheckDiagramType?: boolean;
side?: ComponentProps<typeof Tooltip.Content>['side'];
labelPrefix?: string;
isVisible?: boolean;
sharesData?: boolean;
} = $props();
let shouldDisableComponent = $derived(
shouldCheckDiagramType && $stateStore.diagramType === 'zenuml'
);
</script>
{#if isVisible}
<Tooltip.Provider>
<Tooltip.Root delayDuration={100}>
<Tooltip.Trigger>
<div class={[shouldDisableComponent && 'pointer-events-none cursor-not-allowed grayscale']}>
{@render children()}
</div>
</Tooltip.Trigger>
<Tooltip.Content {side} class="bg-secondary shadow-xl">
<div
class="flex cursor-help items-center gap-2"
title={sharesData
? 'Your diagram will be sent to the external service'
: 'Your diagram is not shared'}>
{#if shouldDisableComponent}
<div class="text-muted-foreground">
This diagram type is not supported in {domain}
</div>
{:else}
<ExternalLinkIcon />
<span class="flex items-center gap-1">
{labelPrefix}
<div class="text-accent">{domain}</div>
</span>
{/if}
</div>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
{/if}
+13 -7
View File
@@ -29,16 +29,18 @@
href: $urlsStore.mermaidChart({ medium: 'main_menu' }).playground
},
{
label: 'Plugins',
icon: PluginIcon,
checkDiagramType: false,
href: $urlsStore.mermaidChart({ medium: 'main_menu' }).plugins,
checkDiagramType: false
icon: PluginIcon,
label: 'Plugins',
sharesData: false
},
{
label: 'MermaidChart',
icon: MermaidChartIcon,
checkDiagramType: false,
href: $urlsStore.mermaidChart({ medium: 'main_menu' }).home,
checkDiagramType: false
icon: MermaidChartIcon,
label: 'MermaidChart',
sharesData: false
}
]);
</script>
@@ -76,7 +78,11 @@
</div>
{#each mermaidChartMenuItems as item}
<McWrapper side="right" shouldCheckDiagramType={item.checkDiagramType}>
<McWrapper
side="right"
labelPrefix={item.sharesData === false ? 'Opens a new tab in' : undefined}
sharesData={item.sharesData}
shouldCheckDiagramType={item.checkDiagramType}>
{@render menuItem({
...item,
class: 'text-accent bg-background hover:bg-muted'
+10 -43
View File
@@ -1,50 +1,17 @@
<script lang="ts">
import { env } from '$/util/env';
import { stateStore } from '$/util/state';
import * as Tooltip from '$lib/components/ui/tooltip';
import type { ComponentProps, Snippet } from 'svelte';
import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded';
import type { ComponentProps } from 'svelte';
import ExternalLinkWrapper from './ExternalLinkWrapper.svelte';
let {
children,
shouldCheckDiagramType = true,
side = 'bottom',
labelPrefix = 'Opens a new tab in'
}: {
children: Snippet;
shouldCheckDiagramType?: boolean;
side?: ComponentProps<typeof Tooltip.Content>['side'];
labelPrefix?: string;
} = $props();
let shouldDisableComponent = $derived(
shouldCheckDiagramType && $stateStore.diagramType === 'zenuml'
);
...props
}: Omit<ComponentProps<typeof ExternalLinkWrapper>, 'isVisible' | 'domain'> = $props();
</script>
{#if env.isEnabledMermaidChartLinks}
<Tooltip.Provider>
<Tooltip.Root delayDuration={100}>
<Tooltip.Trigger>
<div class={[shouldDisableComponent && 'pointer-events-none cursor-not-allowed grayscale']}>
{@render children()}
</div>
</Tooltip.Trigger>
<Tooltip.Content {side}>
<div class="flex items-center gap-2">
{#if shouldDisableComponent}
<div class="text-muted-foreground">
This diagram type is not supported in MermaidChart.com
</div>
{:else}
<ExternalLinkIcon />
<span class="flex items-center gap-1">
{labelPrefix}
<div class="text-accent">MermaidChart.com</div>
</span>
{/if}
</div>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
{/if}
<ExternalLinkWrapper
{...props}
domain="MermaidChart.com"
isVisible={env.isEnabledMermaidChartLinks}>
{@render children()}
</ExternalLinkWrapper>
+1 -1
View File
@@ -96,7 +96,7 @@
Live Editor
</a>
<McWrapper labelPrefix="Opens the current diagram in">
<McWrapper>
<div class="hidden items-center justify-center gap-4 md:flex">
<Separator orientation="vertical" />
<Switch
+21 -29
View File
@@ -1,4 +1,5 @@
<script>
import ExternalLinkWrapper from '$/components/ExternalLinkWrapper.svelte';
import * as Dialog from '$/components/ui/dialog';
import ShieldIcon from '~icons/material-symbols/shield-lock-outline-rounded';
</script>
@@ -7,7 +8,7 @@
<Dialog.Trigger>
<ShieldIcon />
</Dialog.Trigger>
<Dialog.Content class="max-h-full overflow-y-auto">
<Dialog.Content class="max-h-full overflow-hidden overflow-y-auto p-12">
<Dialog.Header>
<Dialog.Title class="flex items-center gap-2 text-xl">
<ShieldIcon class="size-8 text-green-700" />
@@ -15,39 +16,30 @@
</Dialog.Title>
</Dialog.Header>
<p class="text-xl font-semibold">
The content of the diagrams you create never leaves your browser.
</p>
<p>It's only stored in the URL, and your browser's local storage.</p>
<p class="text-xl font-semibold">Your diagrams never leave your browser.</p>
<p>They're only stored in the URL and your browser's local storage.</p>
<p>
Mermaid Live Editor is a fully open source, client side application, deployed transparently on <a
This is a fully open source, client-side app deployed on <a
href="https://github.com/mermaid-js/mermaid-live-editor/deployments"
class="underline"
target="_blank">
GitHub Pages
</a>.
target="_blank">GitHub Pages</a>
that works offline as a
<a href="https://web.dev/explore/progressive-web-apps" target="_blank">Progressive Web App</a
>.
</p>
<p>
It will also work as a fully offline
<a href="https://web.dev/explore/progressive-web-apps" target="_blank">
Progressive Web App.
</a>
</p>
<p>
The only server we have is a self hosted version of the open source and privacy friendly
Plausible Analytics. We only collect anonymous data related to actions performed, like the
type of diagram rendered, number of times a feature was used, etc.
</p>
<p>
All the data we collect is anonymized and
<a href="https://p.mermaid.live/mermaid.live" class="underline" target="_blank">
available publicly.
</a>
</p>
<p>
Additional services like the external PNG/SVG/Kroki links and "Save to Mermaid Chart" feature
will share your diagram with the respective 3rd party service.
We use self hosted, privacy-friendly Plausible Analytics to collect anonymous usage metadata
(diagram types, feature usage, etc.). All data is <a
href="https://p.mermaid.live/mermaid.live"
class="underline"
target="_blank">publicly available</a
>.
</p>
<ExternalLinkWrapper domain="example.com" isVisible>
<p class="text-left">
External services (PNG/SVG/Kroki exports, "Save to Mermaid Chart", "Repair with AI", etc)
will share your diagram with those 3rd parties, and are highlighted in the UI on hover.
</p>
</ExternalLinkWrapper>
</Dialog.Content>
</Dialog.Root>
+6
View File
@@ -5,6 +5,12 @@ import { applyMigrations } from './migrations';
import { initURLSubscription, loadState, updateCodeStore, verifyState } from './state';
import { initAnalytics, plausible } from './stats';
export const getDomain = (url?: string): string => {
if (!url) return '';
const domain = new URL(url).hostname;
return domain;
};
export const loadStateFromURL = (): void => {
loadState(window.location.hash.slice(1));
};