Files
mermaid-live-editor/src/lib/util/util.ts
T
Sidharth VinodandClaude Fable 5 b3c8bd6c28 refactor: Migrate editor state from svelte/store to runes
Replace the writable+derived store pipeline in state.ts with a
state.svelte.ts module: a deep $state inputState mutated only through
the exported update functions, each of which persists the snapshot and
re-validates it asynchronously into validatedState.current (with
urls.current derived from it). Store subscriptions in components become
$effect blocks, and the rough/grid toggles use function bindings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:34:07 +05:30

126 lines
3.4 KiB
TypeScript

import { C } from '$/constants';
import { env } from './env';
import { loadDataFromUrl } from './fileLoaders/loader';
import { initLoading } from './loading';
import { isOnMermaidAI } from './migration/domainMigration';
import { applyMigrations } from './migrations';
import { initURLSubscription, loadState, updateCodeStore, verifyState } from './state.svelte';
import { getAnalyticsSafeUrl, 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));
};
export const syncDiagram = (): void => {
updateCodeStore({
updateDiagram: true
});
};
export const initHandler = async (): Promise<void> => {
applyMigrations();
loadStateFromURL();
await initLoading('Loading Gist...', loadDataFromUrl().catch(console.error));
syncDiagram();
initURLSubscription();
await initAnalytics();
plausible?.trackPageview({
url: getAnalyticsSafeUrl()
});
verifyState();
};
export const isMac = navigator.platform.toUpperCase().includes('MAC');
export const cmdKey = isMac ? 'Cmd' : 'Ctrl';
export const MCBaseURL = env.isEnabledMermaidChartLinks
? 'https://mermaid.ai' // 'http://localhost:5174'
: 'https://example.com';
const buildUtmParams = ({
utmCampaign,
utmMedium
}: {
utmCampaign: string;
utmMedium: string;
}): URLSearchParams =>
new URLSearchParams({
utm_campaign: utmCampaign,
utm_medium: utmMedium,
utm_source: getUTMSource()
});
export const getCheckoutUrl = (utm: { utmCampaign: string; utmMedium: string }): string => {
const params = buildUtmParams(utm);
params.set('coupon', 'arDfyFT8');
params.set('tier', 'plus');
return `${MCBaseURL}/app/user/billing/checkout?${params.toString()}`;
};
export const getMermaidAiLiveUrl = (utm: { utmCampaign: string; utmMedium: string }): string => {
return `${MCBaseURL}/live?${buildUtmParams(utm).toString()}`;
};
let count = 0;
export const errorDebug = (limit = 1000) => {
count += 1;
if (count > limit) {
console.log(count, limit);
// eslint-disable-next-line no-debugger
debugger;
}
};
export const formatJSON = (data: unknown): string => JSON.stringify(data, undefined, 2);
export const fetchJSON = async <T>(url: string): Promise<T> => {
const res = await fetch(url);
return res.json() as T;
};
export const fetchText = async (url: string): Promise<string> => {
const res = await fetch(url);
return res.text();
};
export const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
} catch {
fallbackCopyToClipboard(text);
}
};
function fallbackCopyToClipboard(text: string) {
const textArea = document.createElement('textarea');
textArea.value = text;
// Make the textarea out of viewport
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.append(textArea);
textArea.focus();
textArea.select();
try {
// The deprecated but widely supported method
document.execCommand('copy');
} catch (error) {
console.error('Failed to copy:', error);
throw error;
} finally {
textArea.remove();
}
}
export const getUTMSource = (): string => {
if (typeof window !== 'undefined' && isOnMermaidAI()) {
return C.aiLiveEditor;
}
return C.utmSource;
};