Compare commits

...
Author SHA1 Message Date
renovate[bot]andGitHub 5385962c79 fix(deps): update all non-major dependencies 2026-06-24 08:49:09 +00:00
Sidharth VinodandGitHub 335f4bbfde Merge pull request #1991 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2026-06-19 19:23:27 +00:00
renovate[bot]andGitHub bc235e7498 fix(deps): update all non-major dependencies 2026-06-19 09:54:22 +00:00
Sidharth VinodandGitHub 7f08e5b970 Merge pull request #1987 from mermaid-js/sidv/examplesSelection
feat: add example picker dropdown to sample diagrams
2026-06-10 18:43:03 +00:00
Sidharth VinodandClaude Fable 5 a257f78d8e feat: add example picker dropdown to sample diagrams
Each diagram type in the Sample Diagrams panel is now a split button:
clicking the diagram name loads its default example, and a dropdown
arrow on the right (shown when a diagram has more than one example)
opens a popover listing all examples for that diagram.

- getSampleDiagrams now returns all examples per diagram with the
  default example first, instead of flattening to the default's code
- loadSampleDiagram analytics now include the chosen example title

https://claude.ai/code/session_015rUgHChBEkquLu77fMxmQ6

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 00:09:45 +05:30
Sidharth VinodandGitHub 9cd92e1fd6 Merge pull request #1988 from mermaid-js/sidv/runes-migration-fixes
fix: Address regressions and structural risks in the runes migration
2026-06-10 18:35:50 +00:00
Sidharth VinodandClaude Fable 5 c253054916 refactor: Make persisted() values raw state
The getter handed out a deep $state proxy, so in-place mutation of a
persisted array/record would update the UI while silently never being
written to localStorage. All consumers already replace .value wholesale;
$state.raw makes that the only semantics and drops the per-read proxy
overhead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:57:27 +05:30
Sidharth VinodandClaude Fable 5 d2f067a540 refactor: Route every input state mutation through one untracked gateway
The untrack()/persistAndProcess() pair was a per-function convention:
each update function had to remember both, a forgotten untrack would let
calling effects subscribe to input state (the bug fixed in 6a9a306e),
and a forgotten persistAndProcess would silently skip persistence and
re-validation. A single update(mutate) gateway now makes both structural,
and the scattered untrack calls (three of which were dead) are gone.

Also:
- Export inputState as Readonly<State> so writes outside the update
  functions are type errors instead of comment violations.
- Share the 'codeStore' key as a constant instead of two literals.
- Reuse validatedStateOf() in processState instead of duplicating the
  validated-state defaults.
- Use $state.raw for validatedCurrent: it is only ever replaced
  wholesale, so deep proxying every published state was pure overhead.

The new state.svelte.test.ts pins the invariants: update functions never
make a calling effect track input state, and every mutation persists.
Vitest needed resolve.conditions=['browser'] for those tests — it was
loading Svelte's server build, where $effect is a no-op.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:57:01 +05:30
Sidharth VinodandClaude Fable 5 59105628f4 fix: Treat a stored JSON null as a missing persisted value
readJSON returned any successfully parsed value, so a localStorage
entry holding the literal "null" came back as null instead of the
fallback — and a null inputState crashes module init on every load
until storage is cleared. The deleted MacFJA persist layer guarded
this with `null !== initialValue`; restore that behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:53:00 +05:30
Sidharth VinodandClaude Fable 5 144b4dc190 fix: Stop mobile editor from reverting keystrokes
currentText was $state, and the validated-state sync $effect both reads
and writes it. Every keystroke (which sets currentText in the CodeMirror
updateListener) therefore re-ran the effect while re-validation was
still in flight, dispatching a full-document replacement with the stale
validatedState text — visibly reverting the keystroke and resetting the
cursor until validation caught up.

The effect only needs to react to validatedState publishes, so make
currentText a plain variable, matching DesktopEditor.

No unit test: the repo has no component-test harness, and CodeMirror
cannot mount under jsdom without one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:51:57 +05:30
Sidharth VinodandClaude Fable 5 6a9a306ed7 fix: Untrack input state reads inside state update functions
toggleDarkTheme runs inside the layout $effect; without untrack the
effect starts depending on inputState.mermaid and re-adds the default
theme whenever a loaded config replaces it (caught by the unsafe-config
scrub e2e test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:44:16 +05:30
Sidharth VinodandClaude Fable 5 337e9f86ac refactor: Use $app/state instead of $app/stores in error page
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:38:55 +05:30
Sidharth VinodandClaude Fable 5 c9cfaa10e7 refactor: Replace vendored svelte-persistent-store with shared persisted rune
History now uses the shared persist.svelte helpers, the MacFJA store
copy is deleted, and the esserializer dependency it required is dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:37:53 +05:30
Sidharth VinodandClaude Fable 5 ed06c07c06 refactor: Migrate hidden promotions store to the persisted rune
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:36:58 +05:30
Sidharth VinodandClaude Fable 5 a49503a1f0 refactor: Migrate the migrations store to the persisted rune
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:35:41 +05:30
Sidharth VinodandClaude Fable 5 81aaafc085 refactor: Migrate loading state from svelte/store to runes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:35:00 +05:30
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
Sidharth VinodandClaude Fable 5 ca96ca8771 feat: Add shared runes-based localStorage persistence module
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 00:23:48 +05:30
Sidharth VinodandGitHub 219d8af74f Merge pull request #1984 from mermaid-js/sidv/fixViewURL
fix: base URL handling
2026-06-09 19:40:49 +05:30
Sidharth Vinod 49c5928202 fix: Resolve URLs in urlsStore 2026-06-09 19:32:08 +05:30
Sidharth VinodandGitHub 459c22cf09 Merge pull request #1982 from mermaid-js/sidv/rewrite-timeline-history
fix: Rewrite Timeline/Saved history state and UI handling
2026-06-08 15:01:44 +00:00
Sidharth VinodandClaude Opus 4.8 f25b3c82cd refactor: De-duplicate mode routing and drop dead getStateString
Code-review cleanup:
- Collapse the two mode switches (the historyState.entries getter and
  activeSlot) into a single slotFor(mode) helper; entries now reads
  `slotFor(mode)?.value ?? loader`.
- Remove the now-unused getStateString export from state.ts (its only
  consumer was rewritten off it in this PR).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:28:50 +05:30
Sidharth VinodandClaude Opus 4.8 2db9355b7b fix: Accept time:0 entries and precompute history entry URLs
Code-review follow-ups:
- validateEntry no longer rejects entries whose time is 0 (epoch); it now
  checks `typeof time === 'number'`, so restoring/uploading such entries
  works instead of silently dropping them.
- History.svelte serializes each entry's "open in new tab" URL once via a
  $derived list instead of calling serializeState (pako deflate) per row on
  every render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:22:40 +05:30
Sidharth VinodandClaude Opus 4.8 8dd9cb49a0 feat: Add "open in new tab" link to history entries
Each history entry now has a third action: a real anchor (rendered via
the Button's href, target="_blank") linking to the editor with that
entry's serialized state. Being a normal link, users can also copy it or
open it in a new tab via the context menu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 20:03:31 +05:30
Sidharth VinodandClaude Opus 4.8 64e8822e32 fix: Make svelte-check pass and resolve TS6/Svelte 5.56 type errors
Adding the `svelte-check` CI gate surfaced pre-existing type errors and
warnings under develop's TypeScript 6 / Svelte 5.56 bump. Fixes them so
`pnpm check` reports 0 errors / 0 warnings:

- Add `lang="ts"` to Share/Privacy (and a script to PrivacyPolicyLink) so
  importers get real declarations instead of implicit `any`.
- Replace the deprecated `monaco.languages.json` with the new top-level
  `monaco.json` namespace (proper API, no casts).
- Navbar: use `resolve('/', {})` instead of the deprecated `base`.
- Type the promo `component` as `Component<{ closeBanner: Snippet }>` and
  MainMenu's `renderer` as `Snippet<[Omit<MenuItem, 'renderer'>]>`.
- Index-by-string casts in state.ts / Preset / DiagramDocumentationButton.
- Make Actions' clipboard handler accept an optional event.
- toggle-group: expose variant/size via getters to fix the
  state_referenced_locally warning.
- Make the History e2e save/delete cases change state via a sample
  diagram (deterministic) instead of editor typing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:53:25 +05:30
Sidharth Vinod a3a3f8ffbe Update .gitignore 2026-06-08 19:28:39 +05:30
Sidharth VinodandClaude Opus 4.8 e6333e9d7f refactor: Convert history state to Svelte 5 runes + add svelte-check
Migrate the Timeline/Saved history state module to idiomatic Svelte 5
runes and wire up type-checking.

- Rename history.ts -> historyState.svelte.ts and replace the Svelte
  stores with $state-backed reactive values, removing the get() calls
  (the only remaining one reads the external inputStateStore). A small
  localStorage-backed `persisted()` helper keeps the same keys, so user
  data is preserved. Consumers read via the reactive `historyState`
  getter object instead of store auto-subscriptions.
- The gist loader now replaces the in-memory revisions in one call
  (setLoaderEntries) instead of appending, so loading a new gist no
  longer accumulates stale revisions.
- Add svelte-check: new `check` script, a "Type check" step in the
  unit-tests CI workflow, and skipLibCheck so the check passes on the
  dependency .d.ts files. svelte-check reports 0 errors / 0 warnings.
- Add accessible labels to the History toggle and the per-item
  Restore/Delete buttons (a11y + testability).
- Unskip tests/history.spec.ts and rewrite it against the new UI:
  load-from-localStorage + restore, tab-highlight follows the mode,
  save/dedupe, auto-vs-manual isolation, and delete/clear. All pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:28:38 +05:30
Sidharth VinodandClaude Opus 4.8 3628acec02 fix: Rewrite Timeline/Saved history state and UI handling
The auto (Timeline) and manual (Saved) history handling had several bugs.
This rewrites the state module into a small, tested source-of-truth API and
turns History.svelte into a thin view.

Bugs fixed:
- Auto-saves no longer leak into the Saved list. addEntry now writes only to
  the store for its own type (previously every entry was also pushed to the
  manual store).
- Dedup is keyed on code + config via stateKey() instead of the whole input
  state, so volatile/view-only fields (renderCount, updateDiagram, pan/zoom)
  no longer cause spurious saves or hide real edits.
- The active-tab highlight tracks the history mode. History binds
  activeTabID={$historyModeStore} and Tabs derives the highlight reactively
  instead of mutating its prop once, which had frozen it on the first tab.
- Auto-save runs for the whole edit session via startAutoSave() in +page,
  with proper cleanup. Previously the interval lived in History.svelte, only
  ran while the panel was open, and was never cleared (leaking a new interval
  on every open).
- restoreEntries() routes each uploaded entry to the store matching its own
  type instead of dumping everything into one store by the first entry's type.

The persisted localStorage keys (autoHistoryStore, manualHistoryStore,
autoHistoryMode) are unchanged, so existing user data is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 19:27:23 +05:30
Sidharth VinodandGitHub ebced635c9 Merge pull request #1774 from mermaid-js/sidv/fixUndo
fix: #1753 Undo in Monaco
2026-06-08 13:16:56 +00:00
Sidharth VinodandCursor 1f0812496e fix: prevent state feedback loop when updating Monaco via executeEdits
Guard programmatic editor updates so sample diagrams and URL loads do not re-trigger onUpdate, which caused infinite state/URL churn and failing e2e tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 18:40:07 +05:30
Sidharth Vinod c62ebf6d09 fix: #1753 Undo in Monaco 2026-06-08 18:14:56 +05:30
Sidharth VinodandGitHub 6438d0074d Merge pull request #1719 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies to v10.34.1
2026-06-08 18:02:54 +05:30
Sidharth VinodandGitHub 130711a9d0 Merge pull request #1718 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies to ^24.12.4
2026-06-08 18:02:07 +05:30
renovate[bot]andGitHub 7c6e28e163 chore(deps): update all non-major dependencies to v10.34.1 2026-06-08 12:27:57 +00:00
renovate[bot]andGitHub 1b432bcaaa chore(deps): update all non-major dependencies to ^24.12.4 2026-06-08 12:27:15 +00:00
Sidharth VinodandGitHub da89994440 Merge pull request #1980 from mermaid-js/chore/update-all-deps
chore: update all dependencies to latest versions
2026-06-08 12:21:54 +00:00
Sidharth VinodandCursor c25675d418 chore: update all dependencies to latest versions
Bump the full dependency tree (Vite 8, Vitest 4, ESLint 10, Svelte 5.56,
mode-watcher 1.x, etc.) and adapt code for breaking API changes. Upgrade
Playwright CI image to v1.60 for Node 24 compatibility.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 17:47:38 +05:30
Sidharth VinodandGitHub a58cde9516 Merge pull request #1971 from mermaid-js/chore/node-24-lts
chore: Node.js 24 LTS and latest mermaid packages
2026-06-08 11:41:55 +00:00
Sidharth VinodandCursor ccf5a13018 fix: upgrade Playwright to 1.60 for Node 24.16 compatibility
Playwright <1.60 hangs on browser extraction under Node 24.16.0; bump
the test dependency and CI container image to unblock e2e.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 17:08:27 +05:30
Sidharth VinodandGitHub 4ab4e022a0 Merge branch 'develop' into chore/node-24-lts 2026-06-08 16:19:46 +05:30
Sidharth VinodandGitHub 52abdccc3a Merge pull request #1963 from IvanTheGeek/bug/1961_favicon-link-tags
fix(#1961): Correct favicon link tags
2026-06-08 08:22:51 +00:00
Ivan The Geek d134306482 fix: drop ico link tag — favicon.ico has opaque white corners
The .ico file is a single 32×32 frame with white (255,255,255,255)
pixels filling the rounded-corner regions instead of transparent
ones, so declaring it as an `<link rel="icon">` made Chrome render
the favicon with visible white corners on dark backgrounds. The
svg+xml type fix and apple-touch-icon link remain — those don't
involve the .ico.

Legacy browsers that need the .ico will still find it via the root
`/favicon.ico` auto-probe; regenerating the .ico from favicon.svg
so its content matches the rest is a separate asset change.
2026-06-07 13:34:40 -04:00
Sidharth VinodandCursor d040d8d84a chore: bump mermaid and related packages to latest
Upgrade mermaid 11.15.0, layout-elk 0.2.1, and layout-tidy-tree 0.2.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 01:07:52 +05:30
Sidharth VinodandCursor c2cd0c0bfc chore: upgrade Node.js to 24.16.0 LTS
Pin CI, Docker, and engines to the current Active LTS and bump @types/node.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 20:44:47 +05:30
Sidharth VinodandGitHub 2ff56f67bc Merge pull request #1965 from mermaid-js/sidv/editor-chooser-live-2026
Ship editor chooser testB with live_2026 UTM campaign
2026-05-29 16:02:47 +05:30
Sidharth VinodandCursor 55d9bd043f refactor: Inline editor chooser modal content
Remove the separate testB component now that the A/B test is over.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 15:49:44 +05:30
Sidharth VinodandCursor adf3ddf015 feat: Ship editor chooser testB and use live_2026 UTM campaign
End the A/B test by always showing the testB modal, remove unused variant
components and assignment logic, and route CTA links with utm_campaign=live_2026.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 15:45:04 +05:30
Sidharth VinodandGitHub bfea7dd4db Merge pull request #1964 from mermaid-js/sidv/close-broken-link-issues
chore: Auto-close issues with only "Broken link" in title
2026-05-28 00:42:19 +05:30
Sidharth VinodandCursor 7cd24ddcb3 chore: Clarify live editor vs Mermaid syntax issues in auto-close comment
Direct syntax error reports to mermaid-js/mermaid and limit this repo to live editor bugs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 00:41:27 +05:30
Sidharth VinodandCursor 241f99bacf chore: Auto-close issues with only "Broken link" in title
Add a GitHub Action that closes low-effort issues using the default pre-filled title and prompts reporters to include the failing URL and details.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-28 00:38:52 +05:30
Ivan The GeekandIvan The Geek 472a3283c8 fix(#1961): Correct favicon link tags
Three small markup fixes in src/app.html:

- Update the icon link's `type` from `image/png` to `image/svg+xml` so
  it matches its `favicon.svg` href. The MIME mismatch was introduced
  in #1065 when the href was switched from `.png` to `.svg` without
  updating `type`; browsers ignore the attribute and trust the
  Content-Type so it rendered fine, but the markup was incorrect.
- Add an explicit `image/x-icon` link for `favicon.ico`. The file
  already ships in `static/`, but it was previously discoverable only
  via the root `/favicon.ico` auto-probe.
- Add an `apple-touch-icon` link pointing at `favicon.png` (512×512)
  so iOS "Add to Home Screen" uses the Mermaid logo instead of a
  low-quality page screenshot.

No behavior change for existing desktop users.
2026-05-25 13:38:32 -04:00
Sidharth VinodandGitHub 7b43e33971 Merge pull request #1951 from ZenUml/chore/mermaid-zenuml-0.2.3
[codex] bump mermaid-zenuml to 0.2.3
2026-05-18 20:17:21 +05:30
MrCoder 6af70cb44c chore: bump mermaid-zenuml to 0.2.3 2026-05-14 16:56:18 +10:00
Sidharth Vinod 1fabea49cf chore: Update mermaid-examples 2026-04-23 14:40:49 +05:30
Sidharth VinodandGitHub 1bd6355ea8 Merge pull request #1938 from mermaid-js/sidv/editorChooserExperiments
feat(MC-4486): Add variations to editor picker
2026-04-23 14:32:44 +05:30
Sidharth VinodandGitHub 29eb7b01fc Merge pull request #1939 from mermaid-js/sidv/EnhancedEditButton
chore(MC-4489): Add enhanced edit button
2026-04-23 14:16:34 +05:30
Sidharth Vinod b446c37315 chore: Add enhanced edit button 2026-04-23 13:31:19 +05:30
Sidharth Vinod 031477cda9 fix: Update tagline and layout 2026-04-23 13:05:45 +05:30
Sidharth Vinod a9becf5509 fix: copy 2026-04-22 22:37:01 +05:30
Sidharth Vinod 79c4b3bf55 chore: Add noopener 2026-04-22 21:04:00 +05:30
Sidharth Vinod a61a69e477 fix: Show picker on netlify 2026-04-22 18:58:31 +05:30
Sidharth Vinod 1daac45c87 feat(MC-4486): Add variations to editor picker 2026-04-22 18:52:02 +05:30
Alois KlinkandGitHub e3562168ad Merge pull request #1929 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to ^11.14.0
2026-04-10 12:42:42 +00:00
renovate[bot]andGitHub 7fa7a48325 fix(deps): update dependency mermaid to ^11.14.0 2026-04-10 12:39:46 +00:00
Alois KlinkandGitHub 4925de94a9 Merge commit from fork
fix: improve sanitization of mermaid config
2026-04-10 21:35:24 +09:00
Sidharth Vinod 679e444bf1 chore: Remove canonical 2026-04-02 17:34:24 +05:30
Alois Klink 46a7a533ab fix: copy mermaid config sanitization check
Copy the XSS prevention check from the `sanitize` function in mermaid
upstream's `config.ts` file.

See: https://github.com/mermaid-js/mermaid/blob/9745f325cb9e1967640f0e85da193a2f820634f1/packages/mermaid/src/config.ts#L178-L183
2026-04-02 19:00:54 +09:00
Alois Klink 67aacdebe4 fix: sanitize config for __ keys
According to upstream, this is supposed to be fore prototype pollution
prevention.

See: https://github.com/mermaid-js/mermaid/blob/9745f325cb9e1967640f0e85da193a2f820634f1/packages/mermaid/src/config.ts#L169-L174
2026-04-02 19:00:54 +09:00
Alois Klink 7e9cdfca73 fix: sanitize all secure keys in config
Check and remove all the secure keys in the site Mermaid Config,
not just `securityLevel`. Unfortunately, the logic of Mermaid's
`sanitize` function in `config.ts` is a bit convoluted. For instance,
the `secure` config restricts keys deeply in the object.

See: https://github.com/mermaid-js/mermaid/blob/9745f325cb9e1967640f0e85da193a2f820634f1/packages/mermaid/src/config.ts#L155-L190
2026-04-02 19:00:54 +09:00
Alois Klink 48b9560e8e fix: sanitize config loaded from gist/config URL
Currently, the `config` in the codeState in the hash is sanitized for
unsafe values, however the `?config` URL parameter or configs loaded
from a GitHub Gist are not.

Reported-by: Chai Cheng Xun @QiaoNPC
2026-04-02 18:59:09 +09:00
Sidharth VinodandGitHub f9f1e27bc3 Merge pull request #1923 from mermaid-js/sidv/visualEditor
feat: Add visual editor button
2026-04-01 22:20:22 +05:30
Sidharth Vinod 62699769d5 fix: Use {} instead of undefined 2026-04-01 22:19:02 +05:30
Sidharth Vinod 5608dc5661 feat: Add visual editor button 2026-04-01 21:50:18 +05:30
Sidharth VinodandGitHub 29e92a9702 Merge pull request #1917 from mermaid-js/sidv/fixBasePath
fix(#1914): Resolve base path
2026-04-01 15:13:59 +00:00
Sidharth Vinod 0e0da3b1e9 chore: Reduce Error debounce interval 2026-04-01 20:43:15 +05:30
Sidharth Vinod d273e1d722 fix: Asset links 2026-03-30 21:30:13 +05:30
Sidharth Vinod c799dff042 fix(#1914): Resolve redirectURL considering base path 2026-03-30 21:24:33 +05:30
Alois Klink ae7ad1b93e refactor: move config sanitization into function
Moves the code that prompts the user to remove unsafe MermaidConfig
settings into it's own function, so that it can be reused.
2026-03-30 17:06:32 +09:00
Sidharth Vinod c675dc02e9 fix: Avoid double popups 2026-03-25 17:03:04 +09:00
Sidharth Vinod ab760f35fa feat: Retain search query in URL to get insights into traffic flow 2026-03-18 22:05:07 +09:00
Sidharth VinodandGitHub 8a436791de Merge pull request #1908 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to ^11.13.0
2026-03-16 07:41:08 -07:00
Sidharth VinodandGitHub d16a1776e6 Merge pull request #1909 from mermaid-js/sidv/correctPrivacyPolicy
feat: Configure Privacy Policy
2026-03-16 07:40:41 -07:00
Sidharth Vinod ab720e0267 fix: local test change 2026-03-16 23:33:20 +09:00
Sidharth Vinod d7db71e38a chore: Add new mermaid home button 2026-03-16 23:29:49 +09:00
Sidharth Vinod b466e8430e feat: Show mermaid.live privacy policy only on mermaid.live
As the OSS project only controls the mermaid.live domain, we're adding an env variable to let anyone who is self hosting live editor to set their own privacy policy, or to hide the button if they want to.
2026-03-16 23:29:13 +09:00
renovate[bot]andGitHub ea404df79b fix(deps): update dependency mermaid to ^11.13.0 2026-03-16 01:36:21 +00:00
Sidharth Vinod 15cdb7b64a chore: Show editor picker once to existing users as well 2026-03-12 13:35:42 +09:00
Sidharth Vinod 340911f271 chore: Remove panZoom event 2026-03-05 23:28:34 +05:30
Sidharth VinodandGitHub 44d15c0f5e Merge pull request #1901 from mermaid-js/claude/hide-editor-picker-mobile-2h7p0
Hide editor picker on mobile
2026-03-03 14:09:58 -08:00
Claude c36e51fb9f Hide editor picker dialog on mobile in shouldShowEditorChooser
Return false when viewport width is below 640px (Tailwind sm breakpoint),
matching the isMobile check used elsewhere in the app.

https://claude.ai/code/session_01KoPM7CZKWdwdZPm4MneGVE
2026-03-03 22:02:10 +00:00
Sidharth Vinod 82f7371fed fix: Do not show editor selector to users coming from docs site, as they have seen one already 2026-03-04 01:50:29 +05:30
Sidharth Vinod e56451e027 chore: Update coupon code 2026-03-04 01:43:34 +05:30
Sidharth VinodandGitHub 1ed126e684 Merge pull request #1898 from mermaid-js/sidv/fixTests
tests: Tweak failing tests
2026-03-03 11:41:35 -08:00
Sidharth Vinod a85bc09092 fix: Track mermaid.ai outflow 2026-03-04 01:10:12 +05:30
Sidharth Vinod 5cc29dfebe tests: Tweak failing tests 2026-03-04 00:32:16 +05:30
Sidharth VinodandGitHub 6079e5e1ba Merge pull request #1896 from mermaid-js/sidv/popup
Editor Chooser popup
2026-03-03 10:33:43 -08:00
Sidharth Vinod dabbf5700a test: Disable popup in tests 2026-03-03 23:45:51 +05:30
Sidharth Vinod 13e5e834a2 fix: Respect MERMAID_IS_ENABLED_MERMAID_CHART_LINKS for popup 2026-03-03 15:56:47 +05:30
Sidharth Vinod bf1a7581e8 chore: Add editor chooser dialog for first time users 2026-03-03 15:54:37 +05:30
Sidharth Vinod d092480cf3 chore: Auto redirect to new domain 2026-03-03 13:22:36 +05:30
Sidharth VinodandGitHub 94f7dafc72 Merge pull request #1886 from chouheiwa/fix/cjk-text-visibility-in-svg-export
fix: ensure CJK text visibility in SVG export
2026-02-16 21:11:50 +05:30
Sidharth VinodandGitHub 8d7db5501a Merge pull request #1883 from saurabhg772244/saurabh/add-mermaid-inline-ai
add mermaid inline AI
2026-02-16 20:44:51 +05:30
saurabhg772244 f6cf766986 added dark mode for icon 2026-02-16 19:36:20 +05:30
Sidharth VinodandGitHub 4bfe42458e Merge pull request #1887 from mermaid-js/sidv/promo
chore: Update promo URL
2026-02-16 16:46:25 +05:30
Sidharth Vinod f6f9668044 fix: Update couponID 2026-02-16 16:13:33 +05:30
Sidharth Vinod 2b13fde292 chore: Update promo URL 2026-02-16 15:55:21 +05:30
chouheiwa 5aed4ea7a0 fix: use viewBox dimensions for PNG export canvas size
In rough mode, SVG width/height are set to 100%, so getBoundingClientRect
returns the container size instead of the actual diagram size. Use viewBox
dimensions when available to get the correct aspect ratio for PNG export.
2026-02-16 16:19:22 +08:00
chouheiwa 5dd1ec6b63 fix: apply foreignObject clipping fix to PNG export path
Move fixForeignObjectClipping into getBase64SVG so it covers both
SVG and PNG export flows. Previously only getSvgElement (SVG export)
applied the fix, while exportImage (PNG export) passed the svg element
directly to getBase64SVG, bypassing the fix entirely.
2026-02-16 11:12:40 +08:00
chouheiwa 49f800bacb fix: apply foreignObject clipping fix to all languages in rough mode SVG export 2026-02-16 09:18:17 +08:00
chouheiwa 83ba64c8c8 fix: ensure CJK text visibility in SVG export 2026-02-15 17:31:32 +08:00
Sidharth VinodandGitHub 49629cfb61 Update Canonicals 2026-02-13 19:30:59 +05:30
saurabhg772244 6160617458 Fix AI inline popup issues and remove quick edit functionality 2026-02-13 13:54:39 +05:30
saurabhg772244 1166479277 remove unused global style 2026-02-12 20:02:48 +05:30
saurabhg772244 958e7aa62c close popup on outside click and escape button 2026-02-12 19:51:54 +05:30
saurabhg772244 aa3479d508 code refactor and fix icon hover issue 2026-02-12 15:52:21 +05:30
saurabhg772244 830df8c44b add mermaid inline AI 2026-02-12 13:13:16 +05:30
Sidharth VinodandGitHub 629d9639d2 Merge pull request #1882 from mermaid-js/sidv/promo
feat: Add new year promo
2026-02-02 15:33:56 +05:30
Sidharth Vinod 1a529f598f feat: Add new year promo 2026-02-02 15:33:02 +05:30
Sidharth Vinod f3d580b8eb fix: Update tiny tree 2026-01-29 14:22:08 +05:30
Sidharth VinodandGitHub 7414ac0926 Merge pull request #1878 from mermaid-js/sidv/updateMcDomain
feat: Hide tooltip based on domain
2026-01-28 12:02:01 +05:30
Sidharth VinodandGitHub c05fa21dcc Merge pull request #1877 from mermaid-js/sidv/updateDocsURL
chore: Make docs URL configurable during build
2026-01-28 12:01:52 +05:30
Sidharth VinodandGitHub 61e194517d Merge pull request #1875 from mermaid-js/sidv/removePlayground
chore: Remove playground toggle
2026-01-28 12:01:29 +05:30
Sidharth Vinod 9946dff29d feat: Hide tooltip based on domain 2026-01-28 11:52:30 +05:30
Sidharth Vinod d72e91a7b8 chore: Make docs URL configurable during build 2026-01-28 11:37:12 +05:30
Sidharth Vinod 832cf7d4e7 fix: lint errors 2026-01-28 00:35:02 +05:30
Sidharth Vinod b92fda6279 Merge branch 'develop' into sidv/removePlayground
* develop:
  fix: lint errors
  chore: update dependencies
2026-01-28 00:33:21 +05:30
Sidharth Vinod 7f551dbbe6 fix: lint errors 2026-01-28 00:32:44 +05:30
Sidharth Vinod 5dd2fe4bd0 chore: update dependencies 2026-01-28 00:32:32 +05:30
Sidharth Vinod d9e70427af chore: Remove playground toggle 2026-01-28 00:25:17 +05:30
Sidharth Vinod 739ef6dfe2 chore: remove trackOnLocalhost 2025-10-15 15:59:05 +05:30
Sidharth Vinod 48c560c979 fix: Catch panZoom errors 2025-10-15 15:58:04 +05:30
Sidharth Vinod b1367c2178 fix(#1791): Disable plausible init when analytics url is empty 2025-10-15 15:57:41 +05:30
Sidharth Vinod ef6aa4b09d feat: Add layout-tidy-tree 2025-10-15 15:25:43 +05:30
Sidharth Vinod ea6ae3d7e2 feat: debounce error section 2025-10-08 19:57:20 +05:30
Sidharth VinodandGitHub 01cd13c712 Merge pull request #1798 from junqwoopark/bug/1797_fix_export_background_color
fix(export): Remove redundant hsl() from background color
2025-09-18 20:21:25 +05:30
Sidharth VinodandGitHub e41499eda3 Merge pull request #1799 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to ^11.12.0
2025-09-18 20:08:22 +05:30
renovate[bot]andGitHub 6c08c490b1 fix(deps): update dependency mermaid to ^11.12.0 2025-09-18 09:41:15 +00:00
junqwoopark b237dd64eb fix(export): Remove redundant hsl() from background color 2025-09-07 20:59:34 +09:00
Sidharth VinodandGitHub e943ffad49 Merge pull request #1788 from PetrLaskevic/develop
fix: light background with dark diagram theme when downloading SVG ([#1777](https://github.com/mermaid-js/mermaid-live-editor/issues/1777))
2025-09-03 12:37:03 +00:00
Sidharth VinodandGitHub 0e203d3ebd Merge pull request #1795 from mermaid-js/sidv/mainMenu
Update Menu items
2025-09-03 12:31:15 +00:00
Sidharth Vinod c532b06b1a fix: Hover 2025-09-03 17:23:19 +05:30
Sidharth Vinod 7d709fecc5 fix: Add hamburger icon 2025-09-03 17:22:09 +05:30
Sidharth VinodandGitHub b87538cfe4 Merge branch 'develop' into sidv/mainMenu 2025-09-03 17:14:09 +05:30
Sidharth VinodandGitHub 7172054b91 Merge pull request #1793 from mermaid-js/sidv/updateDeps2
Update Deps
2025-09-03 17:13:43 +05:30
Sidharth Vinod c70bfe8cfa chore: Upgrade vitest 2025-09-03 16:59:41 +05:30
Sidharth Vinod fe7d04457a chore: lint fix 2025-09-03 16:49:35 +05:30
Sidharth Vinod 2c717b670e chore: Update menu 2025-09-03 16:47:14 +05:30
Sidharth Vinod cb062d49b1 chore: Update utils 2025-09-03 16:42:08 +05:30
Sidharth Vinod def809f6bd chore: Remove postcss 2025-09-03 16:38:03 +05:30
Sidharth Vinod 7da0081ace Update tailwind and shadcn 2025-09-03 16:36:09 +05:30
Sidharth Vinod ee6f595880 chore: Remove .eslintrc.cjs configuration file 2025-09-03 01:43:25 +05:30
Sidharth Vinod d991139b0a chore: Update eslint 2025-09-03 01:41:51 +05:30
Sidharth Vinod eb9121de12 chore: Update deps 2025-09-02 22:57:16 +05:30
PetrLaskevic 2aaa2e872f fix: light background with dark diagram theme when downloading SVG ([#1777](https://github.com/mermaid-js/mermaid-live-editor/issues/1777)) 2025-08-21 20:04:28 +02:00
Sidharth VinodandGitHub 6f26aec073 Merge pull request #1786 from aschaber1/develop
ci(docker-publish): :contruction_worker: fix quotes
2025-08-20 12:29:32 +05:30
Alexander Schaber f9a9686d76 ci(docker-publish): :contruction_worker: fix quotes 2025-08-20 08:49:04 +02:00
Sidharth VinodandGitHub 6b7a4803a4 Merge pull request #1761 from aschaber1/develop
ci(docker-publish): add arm64 builds and attestations
2025-08-20 12:06:34 +05:30
Sidharth Vinod 5817555d91 chore: Update browserlist 2025-08-20 11:59:49 +05:30
Sidharth VinodandGitHub 5a7ee057c3 Merge pull request #1783 from mermaid-js/sidv/updateMermaid
chore: Update mermaid version
2025-08-20 11:51:41 +05:30
Sidharth Vinod 75126f180d test: Update svg size 2025-08-20 11:47:59 +05:30
Sidharth Vinod 74777b3791 chore: Update mermaid version 2025-08-20 11:42:39 +05:30
Alexander Schaber c9c235b85f ci(docker-publish): 👷 Update Docker publish workflow metadata tagging
- Replace manual version checks with built-in conditions in docker/metadata-action
2025-08-18 18:54:38 +02:00
Alexander Schaber d7ce5aeb42 ci(docker-publish): 👷 Update workflow to trigger certain steps only on push events
- Modify conditions to ensure `docker/login-action` runs only on push
- Add condition to gate `docker/publish-action` execution on push event
2025-08-18 18:18:10 +02:00
Alexander Schaber 8221a1ca48 ci(docker-publish): 👷 Enhance Docker publish workflow security and permissions
- Configure required permissions for package registry and attestations
- Enable build provenance attestation using actions/attest-build-provenance@v2
2025-08-18 18:18:10 +02:00
Alexander Schaber 0ebad9e207 ci(docker-publish): 👷 enable arm64 builds 2025-08-18 18:18:10 +02:00
Sidharth VinodandGitHub ce50b65cde Merge pull request #1775 from mermaid-js/sidv/privacy
chore: Highlight external services
2025-08-11 19:49:24 +05:30
Sidharth Vinod ceaeb1eb23 chore: Organize imports 2025-08-05 01:08:14 +05:30
Sidharth Vinod cf3957c2a2 feat: Example highlight in Privacy 2025-08-05 01:00:18 +05:30
Sidharth Vinod 9fd3a032a9 refactor: Simplify labelPrefix logic 2025-08-05 00:47:24 +05:30
Sidharth Vinod f8842d2ce2 chore: Remove redundant prefix 2025-08-05 00:43:06 +05:30
Sidharth Vinod ee14854139 fix: Remove hardcoded domains 2025-08-05 00:42:01 +05:30
Sidharth Vinod 4d50ec39f3 chore: Clarify privacy statement 2025-08-05 00:28:47 +05:30
Sidharth Vinod 41874f6d44 feat: Wrap all 3rd party services in tooltip 2025-08-05 00:27:37 +05:30
Sidharth VinodandGitHub 2275404e9d Merge pull request #1701 from mermaid-js/sidv/examples
Add examples for all diagrams
2025-08-04 07:19:03 +00:00
Sidharth VinodandGitHub 956a584322 Merge branch 'develop' into sidv/examples 2025-08-04 12:45:45 +05:30
Sidharth Vinod cc308b81c9 test: Update size to match new diagram 2025-08-04 12:45:26 +05:30
Sidharth VinodandGitHub 19b82c3d44 Merge pull request #1769 from mermaid-js/sidv/disposeEditorModels
fix: Dispose editor models
2025-08-02 19:40:47 +00:00
Sidharth Vinod ae928c3713 chore: Run tests on merge queue 2025-08-03 01:10:09 +05:30
Sidharth Vinod 1a2d752ca4 test: Update diagram names 2025-08-02 19:29:46 +05:30
Sidharth Vinod d3e2d137ea Merge branch 'develop' into sidv/examples
* develop:
  fix: #1766 hide external buttons if env var is not set
  chore: Rename layout to ts
  fix(deps): update dependency mermaid to ^11.9.0
2025-08-02 19:19:44 +05:30
Sidharth VinodandGitHub 1da5a3c193 Merge pull request #1757 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to ^11.9.0
2025-08-02 13:47:49 +00:00
Sidharth VinodandGitHub 273a2c06c0 Merge pull request #1770 from mermaid-js/sidv/disableExternalButtons
fix: Disable external buttons based on env
2025-08-02 13:47:08 +00:00
Sidharth Vinod 3ace70a439 fix: #1766 hide external buttons if env var is not set 2025-08-02 19:14:32 +05:30
Sidharth Vinod bc4b24f049 chore: Rename layout to ts 2025-08-02 19:13:46 +05:30
Sidharth Vinod b5c5c8e7b0 fix: Dispose editor models to avoid model already exists issue when switching from mobile to desktop view 2025-08-02 18:58:58 +05:30
renovate[bot]andGitHub 6b09a7ef2d fix(deps): update dependency mermaid to ^11.9.0 2025-07-21 01:24:27 +00:00
Sidharth Vinod 58aa3c0b67 Merge branch 'develop' into sidv/examples
* develop:
  feat: Add Treemap support (mermaid 11.8.1)
  chore: Update text-size
  Chore: Update banner ad
  Enable MC Links
  fix: Trace path
  fix: Hide AI Repair button in config tab
  fix(deps): update all non-major dependencies
  Update as per PR suggestion
  chore: Cleanup tagline logic
  chore(deps): update dependency lucide-svelte to ^0.507.0
  chore: Update announcement bar
2025-07-16 19:59:34 +05:30
Sidharth VinodandGitHub b577f4b2d4 Merge pull request #1750 from mermaid-js/sidv/11.8.1
feat: Add Treemap support (mermaid 11.8.1)
2025-07-10 07:03:31 +00:00
Sidharth Vinod 1dbf0ab5f6 feat: Add Treemap support (mermaid 11.8.1) 2025-07-07 21:16:54 +05:30
Sidharth VinodandGitHub 99dd67e359 Merge pull request #1742 from mermaid-js/rajat-ht/chore-update-banner-ad
Chore: Update banner ad
2025-06-20 10:24:47 +05:30
rajat-ht 7475360115 chore: Update text-size 2025-06-19 16:06:29 +05:30
rajat-ht 470c704542 Chore: Update banner ad 2025-06-18 20:01:43 +05:30
Sidharth VinodandGitHub 0683cdbaa2 Merge pull request #1726 from mermaid-js/sidv/hideAIRepair
fix: Hide AI Repair button in config tab
2025-05-27 13:53:04 +00:00
Sidharth Vinod 569f3ca232 Enable MC Links 2025-05-27 18:43:17 +05:30
Sidharth Vinod 4052221168 fix: Trace path 2025-05-27 18:25:18 +05:30
Sidharth VinodandAlois Klink 400f3ecf95 fix: Hide AI Repair button in config tab
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-05-26 23:14:45 +05:30
Sidharth VinodandGitHub 5ce27dd981 Merge pull request #1714 from mermaid-js/renovate/all-minor-patch
chore(deps): update dependency lucide-svelte to ^0.507.0
2025-05-06 05:13:58 +00:00
Sidharth VinodandGitHub 91fc0895b2 Merge pull request #1712 from mermaid-js/update-announcement-bar
chore: Update announcement bar
2025-05-06 10:43:47 +05:30
Sidharth VinodandGitHub 48c23374a4 Merge pull request #1713 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2025-05-06 10:43:31 +05:30
renovate[bot]andGitHub 829ee28cf9 fix(deps): update all non-major dependencies 2025-05-06 01:45:13 +00:00
rajat-ht 39876254ca Update as per PR suggestion 2025-05-05 23:50:20 +05:30
Sidharth Vinod 0b5a038c9d chore: Cleanup tagline logic 2025-05-05 21:40:43 +05:30
renovate[bot]andGitHub cc702bb556 chore(deps): update dependency lucide-svelte to ^0.507.0 2025-05-05 01:48:03 +00:00
rajat-ht c9e61440f4 chore: Update announcement bar 2025-05-02 18:18:52 +05:30
Sidharth Vinod 4d1c6e4702 Merge branch 'develop' into sidv/examples
* develop:
  chore(deps): update all non-major dependencies
  fix(deps): update all non-major dependencies
  fix: #1659 Frozen UI with comment regex
2025-05-02 02:39:16 +05:30
Sidharth VinodandGitHub 881648a87e Merge pull request #1703 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-05-02 00:58:57 +05:30
renovate[bot]andGitHub d3c8efd712 chore(deps): update all non-major dependencies 2025-05-01 16:01:56 +00:00
Sidharth VinodandGitHub 9777e1782f Merge pull request #1702 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2025-05-01 15:59:58 +00:00
renovate[bot]andGitHub 9391b17366 fix(deps): update all non-major dependencies 2025-05-01 13:15:09 +00:00
Sidharth VinodandGitHub 6b42be2841 Merge pull request #1710 from mermaid-js/sidv/1659
fix: #1659 Frozen UI with comment regex
2025-05-01 18:43:11 +05:30
Sidharth Vinod 6a07387688 fix: #1659 Frozen UI with comment regex 2025-05-01 17:57:40 +05:30
Sidharth Vinod d963baf96e Merge branch 'develop' into sidv/examples
* develop: (32 commits)
  feat: Support jsonSchema to validate config
  link: live version
  fix: Fallback page
  chore(deps): update dependency vite to v5.4.18 [security]
  fix: Fallback to sveltekit on 404 in GH pages
  chore(deps): update all non-major dependencies
  fix: New diagram link
  chore(deps): update dependency @sveltejs/kit to v2.20.6 [security]
  feat: Wait for fontawesome to load
  chore(deps): update all non-major dependencies
  feat: Support pan while zooming
  Update src/lib/util/panZoom.ts
  chore: Track PWA install device type
  chore: Track PWA installs
  chore: Add analytics for mobileViewToggle
  fix: Reduce gap in mobile
  fix: SVG size to avoid
  chore: Unify code update logic
  chore: Cleanup deps
  feat: Add syntax highlighting
  ...
2025-04-20 13:42:04 +05:30
Sidharth Vinod 70ea4f59a0 chore: Update @mermaid-js/examples version 2025-04-20 13:41:17 +05:30
Sidharth VinodandGitHub 4350d7e13a Merge pull request #1700 from mermaid-js/sidv/jsonSchema
feat: Support jsonSchema to validate config
2025-04-19 13:52:47 +00:00
Sidharth Vinod 2eaf136c75 feat: Support jsonSchema to validate config 2025-04-19 12:11:59 +05:30
Sidharth VinodandGitHub 5c35d5e589 Merge pull request #1698 from jsoref/docs/link
chore: Improve screen reader link for `live version`
2025-04-18 12:25:05 -07:00
Josh Soref e90ef3af9e link: live version
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-04-18 08:39:00 -04:00
Sidharth Vinod 0479af715b Merge branch 'develop' of https://github.com/mermaid-js/mermaid-live-editor into develop
* 'develop' of https://github.com/mermaid-js/mermaid-live-editor:
  chore(deps): update dependency vite to v5.4.18 [security]
  chore(deps): update all non-major dependencies
2025-04-16 16:26:21 +05:30
Sidharth Vinod 8aafb909a6 fix: Fallback page 2025-04-16 16:26:15 +05:30
Sidharth VinodandGitHub c9f6816fb6 Merge pull request #1685 from mermaid-js/renovate/npm-vite-vulnerability
chore(deps): update dependency vite to v5.4.18 [security]
2025-04-16 10:47:30 +00:00
renovate[bot]andGitHub e1e7552462 chore(deps): update dependency vite to v5.4.18 [security] 2025-04-16 10:47:13 +00:00
Sidharth VinodandGitHub 64787974d8 Merge pull request #1690 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-04-16 03:46:18 -07:00
Sidharth Vinod 1052420e34 fix: Fallback to sveltekit on 404 in GH pages 2025-04-16 16:14:47 +05:30
renovate[bot]andGitHub a54971bc56 chore(deps): update all non-major dependencies 2025-04-16 10:12:03 +00:00
Sidharth VinodandGitHub f27f4b336c Merge pull request #1691 from mermaid-js/renovate/npm-sveltejs-kit-vulnerability
chore(deps): update dependency @sveltejs/kit to v2.20.6 [security]
2025-04-16 03:10:35 -07:00
Sidharth Vinod bf25d3cc60 fix: New diagram link 2025-04-16 15:39:57 +05:30
renovate[bot]andGitHub 87af8a8c30 chore(deps): update dependency @sveltejs/kit to v2.20.6 [security] 2025-04-14 19:30:27 +00:00
Sidharth VinodandGitHub df6c1cceee Merge pull request #1688 from mermaid-js/sidv/faLoad
feat: Wait for fontawesome to load before rendering
2025-04-14 14:31:59 +00:00
Sidharth VinodandGitHub 987e09d8bc Merge pull request #1680 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-04-13 12:41:21 +00:00
Sidharth VinodandAlois Klink 6ae68fc546 feat: Wait for fontawesome to load
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-13 18:03:59 +05:30
renovate[bot]andGitHub 22444a2000 chore(deps): update all non-major dependencies 2025-04-13 08:27:39 +00:00
Sidharth VinodandGitHub 92f029830f Merge pull request #1687 from mermaid-js/sidv/mobile
feat: Add mobile support
2025-04-13 08:25:59 +00:00
Sidharth Vinod 8b13bf48e7 feat: Support pan while zooming 2025-04-13 13:50:30 +05:30
285baa4c83 Update src/lib/util/panZoom.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-13 01:16:56 -07:00
Sidharth Vinod 7c22576783 chore: Track PWA install device type 2025-04-13 13:41:26 +05:30
Sidharth Vinod e11017672f chore: Track PWA installs 2025-04-13 13:40:50 +05:30
Sidharth Vinod f4a736db62 chore: Add analytics for mobileViewToggle 2025-04-13 13:38:02 +05:30
Sidharth Vinod 2b242befe1 fix: Reduce gap in mobile 2025-04-13 13:34:51 +05:30
Sidharth VinodandAlois the eagle Klink 3039d5cb47 fix: SVG size to avoid
Co-authored-by: Alois "the eagle" Klink <alois@aloisklink.com>
2025-04-13 13:16:44 +05:30
Sidharth Vinod 4bf6dc0db6 chore: Unify code update logic 2025-04-13 13:12:30 +05:30
Sidharth Vinod 795663aafa chore: Cleanup deps 2025-04-13 12:01:15 +05:30
Sidharth Vinod bd1ca7e2ab feat: Add syntax highlighting 2025-04-13 11:57:53 +05:30
Sidharth Vinod 89f648ab8f chore: Update theme-color 2025-04-13 10:13:38 +05:30
Sidharth Vinod ff1a0d97d3 chore: Update og:image 2025-04-13 10:13:11 +05:30
Sidharth Vinod a11aae3573 chore: Update theme color 2025-04-13 10:02:11 +05:30
Sidharth Vinod 6c8402948a feat: Support touch pan & zoom 2025-04-13 09:51:39 +05:30
Sidharth Vinod a5df32931c fix: Responsive breakpoints, remove zoom icons in mobile 2025-04-13 09:51:22 +05:30
Sidharth Vinod 835231d291 fix: Promo bar alignment 2025-04-13 09:50:35 +05:30
Sidharth Vinod 4c31e1fe5d fix: Orientation in PWA manifest 2025-04-13 09:50:13 +05:30
Sidharth VinodandAlois Klink ebff808b31 fix: Privacy popup size
Co-authored-by: Alois Klink <alois@aloisklink.com>
2025-04-13 01:31:31 +05:30
Sidharth Vinod 224296c1d8 fix: padding 2025-04-13 01:27:38 +05:30
Sidharth Vinod 4ec52d9282 chore: Simplify pane logic 2025-04-13 01:25:18 +05:30
Sidharth Vinod 74ad9f44d0 feat: Add mobile support 2025-04-12 20:00:52 +05:30
Sidharth Vinod a0e54ff7bd chore: Use mermaid examples package to populate samples 2025-04-12 17:27:24 +05:30
Sidharth VinodandGitHub 96dddeb34d Merge pull request #1681 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-04-12 09:07:24 +00:00
renovate[bot]andGitHub 6528a0a26a chore(deps): update all non-major dependencies 2025-04-11 22:52:35 +00:00
Sidharth Vinod c530984c97 chore: Simplify URL params setting 2025-04-03 23:24:54 +05:30
Sidharth VinodandGitHub 643a7ca760 Merge pull request #1676 from mermaid-js/sidv/consolidateUTMSource
fix: Consolidate UTM source
2025-04-03 10:49:37 -07:00
Sidharth Vinod aa0d62ba11 fix: Consolidate UTM source 2025-04-03 23:16:20 +05:30
Sidharth VinodandGitHub 110b95d854 Merge pull request #1674 from mermaid-js/promo-links-v2
Update promo bar urls
2025-04-03 17:03:28 +00:00
Steph db0e97b988 update promo bar urls 2025-04-03 08:59:14 -07:00
Sidharth VinodandGitHub a2965bc3ca Merge pull request #1672 from mermaid-js/trackConversion
MC-2931 Add UTM params to MC URLs
2025-04-02 12:21:31 -07:00
Sidharth Vinod a24c66b554 Merge branch 'develop' into trackConversion
* develop:
  fix URLS
2025-04-02 23:54:51 +05:30
Sidharth Vinod b17e136225 MC-2931 Add utm params to MC URLs 2025-04-02 23:50:12 +05:30
Sidharth VinodandGitHub 3be0ad92f5 Merge pull request #1670 from mermaid-js/update-promo-links
Promo bars - fix URLS
2025-04-02 10:56:07 -07:00
Steph a35b1afa41 fix URLS 2025-04-02 08:59:02 -07:00
Sidharth VinodandGitHub 4d1a0ae07b Merge pull request #1667 from mermaid-js/renovate/npm-vite-vulnerability
chore(deps): update dependency vite to v5.4.16 [security]
2025-04-02 02:31:12 -07:00
renovate[bot]andGitHub b7b8d02dd0 chore(deps): update dependency vite to v5.4.16 [security] 2025-04-02 09:30:26 +00:00
Sidharth VinodandGitHub 12b0a27cf8 Merge pull request #1653 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-04-02 09:29:16 +00:00
renovate[bot]andGitHub cd5a858dcc chore(deps): update all non-major dependencies 2025-04-02 09:23:00 +00:00
Sidharth VinodandGitHub 034fccaebb Merge pull request #1663 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to ^11.6.0
2025-04-02 02:20:34 -07:00
Sidharth VinodandGitHub 8fd44b8d5b Merge pull request #1652 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-04-02 02:20:17 -07:00
Sidharth VinodandGitHub a5c4036927 Merge pull request #1665 from mermaid-js/announcement-bar
Update announcement bar
2025-04-02 02:19:48 -07:00
Sidharth Vinod 72e8e583a9 chore: Update fade delay 2025-04-02 14:46:59 +05:30
renovate[bot]andGitHub 5befc1221c fix(deps): update dependency mermaid to ^11.6.0 2025-04-02 09:15:27 +00:00
renovate[bot]andGitHub bc57d8660e chore(deps): update all non-major dependencies 2025-04-02 09:15:01 +00:00
Sidharth VinodandGitHub 5aa0d4a921 Merge branch 'develop' into announcement-bar 2025-04-02 02:14:03 -07:00
Sidharth Vinod 8320bb0651 fix: Type issue on lint 2025-04-02 14:43:45 +05:30
Sidharth VinodandGitHub 50180ed72a Merge branch 'develop' into announcement-bar 2025-04-02 01:58:16 -07:00
Sidharth Vinod 1f702e4960 fix: Update download image size to match CI 2025-04-02 14:24:58 +05:30
Steph af4ef4865b update announcement bar 2025-04-01 23:29:33 -07:00
Sidharth Vinod 37716245f7 fix #1655: Reset pan & zoom state if invalid 2025-03-26 09:36:28 -07:00
Sidharth VinodandGitHub 097f6f0f34 Merge pull request #1633 from mermaid-js/sidv/redesign
Redesign
2025-03-24 10:40:44 -07:00
Sidharth Vinod 0eda1c0b8b fix: Temp hack for zenuml 2025-03-24 10:34:42 -07:00
Sidharth Vinod 3124112006 fix: OptimizeDeps 2025-03-20 12:29:12 -07:00
Sidharth Vinod ee446eb2b8 fix: Dark mode code icon 2025-03-20 12:21:26 -07:00
Sidharth Vinod 866ca5f747 chore: Tweak popup text 2025-03-20 12:17:15 -07:00
Sidharth Vinod 41019ffa9a chore: Minor design tweaks 2025-03-20 12:07:28 -07:00
Sidharth Vinod 64e156085b test: Fix tests 2025-03-19 19:54:42 -07:00
Sidharth Vinod 501c983b99 test: Fix tests 2025-03-19 19:10:51 -07:00
Sidharth Vinod 3005deffc5 chore: Fix lint error 2025-03-18 13:00:08 -07:00
Sidharth Vinod f166156b86 Merge branch 'develop' into sidv/redesign
* develop:
  chore: Update script name
  chore: Remove cypress
  chore: Ignore cypress
  chore: Update test exclude
  chore: Tweak test config
  chore: Ignore cypress
  chore: Fix pw issues
  chore: Add playwright tests
2025-03-18 12:59:32 -07:00
Sidharth Vinod 9250ac3eaa feat: Change Switch to Button for grid and rough 2025-03-18 12:58:25 -07:00
Sidharth Vinod 6111a40dc3 chore: Center empty history text 2025-03-18 12:57:50 -07:00
Sidharth VinodandGitHub 93d02aadc0 Merge pull request #1649 from mermaid-js/sidv/pw
chore: Add playwright tests
2025-03-18 12:57:23 -07:00
Sidharth Vinod c97c6c2199 chore: Fix icon path 2025-03-18 11:55:05 -07:00
Sidharth Vinod b3093087f6 chore: Fix icon path 2025-03-18 11:54:09 -07:00
Sidharth Vinod d29ad6345b fix: Update MC Icon 2025-03-18 11:51:19 -07:00
Sidharth Vinod 7c725556b0 chore: Tweak render delay 2025-03-18 11:42:49 -07:00
Sidharth Vinod df26e5176f chore: Fix spacing in main menu 2025-03-18 11:16:30 -07:00
Sidharth Vinod 3b2c7caf07 fix: New diagram button, add MC links 2025-03-18 11:11:31 -07:00
Sidharth Vinod d039f6534a feat: Disable mermaidchart on unsupported diagrams 2025-03-18 10:51:08 -07:00
Sidharth Vinod 4d08026b77 fix: Add collapse icon to card 2025-03-18 10:50:37 -07:00
Sidharth Vinod 9d1df6126b chore: Fix timeline height 2025-03-18 10:12:11 -07:00
Sidharth Vinod ac308717f9 feat: Update theme icon animation 2025-03-17 23:45:14 -07:00
Sidharth Vinod 4c0de566f0 chore: Update script name 2025-03-17 22:16:30 -07:00
Sidharth Vinod 324dd0b4b2 chore: Remove cypress 2025-03-17 21:27:33 -07:00
Sidharth Vinod 5ed07028c7 chore: Ignore cypress 2025-03-17 20:04:15 -07:00
Sidharth Vinod 9f06808026 chore: Update test exclude 2025-03-17 20:03:07 -07:00
Sidharth Vinod 23c93e9e92 chore: Tweak test config 2025-03-17 19:59:34 -07:00
Sidharth Vinod 65faee55b7 chore: Ignore cypress 2025-03-17 19:51:38 -07:00
Sidharth Vinod 87e0b91543 chore: Fix pw issues 2025-03-17 19:51:04 -07:00
Sidharth Vinod bb084867fa chore: Add playwright tests 2025-03-17 19:47:13 -07:00
Sidharth Vinod b7da3f80f7 chore: Remove testing-library
It wasn't used for any relevant tests, and svelte5 support is spotty.
2025-03-17 18:15:23 -07:00
Sidharth Vinod 893f8625d5 chore: Update snapshots 2025-03-17 13:30:36 -07:00
Sidharth Vinod 355f1929b2 chore: Sort classes 2025-03-17 13:27:44 -07:00
Sidharth VinodandSteph 059a1a98ab Update docs button target
Co-authored-by: Steph <35910788+huynhicode@users.noreply.github.com>
2025-03-17 13:04:47 -07:00
Sidharth Vinod ea0fc8a621 Merge branch 'develop' into sidv/redesign
* develop:
  chore(deps): update all non-major dependencies
  fix(deps): update all non-major dependencies
2025-03-17 12:47:24 -07:00
Sidharth VinodandGitHub 631cc43f2b Merge pull request #1647 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-03-17 12:44:07 -07:00
Sidharth Vinod d0b2cf49da chore: Update imports, cleanup classes 2025-03-17 12:43:32 -07:00
Sidharth Vinod bbb1fd2dae chore: Update security verbiage 2025-03-17 12:42:38 -07:00
Sidharth Vinod 176698a299 chore: Set MC Popup to bottom 2025-03-17 12:42:04 -07:00
Sidharth Vinod 4dbe52126e chore: Cleanup editor 2025-03-17 12:41:40 -07:00
Sidharth Vinod 3fd8c43212 chore: Unify copy button 2025-03-17 12:41:18 -07:00
renovate[bot]andGitHub 75c4764da8 chore(deps): update all non-major dependencies 2025-03-17 19:10:34 +00:00
Sidharth VinodandGitHub afd5aaaada Merge pull request #1646 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2025-03-17 12:09:32 -07:00
Sidharth Vinod cd6f1ca613 fix: Reduce sync threshold, increase wait time 2025-03-17 11:19:05 -07:00
Sidharth Vinod 8da3cdedd9 fix: Use McWrapper, fix PNG export 2025-03-17 11:18:40 -07:00
Sidharth Vinod a72e13bb5c fix: FA icons 2025-03-17 10:16:05 -07:00
Sidharth Vinod 9ddc37add5 chore: Fix png export background 2025-03-17 09:39:51 -07:00
Sidharth Vinod cb267911f5 chore: Fix primary dark mode 2025-03-17 09:30:14 -07:00
renovate[bot]andGitHub d6eb0533c6 fix(deps): update all non-major dependencies 2025-03-17 16:23:21 +00:00
Sidharth Vinod b76fa8106e Merge branch 'develop' into sidv/redesign
* develop:
  chore: Update deps
  chore(deps): update all non-major dependencies
  chore: Prettier ignore pnpm-lock
  chore: Update build dependencies
  chore: Update actions
  chore: Update node version
  chore: Switch from yarn to pnpm
  fix(deps): update dependency mermaid to v11.5.0
2025-03-17 09:21:04 -07:00
Sidharth VinodandGitHub 4a8dbfadba Merge pull request #1644 from mermaid-js/sidv/pnpm
chore: Switch from yarn to pnpm
2025-03-16 19:49:53 -07:00
Sidharth Vinod 15df6a2fcb chore: Update deps 2025-03-16 19:47:10 -07:00
Sidharth Vinod 506128c5df Merge branch 'develop' into sidv/pnpm
* develop:
  chore(deps): update all non-major dependencies
  fix(deps): update dependency mermaid to v11.5.0
2025-03-16 19:45:42 -07:00
Sidharth VinodandGitHub 1195ea94b3 Merge pull request #1642 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-03-16 19:43:06 -07:00
renovate[bot]andGitHub 90894781cd chore(deps): update all non-major dependencies 2025-03-17 02:42:56 +00:00
Sidharth VinodandGitHub 18cd3c4591 Merge pull request #1643 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to v11.5.0
2025-03-16 19:42:04 -07:00
Sidharth Vinod 4091da60c6 chore: Prettier ignore pnpm-lock 2025-03-16 19:41:19 -07:00
Sidharth Vinod 4a143ae8e2 chore: Update build dependencies 2025-03-16 19:40:25 -07:00
Sidharth Vinod 2159375e81 chore: Update actions 2025-03-16 19:37:05 -07:00
Sidharth Vinod 5742878799 chore: Update node version 2025-03-16 19:33:45 -07:00
Sidharth Vinod 592c3addac chore: Switch from yarn to pnpm 2025-03-16 19:19:10 -07:00
renovate[bot]andGitHub 9beb411c14 fix(deps): update dependency mermaid to v11.5.0 2025-03-17 00:35:39 +00:00
Sidharth Vinod 21be4caf82 Merge branch 'develop' into sidv/redesign
* develop:
  chore: Fix coverage dependency
  chore: sort gitignore
  chore: Add redirect to home on 404 error
  chore: Update subdomain
  chore: Update import
  chore: Remove redundant config
  chore: Add CNAME
  chore: Remove lint after build
  chore: Use official action to deploy to pages
  build: update Browserslist db
  fix: use `<output>` and `<pre>` to format errors
  Revert "feat: Sanitize error message"
2025-03-16 17:34:39 -07:00
Sidharth Vinod 6e7c7467a3 fix: PanZoom 2025-03-16 17:32:14 -07:00
Sidharth VinodandGitHub b5be8ca4fc Merge pull request #1639 from mermaid-js/browserslist-update
build: update Browserslist db
2025-03-15 20:35:47 -07:00
Sidharth Vinod 66b4a427b7 chore: Fix coverage dependency 2025-03-15 20:35:03 -07:00
Sidharth Vinod 3a65f2382d chore: sort gitignore 2025-03-15 20:31:15 -07:00
Sidharth Vinod c677afc04a chore: Add redirect to home on 404 error 2025-03-15 20:30:41 -07:00
Sidharth Vinod 4973083133 chore: Update subdomain 2025-03-15 20:09:21 -07:00
Sidharth Vinod 9288094c30 chore: Update import 2025-03-15 20:00:13 -07:00
Sidharth Vinod fe428c7cea chore: Remove redundant config 2025-03-15 19:55:50 -07:00
Sidharth Vinod 423fe7e0d6 chore: Add CNAME 2025-03-15 19:49:00 -07:00
Sidharth Vinod c9905606a1 chore: Remove lint after build 2025-03-15 19:32:37 -07:00
Sidharth VinodandGitHub e0cbcd21a8 Merge pull request #1637 from mermaid-js/release-promotion
Release live editor
2025-03-15 19:29:41 -07:00
Sidharth Vinod 214f6d0862 chore: Use official action to deploy to pages 2025-03-15 19:25:47 -07:00
GitHub Action 80526e58f7 build: update Browserslist db 2025-03-15 02:45:22 +00:00
Sidharth Vinod b2bb5b2bef chore: Minor cleanup 2025-03-14 19:37:25 -07:00
Sidharth Vinod c9efb9e017 fix: body style 2025-03-14 19:06:21 -07:00
Sidharth Vinod 34b078e7e8 fix: Grid color 2025-03-14 19:06:06 -07:00
Sidharth VinodandGitHub e9bc0c24de Merge pull request #1496 from aloisklink/fix/stop-parsing-syntax-error-as-html
Use `<output>` and `<pre>` to format mermaid errors
2025-03-15 07:30:21 +05:30
Sidharth Vinod bfc7535d54 chore: Remove outOfSync 2025-03-14 18:50:25 -07:00
Sidharth Vinod dcdc19d15e chore: Simplify class def 2025-03-14 18:16:44 -07:00
Sidharth Vinod a9db7e0513 Merge branch 'develop' into sidv/redesign
* develop:
  fix: Flickering when rendering, PanZoom & Rough mode combined
  chore: Simplify queueing logic
  fix: Blank screen on initial load
2025-03-14 18:14:36 -07:00
Sidharth VinodandGitHub d92c1e5e4c Merge pull request #1636 from mermaid-js/release-promotion
Release live editor
2025-03-15 06:39:13 +05:30
Sidharth Vinod 99255ab693 Merge branch 'develop' into pr/aloisklink/1496
* develop: (101 commits)
  fix: Flickering when rendering, PanZoom & Rough mode combined
  chore: Simplify queueing logic
  fix: Blank screen on initial load
  chore: Add delay for playground toggle
  fix: Record correct diagram type, send playground toggle event immediately.
  chore(deps): update dependency autoprefixer to v10.4.21
  chore(deps): update all non-major dependencies
  chore(deps): update dependency typescript to v5.8.2
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  Use netlify to redirect users
  chore: Add sitemap to robots.txt
  fix: Add redirect for /index.html
  chore: Add sitemap
  fix: Do not index /view page
  chore(deps): update all non-major dependencies
  feat: Pause banner animation when mouse is on the banner
  fix: Layout for banner
  use snippets
  update urls and styling
  ...
2025-03-14 18:08:52 -07:00
Sidharth VinodandGitHub ec40c0369c Merge pull request #1635 from mermaid-js/sidv/panZoomRough
fix: Flickering when rendering, PanZoom & Rough mode combined
2025-03-15 06:36:16 +05:30
Sidharth Vinod a56be6ae14 fix: Flickering when rendering, PanZoom & Rough mode combined 2025-03-14 18:00:12 -07:00
Sidharth VinodandGitHub e29170a1aa Merge pull request #1634 from mermaid-js/release-promotion
Release live editor
2025-03-15 06:02:18 +05:30
Sidharth Vinod d7cd301e15 chore: Simplify queueing logic 2025-03-14 17:26:29 -07:00
Sidharth Vinod 393a5f659e fix: Blank screen on initial load
Queued the rendering pipeline
2025-03-14 15:51:37 -07:00
Sidharth Vinod 71a336c41f fix: Rendering race condition 2025-03-14 15:40:14 -07:00
Sidharth Vinod e394525376 fix: Diagram rendering 2025-03-14 15:20:12 -07:00
Sidharth Vinod 6175561449 chore: Simplify reactivity for tabs 2025-03-14 13:58:31 -07:00
Sidharth Vinod feb3ba88a5 format: eslint + prettier 2025-03-14 11:24:44 -07:00
Sidharth Vinod da75a4951a format: prettier 2025-03-14 11:03:35 -07:00
Sidharth Vinod 02ef7eab78 chore: Simplify docs button 2025-03-14 11:03:10 -07:00
Sidharth Vinod f951ad055b feat: Remove autoSync, add grid toggle 2025-03-13 23:35:59 -07:00
Sidharth Vinod 9e453e84f6 feat: MermaidChart tooltip 2025-03-13 23:17:06 -07:00
Sidharth Vinod 4e7c42f59a fix: Icons 2025-03-13 23:16:56 -07:00
Sidharth Vinod 44e44ede97 feat: MermaidChart tooltip 2025-03-13 22:03:16 -07:00
Sidharth Vinod b624ecb859 feat: Error popup 2025-03-13 20:21:38 -07:00
Sidharth Vinod b13e351460 feat: Full screen view 2025-03-13 19:59:21 -07:00
Sidharth Vinod 30abb2303b fix: Imports 2025-03-13 19:59:08 -07:00
Sidharth Vinod bb29c4af7f fix: Share modal 2025-03-13 19:51:46 -07:00
Sidharth Vinod 30cb61b966 feat: Share modal 2025-03-13 19:50:41 -07:00
Sidharth Vinod 7abe8f82b1 format: prettier 2025-03-13 17:42:52 -07:00
Sidharth Vinod 393e7efbda chore: Simplify history pane 2025-03-13 17:03:19 -07:00
Sidharth Vinod 83715bb250 Merge branch 'develop' into sidv/redesign
* develop:
  chore: Add delay for playground toggle
  fix: Record correct diagram type, send playground toggle event immediately.
  chore(deps): update dependency autoprefixer to v10.4.21
  chore(deps): update all non-major dependencies
  chore(deps): update dependency typescript to v5.8.2
  chore(deps): update all non-major dependencies
  build: update Browserslist db
2025-03-13 15:21:54 -07:00
Sidharth Vinod 6a55d67c4e feat: Tweak links 2025-03-13 15:18:38 -07:00
Sidharth Vinod f16180ec59 feat: Responsiveness 2025-03-13 15:14:42 -07:00
Sidharth Vinod 2883031609 feat: MainMenu 2025-03-13 14:47:13 -07:00
Sidharth Vinod b921101cd5 feat: MainMenu 2025-03-13 13:39:49 -07:00
Sidharth Vinod 1e89e2b4be feat: Privacy popup 2025-03-13 13:22:22 -07:00
Sidharth Vinod 050dafa409 feat: Navbar 2025-03-13 13:07:13 -07:00
Sidharth Vinod 0532e403bf feat: Add history section 2025-03-13 13:01:47 -07:00
Sidharth Vinod 0f3cfbdeac fix: Card icons 2025-03-13 10:26:31 -07:00
Sidharth Vinod 21644297d1 fix: Toolbar spacing and colors 2025-03-13 10:26:14 -07:00
Sidharth Vinod 3d279652b4 format: prettier 2025-03-13 10:23:38 -07:00
Sidharth Vinod c3faf2ff42 fix: Reduce overlap of diagram and toolbars 2025-03-13 10:22:36 -07:00
Sidharth Vinod 72616fca7a fix: Responsiveness of editor 2025-03-13 10:21:02 -07:00
Sidharth VinodandGitHub 51b187be6a Merge pull request #1620 from mermaid-js/release-promotion
Release live editor
2025-03-13 21:54:54 +05:30
Sidharth VinodandGitHub e91e190601 Merge pull request #1629 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-03-13 16:24:09 +00:00
Sidharth VinodandGitHub 40c66ce6e3 Merge pull request #1628 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update dependency autoprefixer to v10.4.21
2025-03-13 16:23:53 +00:00
Sidharth VinodandGitHub 57fc6ede1e Merge pull request #1632 from mermaid-js/sidv/FixAnalytics
fix: Analytics
2025-03-13 21:53:18 +05:30
Sidharth Vinod 62f60867f2 chore: Add delay for playground toggle 2025-03-13 09:22:30 -07:00
Sidharth Vinod 91331bda7e fix: Record correct diagram type, send playground toggle event immediately. 2025-03-13 09:11:46 -07:00
renovate[bot]andGitHub 86c7425271 chore(deps): update dependency autoprefixer to v10.4.21 2025-03-13 15:50:33 +00:00
Sidharth Vinod 73dd347bab Merge branch 'develop' of https://github.com/mermaid-js/mermaid-live-editor into develop
* 'develop' of https://github.com/mermaid-js/mermaid-live-editor:
  chore(deps): update dependency typescript to v5.8.2
  chore(deps): update all non-major dependencies
  build: update Browserslist db
2025-03-13 08:46:19 -07:00
renovate[bot]andGitHub b9060ddc31 chore(deps): update all non-major dependencies 2025-03-13 15:02:07 +00:00
Sidharth Vinod fcc7a03650 feat: Migrate to Material icons with Iconify, partial 2025-03-12 21:17:36 -07:00
Sidharth Vinod 2fae6f7798 feat: Floating toolbars 2025-03-11 22:32:59 -07:00
Sidharth Vinod d8cdf2f42c feat: Floating toolbars 2025-03-10 21:28:08 -07:00
Sidharth Vinod f608761fa2 feat: Dark mode 2025-03-10 21:27:50 -07:00
Sidharth Vinod e33aed9cc6 fix: Action panel 2025-03-07 17:00:16 +09:00
Sidharth Vinod 3c2acccf52 fix: Action & Preset stacking 2025-03-07 11:13:30 +07:00
Sidharth Vinod 0cd51750b8 fix: Imports 2025-03-07 03:04:42 +05:30
Sidharth Vinod f6ce710c1b fix: Theme, actions 2025-03-07 03:04:24 +05:30
Sidharth Vinod 433a301776 fix: Preset spacing 2025-03-06 23:17:08 +05:30
Sidharth Vinod bb184a20ca fix: Card padding 2025-03-06 18:53:41 +05:30
Sidharth Vinod 7ca468d42f fix: Actions panel 2025-03-06 18:48:40 +05:30
Sidharth Vinod 5d2f1e4973 fix: Remove overlap of promo 2025-03-06 18:21:18 +05:30
Sidharth Vinod db2e8e0297 fix: Change font 2025-03-06 18:15:20 +05:30
Sidharth Vinod 235d3c252d fix: Sample diagrams 2025-03-06 18:15:08 +05:30
Sidharth Vinod 9934429c4f fix: Cards 2025-03-06 14:30:18 +05:30
Sidharth Vinod 1f55da1f45 fix: Resize handle, diagram view 2025-03-06 12:41:52 +05:30
Sidharth Vinod 9c5ef434dc fix: Resizing, card 2025-03-06 12:24:19 +05:30
Sidharth Vinod 4968e224ae chore: Migrate navbar 2025-03-06 11:58:43 +05:30
Sidharth VinodandGitHub ac812ca1e4 Merge pull request #1619 from mermaid-js/renovate/all-minor-patch
chore(deps): update dependency typescript to v5.8.2
2025-03-03 12:31:36 +00:00
renovate[bot]andGitHub c1ecd9cd04 chore(deps): update dependency typescript to v5.8.2 2025-03-03 06:25:23 +00:00
Sidharth VinodandGitHub 68371dae63 Merge pull request #1617 from mermaid-js/browserslist-update
build: update Browserslist db
2025-03-03 06:24:29 +00:00
Sidharth VinodandGitHub 009e4f2390 Merge pull request #1618 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-03-03 06:24:19 +00:00
renovate[bot]andGitHub 50696445cd chore(deps): update all non-major dependencies 2025-03-03 02:46:19 +00:00
GitHub Action bd7ca5a25a build: update Browserslist db 2025-03-01 02:52:22 +00:00
Sidharth Vinod b8aa272bc6 fix: Promo bar 2025-03-01 00:17:09 +05:30
Sidharth Vinod 66566bd6af chore: Add shadcn/button 2025-03-01 00:16:50 +05:30
Sidharth Vinod 3075b63d4a chore: Add shadcn 2025-03-01 00:16:38 +05:30
Sidharth VinodandGitHub c20996488d Merge pull request #1615 from mermaid-js/release-promotion
Release live editor
2025-02-25 07:47:29 +01:00
Sidharth VinodandGitHub 0871de7838 Merge pull request #1613 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-02-25 07:41:56 +01:00
Sidharth VinodandGitHub 20cadcab79 Merge pull request #1614 from mermaid-js/sidv/indexing
Indexing fixes
2025-02-25 07:41:44 +01:00
Sidharth Vinod fb5894e093 Use netlify to redirect users 2025-02-25 12:08:54 +05:30
Sidharth Vinod fa0498f75d chore: Add sitemap to robots.txt 2025-02-25 08:54:26 +05:30
Sidharth Vinod cdc7f9faa4 fix: Add redirect for /index.html 2025-02-25 08:50:03 +05:30
Sidharth Vinod 51591ba075 chore: Add sitemap 2025-02-25 08:49:46 +05:30
Sidharth Vinod 78ccc2ff4c fix: Do not index /view page 2025-02-25 08:49:25 +05:30
renovate[bot]andGitHub fe8e51fa79 chore(deps): update all non-major dependencies 2025-02-24 19:55:23 +00:00
Sidharth VinodandGitHub e0b660d56b Merge pull request #1610 from mermaid-js/release-promotion
Release live editor
2025-02-21 22:43:23 +05:30
Sidharth VinodandGitHub 03404e8e59 Merge pull request #1611 from mermaid-js/promos
Update promos
2025-02-21 17:06:40 +00:00
Sidharth Vinod edc36351d4 feat: Pause banner animation when mouse is on the banner 2025-02-21 21:23:21 +05:30
Sidharth Vinod 66c155bab7 fix: Layout for banner 2025-02-21 21:02:47 +05:30
Steph ee3f4420f7 use snippets 2025-02-21 05:41:03 -08:00
Sidharth VinodandGitHub b35ce74065 Merge pull request #1606 from mermaid-js/browserslist-update
build: update Browserslist db
2025-02-21 13:44:41 +05:30
Sidharth VinodandGitHub 31616cadc0 Merge pull request #1607 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-02-21 08:14:21 +00:00
Sidharth VinodandGitHub 7389a9fdad Merge pull request #1608 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2025-02-21 13:44:08 +05:30
Steph 96c8333bbb update urls and styling 2025-02-20 16:01:49 -08:00
renovate[bot]andGitHub f45b04e16a chore(deps): update all non-major dependencies 2025-02-20 12:31:22 +00:00
renovate[bot]andGitHub 5c0c471ff6 chore(deps): update all non-major dependencies 2025-02-18 18:48:44 +00:00
GitHub Action fccfd6fbe2 build: update Browserslist db 2025-02-15 02:40:36 +00:00
Sidharth VinodandGitHub 9a70c08a6f Merge pull request #1602 from mermaid-js/release-promotion
Release live editor
2025-02-14 13:46:19 +05:30
Sidharth VinodandGitHub 77234d95b3 Merge pull request #1605 from mermaid-js/update-promos
Update promos
2025-02-14 13:45:46 +05:30
Sidharth Vinod 1df59ae5dd chore: Cleanup class 2025-02-14 13:39:04 +05:30
Sidharth Vinod 7e043e7e7e Merge branch 'update-promos' of https://github.com/mermaid-js/mermaid-live-editor into update-promos
* 'update-promos' of https://github.com/mermaid-js/mermaid-live-editor:
  update interval
2025-02-14 13:27:30 +05:30
Sidharth Vinod 8ddf56191d chore: Add padding to link 2025-02-14 13:27:22 +05:30
Steph 942840fb62 update interval 2025-02-13 15:41:19 -08:00
Sidharth Vinod bb224b486a fix: Position of text when switching, data structure for links 2025-02-13 18:26:26 +05:30
Steph 23bb5d63dc update urls 2025-02-13 03:57:41 -08:00
Steph abf80da2c1 wip 2025-02-13 03:46:28 -08:00
Sidharth VinodandGitHub 276b49e554 Merge pull request #1601 from mermaid-js/sidv/hidePromotions
Timebox Hidden Promotions
2025-02-05 19:04:05 +05:30
Sidharth VinodandGitHub bb7600f8c5 Merge pull request #1579 from mermaid-js/renovate/all-minor-patch
fix(deps): update all non-major dependencies (minor)
2025-02-04 23:44:47 +05:30
Sidharth Vinod 0b1dd4b06d chore: Cleanup 2025-02-04 23:41:48 +05:30
Sidharth Vinod dc815cdc4d feat: Add hide duration for promotions 2025-02-04 23:39:59 +05:30
Sidharth VinodandGitHub c5eaf340b4 Merge pull request #1595 from mermaid-js/release-promotion
Release live editor
2025-02-04 21:00:21 +05:30
renovate[bot]andGitHub 4cead1811f fix(deps): update all non-major dependencies 2025-02-04 15:28:23 +00:00
Sidharth Vinod 1b7065609b Merge branch 'master' into develop
* master:
  update end date
  update-promo
2025-02-04 20:57:19 +05:30
Sidharth VinodandGitHub e98a7b8647 Merge pull request #1585 from mermaid-js/renovate/patch-mermaid-packages
fix(deps): update dependency mermaid to v11.4.1
2025-02-04 20:56:23 +05:30
renovate[bot]andGitHub 922f82cc31 fix(deps): update dependency mermaid to v11.4.1 2025-02-04 15:26:02 +00:00
Sidharth VinodandGitHub f5f5038c69 Merge pull request #1599 from mermaid-js/browserslist-update
build: update Browserslist db
2025-02-04 20:55:23 +05:30
Sidharth VinodandGitHub d6288608be Merge pull request #1600 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-02-04 20:55:09 +05:30
renovate[bot]andGitHub d36571491f chore(deps): update all non-major dependencies 2025-02-04 12:46:37 +00:00
Sidharth Vinod a7e14c77cb Update banner timing 2025-02-04 18:03:13 +05:30
GitHub Action dab0e8880f build: update Browserslist db 2025-02-01 02:41:49 +00:00
Sidharth VinodandGitHub f2e2fc4e11 Merge pull request #1583 from mermaid-js/renovate/npm-sveltejs-kit-vulnerability
chore(deps): update dependency @sveltejs/kit to v2.8.3 [security]
2025-01-27 19:05:15 +05:30
Sidharth VinodandGitHub 54b7655015 Merge pull request #1592 from mermaid-js/renovate/npm-vite-vulnerability
chore(deps): update dependency vite to v5.4.12 [security]
2025-01-27 19:05:01 +05:30
Sidharth VinodandGitHub 681f434574 Merge pull request #1596 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2025-01-27 19:04:49 +05:30
renovate[bot]andGitHub 2d7252c640 chore(deps): update all non-major dependencies 2025-01-27 00:36:08 +00:00
renovate[bot]andGitHub e97c727170 chore(deps): update dependency vite to v5.4.12 [security] 2025-01-26 04:39:11 +00:00
renovate[bot]andGitHub 4233a1e21e chore(deps): update dependency @sveltejs/kit to v2.8.3 [security] 2025-01-26 04:39:02 +00:00
Sidharth VinodandGitHub 8735b0f52b Merge pull request #1575 from mermaid-js/browserslist-update
build: update Browserslist db
2025-01-26 10:07:45 +05:30
Sidharth VinodandGitHub 975e34b2fd Merge pull request #1578 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2025-01-26 10:07:34 +05:30
renovate[bot]andGitHub c77fc8c979 fix(deps): update all non-major dependencies 2025-01-23 20:30:38 +00:00
GitHub Action f3b62ce674 build: update Browserslist db 2025-01-15 02:39:27 +00:00
Sidharth VinodandGitHub cc562549a9 Merge pull request #1590 from mermaid-js/update-promo-bar
Update promo bar
2025-01-06 12:17:16 +05:30
Steph 2438363c03 update end date 2025-01-03 12:02:44 -08:00
Steph bd9625a3f9 update-promo 2025-01-03 10:39:00 -08:00
GitHub Action 7df3df748b build: update Browserslist db 2025-01-01 02:51:07 +00:00
GitHub Action e37253645c build: update Browserslist db 2024-12-15 03:02:55 +00:00
GitHub Action 502e0b5fc3 build: update Browserslist db 2024-12-01 03:09:36 +00:00
GitHub Action 13d7f878f1 build: update Browserslist db 2024-11-15 02:52:27 +00:00
Sidharth VinodandGitHub bb0ab7c7e2 Merge pull request #1574 from mermaid-js/release-promotion
Release live editor
2024-11-14 20:44:46 +05:30
Sidharth VinodandGitHub 939ef8b56e Merge pull request #1572 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-11-14 20:42:14 +05:30
renovate[bot]andGitHub 1e6e994413 chore(deps): update all non-major dependencies 2024-11-14 15:05:37 +00:00
Sidharth VinodandGitHub ba1c0da871 Merge pull request #1573 from mermaid-js/playground
MC Playground Toggle
2024-11-14 20:34:40 +05:30
Sidharth Vinod 5b103acb11 chore: Update snapshots 2024-11-14 19:09:42 +05:30
Sidharth Vinod b658ceedc9 chore: Update URL 2024-11-14 17:27:10 +05:30
Sidharth Vinod dc114d55c9 fix: Unit test 2024-11-14 13:53:28 +05:30
Sidharth Vinod 90716ef8d5 chore: Update labels 2024-11-14 13:50:46 +05:30
Sidharth Vinod f954dfab61 feat: Retain toggle label positions 2024-11-13 21:07:57 +05:30
Sidharth VinodandGitHub 877cbdb6f9 Merge pull request #1570 from mermaid-js/release-promotion
Release live editor
2024-11-12 00:14:29 +05:30
Sidharth Vinod 09e705c3ae Merge branch 'develop' into playground
* develop:
  fix: Add fallback for state
  update whiteboard tagline
  remove august promo
  update dates
  update taglines
  remove product hunt promo
  add and update product hunt promo for whiteboard
  add product hunt badge to navbar
2024-11-11 16:56:59 +05:30
Sidharth Vinod 83f3c07f10 Merge branch 'master' into develop
* master:
  update whiteboard tagline
  remove august promo
  update dates
  update taglines
  remove product hunt promo
  add and update product hunt promo for whiteboard
  add product hunt badge to navbar
2024-11-11 16:55:26 +05:30
Sidharth Vinod 3e3a0162bf fix: Add fallback for state 2024-11-11 16:54:03 +05:30
Sidharth Vinod 8c1c348f68 chore: Cleanup 2024-11-11 01:47:43 +05:30
Sidharth Vinod f7f3e35fcf feat: Add dropdown navbar items 2024-11-11 01:45:12 +05:30
Sidharth Vinod 5acd3faacc fix: Add fallback for state 2024-11-08 22:46:54 +05:30
Sidharth Vinod 4c24b52e12 feat: Add mermaidchart playground toggle 2024-11-08 22:30:47 +05:30
Sidharth VinodandGitHub 8fd965624f Merge pull request #1565 from mermaid-js/sidv/svelte5
Svelte 5
2024-11-08 22:16:16 +05:30
Sidharth Vinod e2d7fd46ac chore: Update action versions 2024-11-08 22:00:52 +05:30
Sidharth Vinod 09616ed156 chore: Fix unit test 2024-11-08 21:42:57 +05:30
Sidharth Vinod 118f03385e Merge branch 'develop' into sidv/svelte5
* develop:
  chore(deps): update all non-major dependencies
  chore(deps): update dependency vite to v5.4.6 [security]
  fix(deps): update all non-major dependencies
  test: increase file size limit
  fix(deps): update dependency mermaid to v11.4.0
  build: update Browserslist db
2024-11-08 21:36:10 +05:30
Sidharth VinodandGitHub ee156c7e69 Merge pull request #1543 from mermaid-js/renovate/npm-vite-vulnerability
chore(deps): update dependency vite to v5.4.6 [security]
2024-11-08 21:19:18 +05:30
Sidharth VinodandGitHub e80f0865f5 Merge pull request #1539 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-11-08 21:19:01 +05:30
renovate[bot]andGitHub aa9e9c8a23 chore(deps): update all non-major dependencies 2024-11-08 10:15:08 +00:00
renovate[bot]andGitHub 75697b7ce1 chore(deps): update dependency vite to v5.4.6 [security] 2024-11-08 10:14:47 +00:00
Sidharth VinodandGitHub 98f7b1b3d1 Merge pull request #1546 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2024-11-08 15:44:00 +05:30
renovate[bot]andGitHub 84db2c7c6a fix(deps): update all non-major dependencies 2024-11-08 10:01:04 +00:00
Sidharth VinodandGitHub d8401a8f13 Merge pull request #1563 from mermaid-js/release-promotion
Release live editor
2024-11-08 15:29:57 +05:30
Sidharth VinodandGitHub 6816bd986c Merge pull request #1567 from mermaid-js/browserslist-update
build: update Browserslist db
2024-11-08 15:29:32 +05:30
Sidharth VinodandGitHub 95c858936d Merge pull request #1568 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to v11.4.0
2024-11-08 15:29:20 +05:30
Sidharth Vinod c67b84a458 test: increase file size limit 2024-11-08 14:52:57 +05:30
Sidharth VinodandGitHub 765c35265f Merge pull request #1564 from mermaid-js/update-whiteboard-tagline
Promo bar: update Whiteboard tagline
2024-11-06 11:39:57 +05:30
renovate[bot]andGitHub ecc34aabc5 fix(deps): update dependency mermaid to v11.4.0 2024-11-04 02:04:17 +00:00
GitHub Action 6517053b8e build: update Browserslist db 2024-11-01 02:54:51 +00:00
Sidharth Vinod 190bac8c70 Svelte 5 migration 2024-10-30 23:38:20 +05:30
Steph cfdb4fbf61 update whiteboard tagline 2024-10-30 09:20:01 -07:00
Sidharth Vinod 8cf4ac432d Allow props 2024-10-30 01:33:03 +05:30
Sidharth VinodandGitHub 5ba65bc032 Merge pull request #1557 from mermaid-js/browserslist-update
build: update Browserslist db
2024-10-30 00:33:48 +05:30
Sidharth VinodandGitHub 2fe3df56b7 Merge pull request #1559 from mermaid-js/promo-updates
Remove Product Hunt promos and Update taglines
2024-10-22 20:10:23 +05:30
Steph 659e82e9ae remove august promo 2024-10-22 07:35:08 -07:00
Steph 76c937a878 update dates 2024-10-22 00:12:07 -07:00
Steph 98cbcf1abe update taglines 2024-10-22 00:07:40 -07:00
Steph 6bdabf9325 remove product hunt promo 2024-10-21 23:32:40 -07:00
GitHub Action 4c06db65f4 build: update Browserslist db 2024-10-15 02:49:53 +00:00
Sidharth VinodandGitHub cc963db471 Merge pull request #1554 from mermaid-js/release-promotion
Release live editor
2024-10-05 09:49:22 +05:30
Sidharth VinodandGitHub 8190a9433f Merge pull request #1528 from bluprince13/feature/1511_ability-to-disable-data-share-buttons
Disable buttons/links to share data with other websites depending on environment variables
2024-10-04 22:55:04 +05:30
Sidharth VinodandGitHub 4415849a6d Merge branch 'develop' into feature/1511_ability-to-disable-data-share-buttons 2024-10-04 14:45:39 +05:30
Sidharth VinodandGitHub d5123c6a26 Merge pull request #1553 from mermaid-js/release-promotion
Release live editor
2024-10-03 21:19:19 +05:30
Sidharth Vinod 60e2b307e9 Bump mermaid@11.3.0 2024-10-03 21:17:14 +05:30
Sidharth VinodandGitHub 1c04d7d77b Merge pull request #1551 from mermaid-js/browserslist-update
build: update Browserslist db
2024-10-03 21:16:21 +05:30
Sidharth VinodandGitHub f59a2dc6d7 Merge pull request #1552 from mermaid-js/promo-whiteboard
Product Hunt promo - Whiteboard
2024-10-03 15:55:12 +05:30
Steph 3b0fae39c9 add and update product hunt promo for whiteboard 2024-10-03 01:50:54 -07:00
GitHub Action 6e262b3ea4 build: update Browserslist db 2024-10-01 02:54:11 +00:00
Sidharth Vinod 267c6160be chore: Remove unused destructuring 2024-09-29 12:46:27 +05:30
8fb2114831 Update src/lib/util/promos/promo.ts
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-09-28 18:19:51 +01:00
7bae587c8c Update src/routes/edit/+page.svelte
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2024-09-28 18:19:39 +01:00
Vipin Ajayakumar f66331852e Disable buttons/links to share data with other websites depending on env variables 2024-09-28 16:12:43 +01:00
Sidharth VinodandGitHub c06828dbf2 Merge pull request #1541 from mermaid-js/release-promotion
Release live editor
2024-09-16 22:15:28 +05:30
Sidharth Vinod e4092a6be2 chore: Update mermaid to 11.2.1 2024-09-16 22:14:18 +05:30
Sidharth VinodandGitHub 9ffa715d03 Merge pull request #1540 from mermaid-js/release-promotion
Release live editor
2024-09-16 11:31:39 +05:30
Sidharth VinodandGitHub 8fdd959079 Merge pull request #1538 from mermaid-js/browserslist-update
build: update Browserslist db
2024-09-16 11:20:22 +05:30
Sidharth VinodandGitHub 887545323d Merge pull request #1534 from nnmrts/develop
update mermaid dependencies
2024-09-16 05:45:47 +00:00
Sidharth Vinod bc72088fe9 chore: Fix test 2024-09-16 11:14:16 +05:30
GitHub Action 2077d37d3a build: update Browserslist db 2024-09-15 02:45:48 +00:00
Nano Miratus 4781d46c03 update dependencies 2024-09-09 21:38:28 +02:00
Sidharth VinodandGitHub 4a08f6dad5 Merge pull request #1527 from mermaid-js/add-product-hunt-badge
Add Product Hunt badge
2024-09-09 09:15:51 +05:30
Steph 806699d478 add product hunt badge to navbar 2024-09-06 08:20:05 -07:00
Sidharth VinodandGitHub 07595b54a6 Merge pull request #1526 from mermaid-js/release-promotion
Release live editor
2024-09-05 19:21:15 +05:30
Sidharth VinodandGitHub 370a03f708 Merge pull request #1524 from mermaid-js/browserslist-update
build: update Browserslist db
2024-09-05 09:57:54 +05:30
Sidharth VinodandGitHub e88bb8ba30 Merge pull request #1508 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2024-09-05 09:57:41 +05:30
Sidharth VinodandGitHub b3a3a0ed51 Merge pull request #1525 from mermaid-js/update-promo-bar-button-style
Update promo bar styling
2024-09-05 09:57:24 +05:30
Steph fbf2bcb202 update promo bar styling 2024-09-04 18:19:40 -07:00
renovate[bot]andGitHub 762f8f00a3 fix(deps): update all non-major dependencies 2024-09-04 19:07:59 +00:00
GitHub Action 21ba23d95d build: update Browserslist db 2024-09-01 02:47:28 +00:00
Sidharth VinodandGitHub 4d4dde003b Merge pull request #1519 from mermaid-js/release-promotion
Release live editor
2024-08-28 18:05:15 +05:30
Ashish JainandGitHub eaf8eb4245 Merge pull request #1518 from mermaid-js/sidv/elkLoader
feat: Add layout-elk
2024-08-28 12:24:21 +00:00
Sidharth Vinod d023a1142b feat: Add layout-elk 2024-08-28 17:43:37 +05:30
Sidharth VinodandGitHub 571c58e5b9 Merge pull request #1513 from mermaid-js/release-promotion
Release live editor
2024-08-24 15:59:30 +05:30
Sidharth VinodandGitHub 3fbe4a80c8 Merge pull request #1512 from mermaid-js/sidv/MermaidV11
chore: Release mermaid v11
2024-08-24 15:59:02 +05:30
Sidharth Vinod 2cdc8ed09e Merge branch 'develop' into sidv/MermaidV11
* develop:
  chore(deps): update all non-major dependencies
2024-08-24 14:39:05 +05:30
Sidharth VinodandGitHub 48333cab3b Merge pull request #1509 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-08-24 14:37:35 +05:30
Sidharth Vinod dd7ff496c7 Update lockfile 2024-08-24 14:36:59 +05:30
Sidharth Vinod 34207c76b3 Merge branch 'develop' into sidv/MermaidV11
* develop:
  build: update Browserslist db
2024-08-24 14:35:51 +05:30
renovate[bot]andGitHub 15bb56bd7e chore(deps): update all non-major dependencies 2024-08-24 08:55:48 +00:00
Sidharth VinodandGitHub 634921dd65 Merge pull request #1510 from mermaid-js/browserslist-update
build: update Browserslist db
2024-08-24 14:23:49 +05:30
Sidharth Vinod c77ccfeaba chore: Release mermaid v11 2024-08-24 14:23:06 +05:30
GitHub Action 79651594ed build: update Browserslist db 2024-08-15 02:29:13 +00:00
Sidharth VinodandGitHub c40d83d068 Merge pull request #1504 from mermaid-js/release-promotion
Release live editor
2024-08-07 22:11:50 +05:30
Sidharth VinodandGitHub c3fae5817b Merge pull request #1499 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-08-07 16:40:48 +00:00
Sidharth VinodandGitHub f736e1545e Merge pull request #1506 from mermaid-js/update-promo-bar
update promo bar
2024-08-07 10:59:09 +05:30
Sidharth Vinod 2916eaedaa chore: Simplify promotion 2024-08-07 10:22:45 +05:30
renovate[bot]andGitHub d529226922 chore(deps): update all non-major dependencies 2024-08-06 16:32:55 +00:00
Sidharth VinodandGitHub 910fdd76d8 Merge pull request #1505 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-08-06 22:02:11 +05:30
Steph 9dc4f5773c update promo bar 2024-08-06 04:27:25 -07:00
renovate[bot]andGitHub 4aad53032f chore(deps): update all non-major dependencies 2024-08-06 07:30:33 +00:00
Sidharth VinodandGitHub 2815f35334 Merge pull request #1503 from mermaid-js/browserslist-update
build: update Browserslist db
2024-08-02 12:43:19 +00:00
Sidharth VinodandGitHub 82b9bfe64a Merge branch 'develop' into fix/stop-parsing-syntax-error-as-html 2024-08-02 18:12:38 +05:30
Sidharth VinodandGitHub 96b20fc12c Merge pull request #1498 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-08-02 12:42:09 +00:00
renovate[bot]andGitHub 36f9cfbb4a chore(deps): update all non-major dependencies 2024-08-01 13:01:01 +00:00
GitHub Action a01fe0b044 build: update Browserslist db 2024-08-01 02:35:24 +00:00
Sidharth VinodandGitHub 29d0feaab1 Merge pull request #1501 from mermaid-js/product-hunt-updates
Remove Product Hunt text and badge
2024-07-31 10:12:56 +05:30
Steph 6e16716376 remove product hunt text and badge 2024-07-29 00:27:48 -07:00
Alois KlinkandGitHub b58577b72d fix: use <output> and <pre> to format errors
Use the `<output>` and `<pre>` HTML elements to format mermaid errors,
instead just adding the error message as HTML.

Using `<pre>` avoids the need for sanitizing any HTML (or using a
`monospace` font in CSS, since `<pre>` does this automatically).

Using `<output>` means that users using screenreaders should have a
better experience when using the mermaid live editor.

on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2024-07-26 17:30:39 +09:00
Alois Klink 0671a9b2f1 Revert "feat: Sanitize error message"
This reverts commit 996a20d90e.

A better way to do fix this bug would be to not parse the syntax error
as HTML in the first place.
2024-07-26 17:20:15 +09:00
Sidharth VinodandGitHub ff092df72f Merge pull request #1495 from mermaid-js/release-promotion
Release live editor
2024-07-25 18:39:20 +05:30
Sidharth VinodandGitHub 28ef0e07d3 Merge pull request #1494 from mermaid-js/sidv/sanitizeError
feat: Sanitize error message
2024-07-25 18:38:12 +05:30
Sidharth Vinod 996a20d90e feat: Sanitize error message 2024-07-24 11:26:57 +05:30
Sidharth VinodandGitHub 5b1979d420 Merge pull request #1485 from mermaid-js/release-promotion
Release live editor
2024-07-24 10:46:45 +05:30
Sidharth VinodandGitHub eb62ba0b16 Merge pull request #1490 from mermaid-js/renovate/all-minor-patch
chore(deps): update dependency eslint-plugin-svelte to v2.43.0
2024-07-24 10:46:06 +05:30
Sidharth VinodandGitHub 46154948bb Merge pull request #1489 from mermaid-js/renovate/patch-all-minor-patch
fix(deps): update all non-major dependencies (patch)
2024-07-24 10:45:48 +05:30
Sidharth VinodandGitHub cbeb633895 Merge pull request #1492 from mermaid-js/update-product-hunt-content
Update Product Hunt content
2024-07-24 10:45:03 +05:30
Steph 1da5938ff2 update product hunt content 2024-07-23 16:28:50 -07:00
renovate[bot]andGitHub 5621afc874 fix(deps): update all non-major dependencies 2024-07-22 23:52:38 +00:00
renovate[bot]andGitHub d4602888f7 chore(deps): update dependency eslint-plugin-svelte to v2.43.0 2024-07-22 00:15:02 +00:00
Sidharth VinodandGitHub 3a3641b9d0 Merge pull request #1486 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-07-17 05:17:27 +00:00
Sidharth VinodandGitHub 84d666f558 Merge pull request #1487 from mermaid-js/browserslist-update
build: update Browserslist db
2024-07-17 05:17:19 +00:00
renovate[bot]andGitHub 2133625261 chore(deps): update all non-major dependencies 2024-07-16 18:35:04 +00:00
GitHub Action 34ae0ab1c9 build: update Browserslist db 2024-07-15 02:33:10 +00:00
Sidharth VinodandGitHub a782f60d08 Merge pull request #1484 from mermaid-js/master
Master
2024-07-14 09:32:22 +05:30
Sidharth VinodandGitHub 32a90daaeb Merge pull request #1477 from mermaid-js/release-promotion
Release live editor
2024-07-14 09:31:45 +05:30
Sidharth VinodandGitHub e9c11a9e1e Merge pull request #1469 from mermaid-js/dependabot/npm_and_yarn/ws-8.17.1
build(deps): bump ws from 8.16.0 to 8.17.1
2024-07-14 03:58:43 +00:00
Sidharth VinodandGitHub 0c5002bdad Merge pull request #1471 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-07-14 03:58:33 +00:00
Sidharth VinodandGitHub 394206340b Merge pull request #1478 from mermaid-js/browserslist-update
build: update Browserslist db
2024-07-14 09:28:08 +05:30
Sidharth VinodandGitHub 6db6b6d8cd Merge pull request #1481 from allxiao/patch-1
Update manifest.json to align the PWA scope with the editor URL, fix #1480
2024-07-14 09:27:54 +05:30
renovate[bot]andGitHub c4b848efd4 chore(deps): update all non-major dependencies 2024-07-13 12:49:17 +00:00
Menghua XiaoandGitHub e66630f6e1 Update manifest.json to align the URL with the PWA scope, fix #1480 2024-07-09 12:28:13 +08:00
GitHub Action 903106d34b build: update Browserslist db 2024-07-01 02:34:12 +00:00
dependabot[bot]andGitHub d1da3ac52c build(deps): bump ws from 8.16.0 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.16.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-27 06:07:58 +00:00
Sidharth VinodandGitHub 3b8209b3fe Merge pull request #1475 from mermaid-js/dependabot/npm_and_yarn/zenuml/core-3.23.26
build(deps): bump @zenuml/core from 3.17.3 to 3.23.26
2024-06-27 06:05:55 +00:00
Sidharth VinodandGitHub ea2d8e843c Merge pull request #1472 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-06-27 06:05:45 +00:00
Sidharth VinodandGitHub 8909b9e54d Merge pull request #1476 from mermaid-js/add-product-hunt-badge
Add Product Hunt badge
2024-06-27 11:20:47 +05:30
steph 62906cba24 add product hunt badge to navbar 2024-06-26 22:42:28 -07:00
dependabot[bot]andGitHub 2a4a700614 build(deps): bump @zenuml/core from 3.17.3 to 3.23.26
Bumps [@zenuml/core](https://github.com/mermaid-js/zenuml-core) from 3.17.3 to 3.23.26.
- [Commits](https://github.com/mermaid-js/zenuml-core/compare/v3.17.3...v3.23.26)

---
updated-dependencies:
- dependency-name: "@zenuml/core"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-26 19:09:53 +00:00
renovate[bot]andGitHub 8a2bfcf02e chore(deps): update all non-major dependencies 2024-06-24 14:30:04 +00:00
Sidharth VinodandGitHub e9a0378c2c Merge pull request #1470 from mermaid-js/promo-bar
Promo bar - update end date
2024-06-19 23:22:38 +05:30
Steph e703aec424 update endDate on promo 2024-06-19 10:49:13 -07:00
Sidharth VinodandGitHub 582c9662f2 Merge pull request #1468 from mermaid-js/release-promotion
Release live editor
2024-06-17 10:56:21 +05:30
Sidharth VinodandGitHub 6d67f0cd42 Merge pull request #1300 from mermaid-js/sidv/errorHighlight
feat: Add error highlight
2024-06-17 04:44:36 +00:00
Sidharth Vinod aca7f07aa8 Merge branch 'develop' into sidv/errorHighlight
* develop: (148 commits)
  Remove error lines from View
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  Remove stalebot
  build: update Browserslist db
  build: update Browserslist db
  chore: Add isRough to stats
  Update snapshots
  feat: Add rough mode
  fix(deps): update dependency mermaid to v10.9.1
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  chore(deps): update dependency svelte to v4.2.16
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  build: update Browserslist db
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  ...
2024-06-17 10:10:01 +05:30
Sidharth VinodandGitHub 368e741675 Merge pull request #1426 from mermaid-js/dependabot/npm_and_yarn/katex-0.16.10
build(deps): bump katex from 0.16.9 to 0.16.10
2024-06-17 04:38:48 +00:00
Sidharth VinodandGitHub 9695512981 Merge pull request #1299 from mermaid-js/sidv/errorDialogue
feat: Move error dialogue to bottom of editor.
2024-06-17 04:38:33 +00:00
Sidharth Vinod 3baba33e65 Remove error lines from View 2024-06-16 20:22:23 +05:30
Sidharth Vinod 49edf2e8e2 Merge branch 'develop' into sidv/errorDialogue
* develop: (139 commits)
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  Remove stalebot
  build: update Browserslist db
  build: update Browserslist db
  chore: Add isRough to stats
  Update snapshots
  feat: Add rough mode
  fix(deps): update dependency mermaid to v10.9.1
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  chore(deps): update dependency svelte to v4.2.16
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  build: update Browserslist db
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  Update README.md
  ...
2024-06-16 20:18:09 +05:30
Sidharth VinodandGitHub beca75bbba Merge branch 'develop' into dependabot/npm_and_yarn/katex-0.16.10 2024-06-16 20:08:41 +05:30
Sidharth VinodandGitHub 71f4a9bac7 Merge pull request #1461 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-06-16 13:46:44 +00:00
renovate[bot]andGitHub 915b8990ae chore(deps): update all non-major dependencies 2024-06-16 13:46:29 +00:00
Sidharth VinodandGitHub 262d9e972a Merge pull request #1462 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-06-16 13:44:16 +00:00
Sidharth VinodandGitHub 77aa7d851a Merge pull request #1463 from mermaid-js/browserslist-update
build: update Browserslist db
2024-06-16 13:44:04 +00:00
Sidharth VinodandGitHub 6fbc5da142 Merge pull request #1412 from maxstanley/vendor-fa
Vendor Font-Awesome
2024-06-16 13:43:49 +00:00
renovate[bot]andGitHub 238557b911 chore(deps): update all non-major dependencies 2024-06-16 13:42:33 +00:00
Sidharth Vinod 81cc401bf2 Remove stalebot 2024-06-16 19:11:26 +05:30
Sidharth Vinod 7ce93ea210 Merge branch 'develop' into pr/maxstanley/1412
* develop: (34 commits)
  chore: Add isRough to stats
  Update snapshots
  feat: Add rough mode
  fix(deps): update dependency mermaid to v10.9.1
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  chore(deps): update dependency svelte to v4.2.16
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  build: update Browserslist db
  build: update Browserslist db
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  Update README.md
  fix(#1401): Handle error when loading gist
  chore(deps): update all non-major dependencies
  chore(deps): update all non-major dependencies
  Fix clicking issue in diagramUpdate.spec.ts
  Remove pnpm-lock.yaml
  ...
2024-06-16 19:07:48 +05:30
GitHub Action a09f5ee8b6 build: update Browserslist db 2024-06-15 02:24:59 +00:00
GitHub Action fa5bc5a5a1 build: update Browserslist db 2024-06-01 02:27:24 +00:00
Sidharth VinodandGitHub ea0553342c Merge pull request #1458 from mermaid-js/release-promotion
Release live editor
2024-05-23 18:27:12 +05:30
Sidharth VinodandGitHub ca03a85221 Merge pull request #1457 from mermaid-js/sidv/rough
feat: Add rough mode
2024-05-23 12:55:33 +00:00
Sidharth Vinod 4525e0467c chore: Add isRough to stats 2024-05-23 18:25:13 +05:30
Sidharth Vinod 20b4ecc260 Update snapshots 2024-05-23 18:19:42 +05:30
Sidharth Vinod cba9a6754d feat: Add rough mode 2024-05-23 14:05:59 +05:30
Sidharth VinodandGitHub 8b673659d6 Merge pull request #1448 from mermaid-js/release-promotion
Release live editor
2024-05-21 12:40:59 +05:30
Sidharth VinodandGitHub 994a87b103 Merge branch 'master' into release-promotion 2024-05-21 12:40:44 +05:30
Sidharth VinodandGitHub eb3b2d5446 Merge pull request #1450 from mermaid-js/browserslist-update
build: update Browserslist db
2024-05-21 06:52:10 +00:00
Sidharth VinodandGitHub 1e5248250d Merge pull request #1455 from mermaid-js/renovate/patch-mermaid-packages
fix(deps): update dependency mermaid to v10.9.1
2024-05-21 06:52:02 +00:00
renovate[bot]andGitHub b51ab519c2 fix(deps): update dependency mermaid to v10.9.1 2024-05-21 06:45:58 +00:00
Sidharth VinodandGitHub c91434346b Merge pull request #1454 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-05-21 06:44:54 +00:00
renovate[bot]andGitHub c9db1b90c4 chore(deps): update all non-major dependencies 2024-05-20 00:12:40 +00:00
GitHub Action 7b80de3683 build: update Browserslist db 2024-05-15 02:25:09 +00:00
Sidharth VinodandGitHub 116a84ea8d Merge pull request #1447 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update dependency svelte to v4.2.16
2024-05-13 05:42:19 +00:00
Sidharth VinodandGitHub 34a6d3c7b2 Merge pull request #1434 from kerwin612/patch-1
Update README.md
2024-05-13 11:12:02 +05:30
renovate[bot]andGitHub 386a19776b chore(deps): update dependency svelte to v4.2.16 2024-05-13 00:10:36 +00:00
Sidharth VinodandGitHub f7046c27c5 Merge pull request #1437 from mermaid-js/release-promotion
Release live editor
2024-05-12 09:22:11 +05:30
Sidharth VinodandGitHub 528d2f0afe Merge pull request #1444 from mermaid-js/browserslist-update
build: update Browserslist db
2024-05-12 09:10:06 +05:30
Sidharth VinodandGitHub 8e7758a7be Merge pull request #1445 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-05-12 09:09:19 +05:30
renovate[bot]andGitHub 61a9ecf702 chore(deps): update all non-major dependencies 2024-05-10 01:52:24 +00:00
Sidharth VinodandGitHub dc72838036 Merge pull request #1442 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-05-03 07:03:47 +00:00
Sidharth VinodandGitHub 86a7909e2b Merge branch 'develop' into renovate/all-minor-patch 2024-05-03 12:33:35 +05:30
Sidharth VinodandGitHub 3dad6ebbb9 Merge pull request #1439 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-05-03 12:33:22 +05:30
renovate[bot]andGitHub 580e2f7506 chore(deps): update all non-major dependencies 2024-05-02 09:48:11 +00:00
renovate[bot]andGitHub cc227ea993 chore(deps): update all non-major dependencies 2024-05-01 15:30:24 +00:00
GitHub Action dcff346abd build: update Browserslist db 2024-05-01 02:23:10 +00:00
Sidharth VinodandGitHub 39e0293156 Merge pull request #1440 from mermaid-js/browserslist-update
build: update Browserslist db
2024-04-16 09:40:17 +05:30
GitHub Action 93b75efe71 build: update Browserslist db 2024-04-15 04:05:03 +00:00
Sidharth VinodandGitHub 68157f7e71 Merge pull request #1431 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-04-13 06:30:11 +00:00
renovate[bot]andGitHub 4eb23185a8 chore(deps): update all non-major dependencies 2024-04-13 06:29:55 +00:00
Sidharth VinodandGitHub 6d5a9acdc7 Merge pull request #1432 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-04-13 06:28:49 +00:00
Sidharth VinodandGitHub f862d29791 Merge pull request #1424 from mermaid-js/fix/1423
fix: subscripts in corner safari, resolves #1423
2024-04-13 06:28:41 +00:00
renovate[bot]andGitHub c5d3eb322a chore(deps): update all non-major dependencies 2024-04-11 19:52:59 +00:00
Kerwin BryantandGitHub df66e433bf Update README.md 2024-04-09 14:00:30 +08:00
Sidharth VinodandGitHub fdd2581b64 Merge pull request #1430 from mermaid-js/release-promotion
Release live editor
2024-04-05 19:07:24 +05:30
Sidharth VinodandGitHub 29b9188d88 Merge branch 'develop' into fix/1423 2024-04-05 19:07:12 +05:30
Sidharth VinodandGitHub 0e6c022a0f Merge pull request #1429 from mermaid-js/1401_LoadGist
fix(#1401): Handle error when loading gist
2024-04-05 19:06:56 +05:30
Sidharth Vinod 57eef9da5c fix(#1401): Handle error when loading gist 2024-04-05 18:58:02 +05:30
Sidharth VinodandGitHub 8fd1c154d6 Merge pull request #1417 from mermaid-js/release-promotion
Release live editor
2024-04-03 00:42:40 +05:30
Sidharth VinodandGitHub e635b23694 Merge pull request #1421 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-04-03 00:42:08 +05:30
Sidharth VinodandGitHub 36390c2034 Merge pull request #1425 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-04-03 00:41:53 +05:30
renovate[bot]andGitHub d2f5dd148f chore(deps): update all non-major dependencies 2024-03-31 02:17:36 +00:00
renovate[bot]andGitHub bc9e8c306b chore(deps): update all non-major dependencies 2024-03-29 10:19:32 +00:00
Sidharth VinodandGitHub 618ac917ec Merge pull request #1383 from dontry/feature/zenuml
Add @mermaid-js/mermaid-zenuml dependency and ZenUML sample diagram
2024-03-27 14:46:05 +05:30
dependabot[bot]andGitHub f89b2f5698 build(deps): bump katex from 0.16.9 to 0.16.10
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.9 to 0.16.10.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.9...v0.16.10)

---
updated-dependencies:
- dependency-name: katex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 20:31:18 +00:00
dontry 329839a9a7 Fix clicking issue in diagramUpdate.spec.ts 2024-03-24 21:45:42 +11:00
Sidharth Vinod cfd8484f76 Remove pnpm-lock.yaml 2024-03-24 15:01:24 +05:30
DonandGitHub 6175a02d5a Merge branch 'mermaid-js:develop' into feature/zenuml 2024-03-24 13:58:26 +11:00
Yash Singh 3613b41875 fix: subscripts in corner safari, resolves #1423 2024-03-23 18:13:05 -07:00
Sidharth VinodandGitHub 44eeceb389 Merge pull request #1420 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-03-23 05:26:20 +00:00
renovate[bot]andGitHub 5276972caa chore(deps): update all non-major dependencies 2024-03-21 02:18:50 +00:00
dontry 664d81f6ea Refactor diagram registration without top-level promise 2024-03-15 23:22:01 +11:00
Sidharth Vinodanddontry 7eee9b7a42 Bump version 2024-03-15 23:22:01 +11:00
dontry 50b0d12353 Replace zenuml example with a more sophisticated one 2024-03-15 23:22:01 +11:00
dontry 19702f885a Update dependencies and error handling in mermaid.ts 2024-03-15 23:22:01 +11:00
dontry e74828b2c8 1. Add @mermaid-js/mermaid-zenuml dependency and register external diagrams
2. Add ZenUML sample diagram to Preset.svelte component
3. Refactor diagram button layout with grid layout
2024-03-15 23:22:01 +11:00
Sidharth VinodandGitHub 1e3248cdba Merge pull request #1399 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-03-12 07:31:21 +00:00
renovate[bot]andGitHub 41e6dafd3b chore(deps): update all non-major dependencies 2024-03-11 17:37:50 +00:00
Sidharth VinodandGitHub ef1390f129 Merge pull request #1416 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-03-11 05:52:43 +00:00
renovate[bot]andGitHub e95ccb4029 chore(deps): update all non-major dependencies 2024-03-11 01:46:00 +00:00
Sidharth VinodandGitHub 6de7cbc520 Merge pull request #1407 from mermaid-js/release-promotion
Release live editor
2024-03-05 23:09:29 +05:30
Sidharth VinodandGitHub 301d39d50b Merge pull request #1413 from mermaid-js/sidv/10.9.0
Mermaid v10.9.0
2024-03-05 23:09:05 +05:30
Sidharth VinodandGitHub 6428d69952 Merge branch 'develop' into sidv/10.9.0 2024-03-05 23:04:09 +05:30
Sidharth VinodandGitHub 873481e5f0 Merge pull request #1398 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-03-05 23:03:40 +05:30
Sidharth Vinod c06d98c1ee Fix prettier 2024-03-05 23:03:22 +05:30
Sidharth VinodandGitHub f7ae8d19e6 Merge pull request #1409 from mermaid-js/update-announcement-bar
Announcement Bar - update link
2024-03-05 23:02:31 +05:30
Sidharth Vinod 89d6d355b2 mermaid v10.9.0 2024-03-05 23:00:51 +05:30
renovate[bot]andGitHub 2d6e74a754 chore(deps): update all non-major dependencies 2024-03-05 17:28:42 +00:00
Sidharth VinodandGitHub ef34b4ca75 Merge pull request #1402 from mermaid-js/browserslist-update
build: update Browserslist db
2024-03-05 22:58:07 +05:30
Max Stanley 2d3f233b5d Include FA Stylesheet in SVG 2024-03-02 16:34:35 +00:00
Max Stanley ab2a024471 Formatting 2024-03-02 16:13:04 +00:00
Max Stanley 5a74e15a22 Remove whitespace 2024-03-02 16:10:35 +00:00
Max Stanley 74be138670 Add #681 TODO 2024-03-02 16:09:08 +00:00
Max Stanley fe3e283b3e Move FA CSS to app.postcss 2024-03-02 14:44:52 +00:00
Max Stanley e5e6045723 Fix indentation 2024-03-02 14:27:08 +00:00
Max Stanley d439963e4d Vendor Font-Awesome 2024-03-02 14:19:34 +00:00
GitHub Action 05a941e659 build: update Browserslist db 2024-03-01 02:18:16 +00:00
GitHub Action edc8af2157 build: update Browserslist db 2024-03-01 02:17:33 +00:00
steph 611fda5eff update link 2024-02-28 21:42:02 -08:00
Sidharth Vinod a14970d481 Bump node version 2024-02-26 21:32:31 +05:30
Sidharth Vinod 3e52f07364 Update node 2024-02-26 21:09:20 +05:30
Sidharth VinodandGitHub e6dd0fa83e Merge pull request #1404 from BaumiCoder/fixLinkToTutorial
Fix broken link to the tutorial page
2024-02-19 13:27:19 +05:30
Sidharth VinodandGitHub a5018276f3 Merge pull request #1405 from BaumiCoder/fixLinkToContributionGuidelines
Fix Link to contribution guidelines
2024-02-19 13:26:46 +05:30
Michael Baumgartner c005cf4bfa Fix Link to contribution guidelines
The linked markdown file in the github repository only contains
the path to the actual file with the guidelines. Furthermore,
there is a rendered version on the website, which shows the diagramms
and not the code of them.
2024-02-16 19:39:59 +01:00
Michael Baumgartner 9d2c8ea316 Fix broken link to the tutorial page 2024-02-16 19:17:13 +01:00
Sidharth VinodandGitHub c0101cdd4f Merge pull request #1400 from mermaid-js/release-promotion
Release live editor
2024-02-12 23:09:19 +05:30
Sidharth Vinod b7ccfff91d Hide overflow for body 2024-02-12 14:12:57 +05:30
Sidharth VinodandGitHub 4f66f40779 Merge pull request #1397 from mermaid-js/release-promotion
Release live editor
2024-02-09 11:19:44 +05:30
Sidharth Vinod ab7aed0146 feat: Add privacy modal 2024-02-09 11:16:31 +05:30
Sidharth VinodandGitHub 552df747fd Merge pull request #1395 from mermaid-js/release-promotion
Release live editor
2024-02-08 08:04:47 +05:30
Sidharth VinodandGitHub 3f95ce271b Merge pull request #1394 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-02-06 14:49:22 +00:00
renovate[bot]andGitHub 2fb1ce7452 chore(deps): update all non-major dependencies 2024-02-06 13:21:27 +00:00
Sidharth VinodandGitHub b5e6fb255c Merge pull request #1393 from mermaid-js/mermaid/10.8.0
Add block diagram sample
2024-02-05 09:23:38 +00:00
Knut Sveidqvist 351d46c5e2 Updated block diagram example 2024-02-05 10:21:54 +01:00
Sidharth Vinod b3955bd85c Add block sample 2024-02-03 11:23:19 +05:30
Sidharth VinodandGitHub 2e25c8475a Merge pull request #1392 from mermaid-js/release-promotion
Release live editor
2024-02-03 11:09:38 +05:30
Sidharth VinodandGitHub 0abf806f24 Merge branch 'master' into release-promotion 2024-02-03 11:09:05 +05:30
Sidharth VinodandGitHub 756c8ae3ac Merge pull request #1389 from mermaid-js/browserslist-update
build: update Browserslist db
2024-02-03 11:08:51 +05:30
Sidharth VinodandGitHub 8c3e208be7 Merge pull request #1391 from mermaid-js/mermaid/10.8.0
Update mermaid v10.8.0
2024-02-03 11:08:42 +05:30
Sidharth VinodandGitHub 7714c8f8a5 Merge branch 'develop' into mermaid/10.8.0 2024-02-03 11:04:16 +05:30
Sidharth VinodandGitHub 9fb2c6ff9e Merge pull request #1388 from Abrifq/develop
Update the docs domain in the docMap test
2024-02-03 11:04:07 +05:30
Sidharth Vinod 1a935bce4f Update mermaid v10.8.0 2024-02-03 10:54:29 +05:30
GitHub Action 82d77ae2ad build: update Browserslist db 2024-02-01 02:17:52 +00:00
Abrifq b40990a528 Whoops! 2024-01-31 16:37:48 +03:00
Abrifq 66ef322a31 Updated docs domain name in the test as well 2024-01-31 16:29:05 +03:00
Sidharth VinodandGitHub f4d24c5ab8 Merge pull request #1387 from mermaid-js/release-promotion
Release live editor
2024-01-30 23:42:44 +05:30
Sidharth VinodandGitHub 2f1c452021 Merge pull request #1384 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-01-30 15:27:33 +00:00
Sidharth VinodandGitHub d947dc9dfb Merge pull request #1335 from Abrifq/develop
Update docs links in the Editor's Docs button
2024-01-30 15:26:28 +00:00
Abrifq 57eea0f8d5 Changed docs domain to mermaid.js.org 2024-01-30 13:18:58 +03:00
Arda AydınandGitHub 193cbdde91 Merge branch 'mermaid-js:develop' into develop 2024-01-30 11:46:00 +02:00
renovate[bot]andGitHub c6de1cd453 chore(deps): update all non-major dependencies 2024-01-30 09:38:51 +00:00
Sidharth VinodandGitHub 0c6a43eac7 Merge pull request #1385 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-01-30 09:38:01 +00:00
renovate[bot]andGitHub 45687fa76b chore(deps): update all non-major dependencies 2024-01-29 19:50:46 +00:00
Arda AydınandGitHub 6b6adf033a Merge branch 'develop' into develop 2024-01-28 18:06:29 +02:00
Sidharth VinodandGitHub d0da4a07b4 Merge pull request #1374 from mermaid-js/release-promotion
Release live editor
2024-01-23 13:46:03 +05:30
Sidharth VinodandGitHub 65d6931336 Merge pull request #1355 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2024-01-23 02:33:25 +00:00
renovate[bot]andGitHub 38a15f0a24 chore(deps): update all non-major dependencies 2024-01-23 01:46:19 +00:00
Sidharth VinodandGitHub 31187f8b73 Merge pull request #1377 from mermaid-js/intelligentAutoSync
feat: Adaptive auto sync
2024-01-22 14:41:04 +00:00
Sidharth VinodandGitHub 6f33a03e18 Merge branch 'develop' into intelligentAutoSync 2024-01-22 19:12:15 +05:30
Sidharth VinodandGitHub d3ecdfa9ef Merge pull request #1380 from mermaid-js/renovate/npm-vite-vulnerability
chore(deps): update dependency vite to v5.0.12 [security]
2024-01-22 13:41:15 +00:00
Sidharth VinodandGitHub 0181069a08 Merge pull request #1381 from mermaid-js/renovate/all-minor-patch
chore(deps): update dependency @sveltejs/kit to v2.4.1
2024-01-22 13:41:06 +00:00
renovate[bot]andGitHub a422ca03cb chore(deps): update dependency @sveltejs/kit to v2.4.1 2024-01-22 01:15:12 +00:00
renovate[bot]andGitHub 42779d7cec chore(deps): update dependency vite to v5.0.12 [security] 2024-01-19 22:39:12 +00:00
Sidharth Vinod 61928528a7 remove fontawesome 2024-01-18 23:20:33 +05:30
Sidharth Vinod 2acb84415a feat: waitForRender 2024-01-18 23:20:21 +05:30
Sidharth Vinod 2033f1add0 Wait to sync before export 2024-01-18 21:24:42 +05:30
Sidharth Vinod 675af38c49 Throw error if out of sync 2024-01-18 21:05:33 +05:30
Sidharth Vinod 0c7d48970c Strict match filename 2024-01-18 20:48:32 +05:30
Sidharth Vinod 71cf898f5c Wait for diagram to sync 2024-01-18 20:32:05 +05:30
Sidharth Vinod 3e457fb0bb Revert "Bump cypress"
This reverts commit d9eba9ceec.
2024-01-18 20:26:26 +05:30
Sidharth VinodandGitHub 2eb9cff6bb Merge pull request #1364 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2024-01-18 14:52:51 +00:00
Sidharth Vinod 45d41b55eb Remove use of cy.clock 2024-01-18 20:16:40 +05:30
Sidharth Vinod 9bf4214617 Merge branch 'develop' into intelligentAutoSync
* develop:
  Mermaid 10.7.0
  Add UTM tags
  build: update Browserslist db
2024-01-18 19:20:49 +05:30
renovate[bot]andGitHub 59019417de chore(deps): update all non-major dependencies 2024-01-18 13:49:18 +00:00
Sidharth Vinod 91e6a3f1ba Mermaid 10.7.0 2024-01-18 19:18:23 +05:30
Sidharth Vinod d9eba9ceec Bump cypress 2024-01-18 19:17:05 +05:30
Sidharth Vinod e6e2b62e82 test: Verify adaptive auto sync 2024-01-17 22:28:43 +05:30
Sidharth VinodandGitHub c2beff5442 Merge pull request #1378 from mermaid-js/janPromo
Add UTM tags
2024-01-17 21:54:22 +05:30
Sidharth Vinod c08df23ca7 Merge branch 'develop' into janPromo
* develop:
  Fix XSS vulnerability
  chore: Update eslint plugin and fix errors
  build: update Browserslist db
  fix: Unit tests
  chore: Fix issues
  Add node-version
  chore: Auto format and fix warnings
  chore: Upgrade dependencies
  build: update Browserslist db
  build: update Browserslist db
2024-01-17 21:50:59 +05:30
Sidharth Vinod b9b2f6ba85 Add UTM tags 2024-01-17 21:49:10 +05:30
Sidharth Vinod f0951d575f feat: Adaptive auto sync 2024-01-17 18:53:57 +05:30
Sidharth VinodandGitHub d8ba3747bf Merge pull request #1373 from mermaid-js/release-promotion
Release live editor
2024-01-15 12:31:41 +05:30
Sidharth VinodandGitHub 27b158e624 Merge pull request #1371 from mermaid-js/browserslist-update
build: update Browserslist db
2024-01-15 07:01:22 +00:00
Sidharth VinodandGitHub 2a3b12ca82 Merge pull request #1372 from mermaid-js/updateDeps
Update ESLint plugin
2024-01-15 12:30:42 +05:30
Sidharth Vinodandchbi afbc7f3de3 Fix XSS vulnerability
Co-authored-by: chbi <chbi@chbi.eu>
2024-01-15 12:15:57 +05:30
Sidharth Vinod 8667c6de33 chore: Update eslint plugin and fix errors 2024-01-15 12:13:13 +05:30
GitHub Action 67d934c12d build: update Browserslist db 2024-01-15 02:25:58 +00:00
Sidharth VinodandGitHub 9a15de1467 Merge pull request #1370 from mermaid-js/release-promotion
Release live editor
2024-01-11 22:44:43 +05:30
Sidharth VinodandGitHub eaecb488ec Merge pull request #1368 from mermaid-js/updateDeps
UpdateDeps
2024-01-11 17:12:52 +00:00
Sidharth Vinod beb5a8935f fix: Unit tests 2024-01-11 22:39:50 +05:30
Sidharth Vinod 6a422ca643 chore: Fix issues 2024-01-11 22:23:18 +05:30
Sidharth Vinod b487ef67b3 Add node-version 2024-01-11 22:17:17 +05:30
Sidharth VinodandGitHub 9e0fa86127 Merge pull request #1369 from mermaid-js/release-promotion
Release live editor
2024-01-11 22:15:16 +05:30
Sidharth Vinod 44e0026e0d Merge branch 'develop' into updateDeps
* develop:
  feat: Jan 2024 Promo
  Update promo.ts
  build: update Browserslist db
  build: update Browserslist db
2024-01-11 22:14:17 +05:30
Sidharth Vinod 3da392a4a7 Merge branch 'develop' of github.com:mermaid-js/mermaid-live-editor into develop
* 'develop' of github.com:mermaid-js/mermaid-live-editor:
  feat: Jan 2024 Promo
  build: update Browserslist db
  build: update Browserslist db
2024-01-11 22:12:29 +05:30
Sidharth Vinod b69cca3763 Merge branch 'develop' into janPromo
* develop:
  Update promo.ts
2024-01-11 22:11:04 +05:30
Sidharth VinodandGitHub 539efbae7d Merge pull request #1363 from mermaid-js/browserslist-update
build: update Browserslist db
2024-01-11 16:39:41 +00:00
Sidharth VinodandGitHub 488f655e2f Merge pull request #1367 from mermaid-js/janPromo
feat: Jan 2024 Promo
2024-01-11 22:09:23 +05:30
Sidharth Vinod 087fb897a0 chore: Auto format and fix warnings 2024-01-11 22:06:29 +05:30
Sidharth Vinod d1cf590f20 chore: Upgrade dependencies 2024-01-11 22:03:44 +05:30
Sidharth Vinod 65587a6c7d feat: Jan 2024 Promo 2024-01-11 21:32:46 +05:30
Sidharth VinodandGitHub 027f7ae76f Update promo.ts 2024-01-03 12:30:51 +05:30
GitHub Action c278f200b9 build: update Browserslist db 2024-01-01 02:26:31 +00:00
GitHub Action a4638c7d1e build: update Browserslist db 2023-12-15 02:22:38 +00:00
Sidharth VinodandGitHub 75ab8c2c58 Merge pull request #1360 from mermaid-js/release-promotion
Release live editor
2023-12-14 10:50:53 +05:30
Sidharth VinodandGitHub 1412bed156 Merge pull request #1362 from mermaid-js/sidv/fixPlausible
Fix plausible tracking
2023-12-14 10:50:23 +05:30
Sidharth Vinod 4891fc8a84 Merge branch 'develop' into sidv/fixPlausible
* develop:
2023-12-14 10:44:02 +05:30
Sidharth Vinod 1a82978f2c chore: Use manual page tracking to avoid sending hash. 2023-12-14 10:37:19 +05:30
Sidharth VinodandGitHub f771662360 Merge pull request #1361 from mermaid-js/sidv/disableHostedAnalytics
feat: Add env vars to configure plausible
2023-12-12 07:48:28 +00:00
Sidharth Vinod 434a50f81c format netlify.toml 2023-12-11 18:00:20 +05:30
Sidharth Vinod e7293cc0a9 feat: Add env vars to configure plausible 2023-12-11 17:42:38 +05:30
Sidharth VinodandGitHub af4242a46a Merge pull request #1356 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2023-12-11 04:28:14 +00:00
renovate[bot]andGitHub d022a22211 chore(deps): update all non-major dependencies 2023-12-11 03:52:50 +00:00
Arda AydınandGitHub db3a314589 Merge branch 'develop' into develop 2023-12-07 08:01:55 +03:00
Sidharth VinodandGitHub be2519f89c Merge pull request #1344 from mermaid-js/release-promotion
Release live editor
2023-12-06 11:32:01 +05:30
Sidharth VinodandGitHub 007b461d5e Merge pull request #1347 from maxstanley/docker_env
Provide environment variables to Docker build
2023-12-06 11:29:27 +05:30
Sidharth VinodandGitHub 0d4ae40f12 Merge pull request #1354 from mermaid-js/dependabot/npm_and_yarn/adobe/css-tools-4.3.2
build(deps): bump @adobe/css-tools from 4.3.1 to 4.3.2
2023-12-06 11:29:09 +05:30
Sidharth VinodandGitHub 5c747a1c03 Merge pull request #1357 from mermaid-js/dependabot/npm_and_yarn/vite-4.5.1
build(deps-dev): bump vite from 4.5.0 to 4.5.1
2023-12-06 11:28:54 +05:30
Sidharth Vinod f2fde65536 feat: trackBannerClick
Purposefully not enabling the outbound link tracking,
as it would track links people have embedded in diagrams
which will then be available in public analytics.
2023-12-06 11:27:16 +05:30
Sidharth Vinod 7ff3594df7 Merge branch 'master' into develop
* master:
  chore: Update MC promo link
  remove toSorted
  chore: Tweak start and end dates
  feat: Add Holiday2023 banner
2023-12-06 11:25:16 +05:30
dependabot[bot]andGitHub 47537f6594 build(deps-dev): bump vite from 4.5.0 to 4.5.1
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.1/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 00:04:15 +00:00
Sidharth VinodandGitHub f52699400e Merge pull request #1353 from mermaid-js/browserslist-update
build: update Browserslist db
2023-12-05 04:49:08 +00:00
Sidharth VinodandGitHub 5a165f0773 Apply suggestions from code review 2023-12-05 10:18:26 +05:30
Sidharth Vinod 19a15e9710 chore: Update MC promo link 2023-12-01 12:00:02 +05:30
dependabot[bot]andGitHub 697201f1e0 build(deps): bump @adobe/css-tools from 4.3.1 to 4.3.2
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 05:24:23 +00:00
GitHub Action 4e93f44121 build: update Browserslist db 2023-12-01 02:26:53 +00:00
Max Stanley 5865b46a26 Simplify docker action 2023-11-30 18:06:19 +00:00
Sidharth VinodandGitHub 9507c67f73 Merge pull request #1348 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update dependency svelte-preprocess to v5.1.1
2023-11-30 04:41:49 +00:00
renovate[bot]andGitHub 4ccf0dc643 chore(deps): update dependency svelte-preprocess to v5.1.1 2023-11-29 16:28:09 +00:00
Sidharth VinodandGitHub 9bfec1f13d Merge pull request #1349 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2023-11-29 16:27:26 +00:00
Sidharth VinodandGitHub dff33bc869 Merge pull request #1351 from mermaid-js/sidv/holiday2023
feat: Add Holiday2023 banner
2023-11-29 11:02:27 +05:30
Sidharth Vinod 670b298ba6 remove toSorted 2023-11-29 10:08:15 +05:30
renovate[bot]andGitHub b25a9d3e97 chore(deps): update all non-major dependencies 2023-11-28 21:12:26 +00:00
Sidharth Vinod 1f28dde6e7 chore: Tweak start and end dates 2023-11-28 14:27:17 +05:30
Sidharth Vinod 9ba2598b00 feat: Add Holiday2023 banner 2023-11-28 14:25:14 +05:30
Max Stanley d2d082ec08 Always push tag event 2023-11-21 22:08:25 +00:00
Max Stanley f6a729f871 Remove env 2023-11-21 22:06:16 +00:00
Max Stanley 6d983158b6 Fix env 2023-11-21 22:03:11 +00:00
Max Stanley 2d7f91ab26 Add new-line 2023-11-21 22:01:13 +00:00
Max Stanley 7db761fba7 Improved docker CI 2023-11-21 21:59:03 +00:00
Max Stanley 11a814ac7d Add environment arguments to Dockerfile 2023-11-21 21:00:14 +00:00
Sidharth VinodandGitHub 4ad6bf915b Merge pull request #1342 from mermaid-js/browserslist-update
build: update Browserslist db
2023-11-20 11:01:27 +00:00
Sidharth VinodandGitHub b156fe9966 Merge pull request #1345 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2023-11-20 11:01:04 +00:00
renovate[bot]andGitHub 2611664047 chore(deps): update all non-major dependencies 2023-11-20 01:29:47 +00:00
Sidharth VinodandGitHub d77f5c8399 Merge pull request #1339 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2023-11-19 18:19:55 +00:00
renovate[bot]andGitHub f5f9ec9560 chore(deps): update all non-major dependencies 2023-11-19 12:04:12 +00:00
Sidharth VinodandGitHub 454c6d836c Merge pull request #1340 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2023-11-19 12:03:31 +00:00
renovate[bot]andGitHub 1b004a76b2 chore(deps): update all non-major dependencies 2023-11-17 23:09:27 +00:00
GitHub Action 517178a7e7 build: update Browserslist db 2023-11-15 02:20:31 +00:00
Arda AydınandGitHub d84947e33d Merge branch 'develop' into develop 2023-11-10 01:56:33 +03:00
Sidharth VinodandGitHub 3f64df7754 Merge pull request #1336 from mermaid-js/release-promotion
Release live editor
2023-11-09 23:33:14 +05:30
Sidharth VinodandGitHub fd411e46be Merge pull request #1302 from HK-SHAO/develop
fix: interface SVG was changed by mistake
2023-11-09 23:32:34 +05:30
Sidharth VinodandGitHub 796d1b98c6 Merge pull request #1321 from subhash-halder/add-xychart-to-mermaid-live-editor
Added XYChart sample in mermaid live ediror
2023-11-09 23:31:46 +05:30
Sidharth VinodandGitHub 8cb8b14093 Merge pull request #1309 from mermaid-js/renovate/all-minor-patch
chore(deps): update all non-major dependencies (minor)
2023-11-09 08:01:11 +00:00
Sidharth VinodandGitHub fad000984a Merge branch 'develop' into develop 2023-11-09 13:24:45 +05:30
renovate[bot]andGitHub 7c9ee990b5 chore(deps): update all non-major dependencies 2023-11-09 07:53:59 +00:00
Sidharth VinodandGitHub bbcd207cb7 Merge pull request #1317 from mozi47/fix/hambergur-menu-toggling
Hamberger Menu Fixed
2023-11-09 07:53:15 +00:00
Sidharth VinodandGitHub 93215b3391 Merge pull request #1308 from mermaid-js/renovate/patch-all-minor-patch
chore(deps): update all non-major dependencies (patch)
2023-11-09 07:50:16 +00:00
Sidharth VinodandGitHub 97dad2419b Merge pull request #1333 from mermaid-js/release-promotion
Release live editor
2023-11-09 11:51:59 +05:30
renovate[bot]andGitHub abb494d768 chore(deps): update all non-major dependencies 2023-11-09 06:21:06 +00:00
Sidharth VinodandGitHub 3c08315a85 Merge pull request #1310 from mermaid-js/dependabot/npm_and_yarn/get-func-name-2.0.2
build(deps): bump get-func-name from 2.0.0 to 2.0.2
2023-11-09 06:20:21 +00:00
Sidharth VinodandGitHub 863efb5746 Merge pull request #1327 from shubhusion/develop
Absolute path for /mermaidchart-logo.svg #1319
2023-11-09 06:18:21 +00:00
Sidharth VinodandGitHub 1250cc5456 Merge pull request #1316 from mermaid-js/renovate/npm-postcss-vulnerability
chore(deps): update dependency postcss to v8.4.31 [security]
2023-11-09 06:15:45 +00:00
Sidharth VinodandGitHub ecc6d2a539 Merge pull request #1318 from mermaid-js/browserslist-update
build: update Browserslist db
2023-11-09 06:15:21 +00:00
renovate[bot]andGitHub 4af85ae30b chore(deps): update dependency postcss to v8.4.31 [security] 2023-11-09 06:13:54 +00:00
Sidharth VinodandGitHub baff40a692 Merge pull request #1328 from mermaid-js/renovate/mermaid-packages
fix(deps): update dependency mermaid to v10.6.1
2023-11-09 11:42:28 +05:30
Sidharth VinodandGitHub 00b084721a Merge pull request #1330 from crpz1/bug/1181_export-png-with-theme-background
Use current theme's background color on PNG export
2023-11-09 06:11:50 +00:00
Abrifq 74c0e6dcad Update tests for the new doc links 2023-11-08 14:22:18 +03:00
renovate[bot]andGitHub 07b0a065fa fix(deps): update dependency mermaid to v10.6.1 2023-11-06 16:04:10 +00:00
Abrifq fd8bba4934 Fixed sankey's docKey
Whoops!
2023-11-05 22:04:37 +03:00
Arda AydınandGitHub 4f5b0cde29 Merge branch 'mermaid-js:develop' into develop 2023-11-05 21:46:47 +03:00
Abrifq e15e5378f7 Added new Mermaid types to Editor Doc Links 2023-11-05 21:45:38 +03:00
Arda AydınandGitHub 4eefa7083e Update Doc links on Editor 2023-11-03 04:25:25 +03:00
Sidharth VinodandGitHub ba12c7ab2f Merge pull request #1332 from zhzy0077/patch-1
DOCUMENTATION button goes to 404
2023-11-02 06:12:53 +00:00
Zhiyuan ZhengandGitHub df49ead019 DOCUMENTATION button goes to 404
This should be https://mermaid.js.org/intro/getting-started.html
2023-11-01 16:08:17 +08:00
GitHub Action 3ef4db3ecd build: update Browserslist db 2023-11-01 02:19:13 +00:00
crpz1 5d9d3feb59 Use current theme's background color on PNG export 2023-10-31 15:56:55 +00:00
shubhusion 5a155d285b Absolute path for /mermaidchart-logo.svg #1319 2023-10-27 22:35:43 +05:30
Subhash Halder d56d2678d4 Added XYChart sample in mermaid live ediror 2023-10-18 19:48:44 +05:30
GitHub Action 0a600d1c5a build: update Browserslist db 2023-10-15 02:17:46 +00:00
Muzakir ShahandGitHub f7f77f35d3 Hamberger Menu Fixed
Hamberger menu is fixed and the menu UI is also updated.
2023-10-06 18:26:52 +05:00
dependabot[bot]andGitHub 77ba621d90 build(deps): bump get-func-name from 2.0.0 to 2.0.2
Bumps [get-func-name](https://github.com/chaijs/get-func-name) from 2.0.0 to 2.0.2.
- [Release notes](https://github.com/chaijs/get-func-name/releases)
- [Commits](https://github.com/chaijs/get-func-name/commits/v2.0.2)

---
updated-dependencies:
- dependency-name: get-func-name
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-27 21:32:44 +00:00
Sidharth Vinod bb526e1534 test: Ignore monaco internal error. 2023-09-26 12:11:48 +05:30
Sidharth Vinod 8cb7bffc28 chore: Fix test without ID 2023-09-26 11:32:40 +05:30
Sidharth Vinod 4fda534c6a chore: Add id to fix test 2023-09-26 10:56:49 +05:30
Sidharth Vinod f64eaaa528 refactor: Remove unnecessary div 2023-09-26 10:54:42 +05:30
HK-SHAOandGitHub bb09d2ed51 Merge pull request #1 from HK-SHAO/HK-SHAO-patch-interface-changed
fix: interface SVG was changed by mistake
2023-09-15 17:08:36 +08:00
HK-SHAOandGitHub f0ce4c9db6 fix: interface SVG was changed by mistake
When setting up an export of different sizes, click the Copy Image button or the Download button, and the SVG size of the user interface changes
2023-09-15 17:07:19 +08:00
Sidharth Vinod 4994ecf1dd feat: Add error highlight 2023-09-15 13:17:58 +05:30
Sidharth Vinod 3ce91d9d22 chore: Simplify classes 2023-09-15 11:38:07 +05:30
Sidharth Vinod 182b488552 Increase max height 2023-09-15 11:21:22 +05:30
Sidharth Vinod 17f27de9bd feat: Move error dialogue to bottom of editor. 2023-09-15 11:12:44 +05:30
187 changed files with 14670 additions and 9987 deletions
+9
View File
@@ -3,3 +3,12 @@
**/.svelte-kit
**/dist
**/docs
**/.github
**/.husky
**/.vscode
Dockerfile
.dockerignore
docker-compose.yml
README.md
+9
View File
@@ -0,0 +1,9 @@
MERMAID_DOMAIN=''
MERMAID_BASE_PATH=''
MERMAID_DOCS_URL='https://mermaid.js.org'
MERMAID_ANALYTICS_URL=''
MERMAID_RENDERER_URL='https://mermaid.ink'
MERMAID_KROKI_RENDERER_URL='https://kroki.io'
MERMAID_IS_ENABLED_MERMAID_CHART_LINKS='true'
# cp .env .env.local to make local changes
-8
View File
@@ -1,8 +0,0 @@
docs/**
.svelte-kit/**
static/**
build/**
node_modules/**
coverage/**
__snapshots__/**
snapshots.js
-100
View File
@@ -1,100 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
'plugin:unicorn/recommended',
'prettier'
],
plugins: [
'svelte3',
'tailwindcss',
'@typescript-eslint',
'es',
'vitest',
'no-only-tests',
'unicorn'
],
ignorePatterns: [
'docs/*',
'*.cjs',
'*.js',
'*.md',
'snapshots.js',
'svelte.config.js',
'renovate.json',
'package.json',
'tsconfig.json'
],
overrides: [
{ files: ['*.svelte'], processor: 'svelte3/svelte3' },
{
files: ['*.ts'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
'prettier'
]
}
],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
extraFileExtensions: ['.svelte'],
allowAutomaticSingleRunInference: true
},
env: {
browser: true,
es2020: true
},
rules: {
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'es/no-regexp-lookbehind-assertions': 'error',
curly: ['error', 'all'],
'no-only-tests/no-only-tests': 'error',
'unicorn/no-null': 'off',
'unicorn/filename-case': [
'error',
{
case: 'camelCase'
}
],
'unicorn/prevent-abbreviations': [
'error',
{
allowList: {
ctx: true,
db: true,
doc: true,
env: true,
fn: true,
i: true,
param: true,
req: true,
res: true,
str: true,
searchParams: true,
temp: true,
ImportMetaEnv: true
}
}
]
}
};
+1 -1
View File
@@ -12,6 +12,6 @@ Describe the way your implementation works or what design decisions you made if
Make sure you
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/master/CONTRIBUTING.md)
- [ ] :book: have read the [contribution guidelines](https://mermaid.js.org/community/contributing.html)
- [ ] :computer: have added unit/e2e tests (if appropriate)
- [ ] :bookmark: targeted `develop` branch
@@ -0,0 +1,60 @@
name: Close broken link issues
on:
issues:
types: [opened, edited]
permissions:
issues: write
jobs:
close-broken-link-issues:
if: github.event.issue.state == 'open'
runs-on: ubuntu-latest
steps:
- name: Close issues with only "Broken link" in the title
uses: actions/github-script@v7
with:
script: |
const title = context.payload.issue.title.trim();
const isBrokenLinkOnlyTitle = /^broken link\.?$/i.test(title);
if (!isBrokenLinkOnlyTitle) {
return;
}
const { owner, repo } = context.repo;
const issueNumber = context.payload.issue.number;
const newIssueUrl = `https://github.com/${owner}/${repo}/issues/new?assignees=&labels=bug&template=bug_report.md&title=Broken%20link`;
const mermaidIssuesUrl = 'https://github.com/mermaid-js/mermaid/issues/new';
const commentBody = [
`@${context.payload.issue.user.login} This issue was automatically closed because the title only contains "Broken link" without additional details.`,
'',
'**Please only open issues here if something is broken in the live editor itself** (e.g. a link fails to load, the editor UI misbehaves, or a feature of mermaid.live does not work).',
'',
'**Do not open issues here for syntax errors or invalid Mermaid diagram code.** Those belong in the main Mermaid repository: [mermaid-js/mermaid](https://github.com/mermaid-js/mermaid/issues/new).',
'',
'If you are reporting a live editor bug, please include:',
'- The full URL that failed to load',
'- What you expected to happen',
'- Any error messages you saw',
'',
`You can [open a new issue in this repo](${newIssueUrl}) with this information, or report diagram syntax issues in the [Mermaid repo](${mermaidIssuesUrl}).`
].join('\n');
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: commentBody
});
await github.rest.issues.update({
owner,
repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned'
});
+38 -14
View File
@@ -14,25 +14,49 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: yarn
node-version-file: '.node-version'
cache: pnpm
- name: Setup Pages
uses: actions/configure-pages@v5
with:
static_site_generator: sveltekit
- name: Build & Deploy
env:
MERMAID_DOMAIN: 'mermaid.live'
MERMAID_ANALYTICS_URL: 'https://p.mermaid.live'
MERMAID_RENDERER_URL: 'https://mermaid.ink'
MERMAID_KROKI_RENDERER_URL: 'https://kroki.io'
MERMAID_IS_ENABLED_MERMAID_CHART_LINKS: 'true'
run: |
export DEPLOY=true
[ "$GITHUB_EVENT_NAME" != "pull_request" ] && rm -rf docs/_app/
yarn install
version=$(yarn version --patch --no-git-tag-version | grep "New version" | cut -d':' -f 2)
yarn build
yarn run lint
cd ..
pnpm install
pnpm build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/master' }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
keep_files: true
path: ./docs
deploy:
if: ${{ github.ref == 'refs/heads/master' }}
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+42 -52
View File
@@ -2,13 +2,11 @@ name: Docker
on:
push:
# Publish `master` as Docker `latest` image.
branches:
# Publish `master` as Docker `latest` image.
- master
# Publish `v1.2.3` tags as releases.
tags:
- v*
# Publish `develop` as Docker `nightly` image.
- develop
# Run tests for all PRs to master and develop.
pull_request:
@@ -16,54 +14,46 @@ on:
- master
- develop
env:
IMAGE_NAME: mermaid-live-editor
jobs:
test:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
docker build . --file Dockerfile
push:
# Ensure test job passes before pushing image.
needs: test
runs-on: ubuntu-latest
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest,enable=${{ github.ref_name == 'master' }}
type=raw,value=nightly,enable=${{ github.ref_name == 'develop' }}
- uses: docker/build-push-action@v5
id: build
with:
context: .
target: mermaid
push: ${{ github.event_name == 'push' }}
platforms: linux/amd64,linux/arm64
pull: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate Build Attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build . --file Dockerfile --tag $IMAGE_NAME
- name: Log into registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push image
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "master" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
-20
View File
@@ -1,20 +0,0 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 0 * * 4'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
exempt-issue-labels: 'retained'
exempt-pr-labels: 'retained'
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 30 days'
stale-pr-message: 'This pr is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 30 days'
days-before-stale: 90
days-before-close: 30
days-before-pr-close: -1
+31 -27
View File
@@ -5,47 +5,51 @@ on:
branches:
- master
- develop
merge_group:
jobs:
cypress-run:
playwright:
name: 'Playwright Tests'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# run 3 copies of the current job in parallel
containers: [1, 2, 3]
container:
image: mcr.microsoft.com/playwright:v1.61.1-jammy
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: actions/cache@v3
id: yarn-and-build-cache
- uses: actions/cache@v4
id: pnpm-and-build-cache
with:
path: |
~/.cache/Cypress
build
node_modules
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node_modules-build-
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install pnpm
uses: pnpm/action-setup@v4
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v3
- uses: actions/setup-node@v4
with:
build: yarn build
start: yarn preview
wait-on: 'http://localhost:3000'
record: true
headless: true
parallel: true
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
- name: Run Playwright tests
run: pnpm test:e2e
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
CYPRESS_CI: true
CI: true
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: test-results
path: test-results/
retention-days: 7
+21 -11
View File
@@ -5,6 +5,7 @@ on:
branches:
- master
- develop
merge_group:
jobs:
unit-tests:
@@ -15,22 +16,31 @@ jobs:
uses: actions/checkout@v3
- uses: actions/cache@v3
id: yarn-and-build-cache
id: pnpm-and-build-cache
with:
path: |
build
node_modules
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node_modules-build-
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Lint & Test
run: |
yarn install
yarn lint
yarn test:unit
- uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm check
- name: Run unit tests
run: pnpm test:unit
+13 -9
View File
@@ -1,14 +1,18 @@
node_modules/
coverage/
.cache/
build/
yarn-error.log
.npmrc
.DS_Store
.cache/
.env.local
.npmrc
/.svelte-kit
/build
/coverage
/docs
/functions
/node_modules
/snapshots.js
/cypress/downloads
/cypress/videos
/cypress/screenshots
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
.playwright-mcp/
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn pre-commit
pnpm pre-commit
+1
View File
@@ -0,0 +1 @@
24.18.0
+1
View File
@@ -1 +1,2 @@
engine-strict=true
auto-install-peers=true
+1
View File
@@ -6,3 +6,4 @@ node_modules/**
coverage/**
__snapshots__/**
snapshots.js
pnpm-lock.yaml
+3 -1
View File
@@ -3,5 +3,7 @@
"svelteSortOrder": "options-scripts-markup-styles",
"bracketSameLine": true,
"trailingComma": "none",
"printWidth": 100
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/app.css"
}
+10
View File
@@ -0,0 +1,10 @@
{
"servers": {
"svelte": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@sveltejs/mcp"]
}
},
"inputs": []
}
+36 -4
View File
@@ -2,26 +2,58 @@
"editor.formatOnSave": true,
"cSpell.blockCheckingWhenLineLengthGreaterThan": 150,
"cSpell.words": [
"appinstalled",
"asyncable",
"Browserslist",
"ckppp",
"corg",
"cssnano",
"daisyui",
"esserializer",
"fsegurai",
"gantt",
"gitgraph",
"hugeicons",
"KROKI",
"localstorage",
"mermaidchart",
"mindmap",
"NEWYEAR",
"noopener",
"noreferrer",
"Pageview",
"pako",
"panmove",
"panstart",
"panzoom",
"pinchmove",
"pinchstart",
"pzoom",
"roughjs",
"sankey",
"Serde",
"serdes",
"Stackable",
"tailwindcss",
"uparrow"
"treemap",
"uparrow",
"xychart",
"zenuml"
],
"vitest.commandLine": "yarn test:unit",
"vitest.commandLine": "pnpm test:unit",
"vitest.enable": true,
"testing.autoRun.mode": "rerun",
"svelte.enable-ts-plugin": true,
"githubPullRequests.ignoredPullRequestBranches": ["develop"]
"githubPullRequests.ignoredPullRequestBranches": ["develop"],
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
},
"tailwindCSS.classAttributes": ["class", "className", ".*Classes"],
"tailwindCSS.experimental.classRegex": [
["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"]
],
"tailwindCSS.emmetCompletions": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
}
}
+1
View File
@@ -0,0 +1 @@
mermaid.live
+35 -15
View File
@@ -1,17 +1,37 @@
# Two-stage docker container for mermaid-js/mermaid-live-editor
# Build : docker build -t mermaid-js/mermaid-live-editor .
# Run : docker run --name mermaid-live-editor --publish 8080:8080 mermaid-js/mermaid-live-editor
# Start : docker start mermaid-live-editor
# Use webbrowser : http://localhost:8080
# Stop : press ctrl + c
# or
# docker stop mermaid-live-editor
FROM node:18.17.1 as mermaid-live-editor-builder
COPY --chown=node:node . /home
WORKDIR /home
RUN yarn install
RUN yarn build
FROM docker.io/library/node:24.16.0-alpine3.22 AS mermaid-live-editor-dependencies
RUN apk --no-cache add build-base git python3 && \
rm -rf /var/cache/apk/*
RUN corepack enable pnpm
WORKDIR /app
COPY ./package.json .
COPY ./pnpm-lock.yaml .
RUN pnpm install
FROM mermaid-live-editor-dependencies AS mermaid-live-editor-builder
ARG MERMAID_RENDERER_URL
ARG MERMAID_KROKI_RENDERER_URL
ARG MERMAID_ANALYTICS_URL
ARG MERMAID_DOMAIN
ARG MERMAID_IS_ENABLED_MERMAID_CHART_LINKS
ARG MERMAID_PRIVACY_POLICY_URL
ARG MERMAID_HIDE_PRIVACY_POLICY
ARG MERMAID_BASE_PATH
COPY . ./
RUN pnpm build
FROM mermaid-live-editor-builder AS mermaid-dev
ENTRYPOINT ["pnpm", "dev"]
FROM nginx:1.28-alpine3.21 AS mermaid
FROM nginxinc/nginx-unprivileged:alpine as mermaid-live-editor-runner
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=mermaid-live-editor-builder --chown=nginx:nginx /home/docs /usr/share/nginx/html
COPY --from=mermaid-live-editor-builder /app/docs /usr/share/nginx/html
-8
View File
@@ -1,8 +0,0 @@
FROM node:18.17.1
WORKDIR /app
COPY package.json .
COPY yarn.lock .
RUN npm install
COPY . .
RUN ls
CMD ["yarn", "dev"]
+62 -15
View File
@@ -1,10 +1,7 @@
[![Mermaid Live Editor](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/detailed/2ckppp/master&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/2ckppp/runs) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)[![Netlify Status](https://api.netlify.com/api/v1/badges/27fa023d-7c73-4a3f-9791-b3b657a47100/deploy-status)](https://app.netlify.com/sites/mermaidjs/deploys)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/sKeNQX4Wtj)
[![Netlify Status](https://api.netlify.com/api/v1/badges/27fa023d-7c73-4a3f-9791-b3b657a47100/deploy-status)](https://app.netlify.com/sites/mermaidjs/deploys)
# Contributors are welcome!
If you want to speed up the progress for mermaid-live-editor, join the slack channel and contact knsv.
# mermaid-live-editor
# Mermaid Live Editor
Edit, preview and share mermaid charts/diagrams.
@@ -17,7 +14,11 @@ Edit, preview and share mermaid charts/diagrams.
## Live demo
You can try out a live version [here](https://mermaid.live/).
You can try out a [live version](https://mermaid.live/).
# Contributors are welcome!
If you want to speed up the progress for mermaid-live-editor, join the Discord channel and contact knsv.
## Docker
@@ -27,15 +28,40 @@ You can try out a live version [here](https://mermaid.live/).
docker run --platform linux/amd64 --publish 8000:8080 ghcr.io/mermaid-js/mermaid-live-editor
```
The published docker image is built using our default environment variables. You cannot override them when running the image. If you need to customize them, you will need to build the image yourself.
### To configure renderer URL
When building, Set the Environment variable MERMAID_RENDERER_URL to the rendering service.
Default is `https://mermaid.ink`
When building set the MERMAID_RENDERER_URL build argument to the rendering
service.
Example:
Default is`https://mermaid.ink`.
Set to empty string to disable PNG and SVG links under Actions
### To configure Kroki Instance URL
When building, Set the Environment variable MERMAID_KROKI_RENDERER_URL to your Kroki instance.
When building set the MERMAID_KROKI_RENDERER_URL build argument to your Kroki
instance.
Default is `https://kroki.io`
Set to empty string to disable Kroki link under Actions
### To configure Analytics
When building set the MERMAID_ANALYTICS_URL build argument to your plausible instance, and MERMAID_DOMAIN to your domain.
Default is empty, disabling analytics.
### To enable Mermaid Chart links and promotion
When building set the MERMAID_IS_ENABLED_MERMAID_CHART_LINKS build argument to `true`
Default is empty, disabling button to save to Mermaid Chart and promotional banner.
### To update the Security modal
The modal shown on clicking the security link assumes analytics, renderer, Kroki
and Mermaid chart are enabled. You can update it by modifying `Privacy.svelte`
if you wish.
### Development
@@ -45,6 +71,28 @@ docker compose up --build
Then open http://localhost:3000
### Building and running images locally
#### Build
```bash
docker build -t mermaid-js/mermaid-live-editor .
```
#### Run
```bash
docker run --detach --name mermaid-live-editor --publish 8080:8080 mermaid-js/mermaid-live-editor
```
Visit: <http://localhost:8080>
#### Stop
```bash
docker stop mermaid-live-editor
```
## Setup
Below link will help you making a copy of the repository in your local system.
@@ -53,15 +101,14 @@ https://docs.github.com/en/get-started/quickstart/fork-a-repo
## Requirements
- [volta](https://volta.sh/) to manage node versions.
- [Node.js](https://nodejs.org/en/). `volta install node`
- [yarn](https://yarnpkg.com/) package manager. `volta install yarn`
- [Node.js](https://nodejs.org/en/) current LTS version
- [pnpm](https://pnpm.io/) package manager. Install with `corepack enable pnpm`
## Development
```sh
yarn install
yarn dev -- --open
pnpm install
pnpm dev -- --open
```
This app is created with Svelte Kit.
+1 -1
View File
@@ -6,7 +6,7 @@
# git clone https://github.com/mermaid-js/docs.git
set -e
rm -rf docs
yarn release
pnpm release
pushd .
if [ ! -d ../docs ]; then
echo "Clone https://github.com/mermaid-js/docs to parent folder before continuing."
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://next.shadcn-svelte.com/schema.json",
"tailwind": {
"css": "src/app.css",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://tw3.shadcn-svelte.com/registry/new-york"
}
-35
View File
@@ -1,35 +0,0 @@
import { defineConfig } from 'cypress';
import fs from 'fs';
import { isFileExist, findFiles } from 'cy-verify-downloads';
export default defineConfig({
projectId: '2ckppp',
viewportWidth: 1440,
viewportHeight: 768,
snapshotFileName: './cypress/snapshots.js',
defaultCommandTimeout: 5000,
requestTimeout: 5000,
retries: {
runMode: 2,
openMode: 0
},
e2e: {
setupNodeEvents(on, config) {
on('task', {
isFileExist,
findFiles,
deleteFile(path) {
fs.rmSync(path);
return null;
},
readFileMaybe(filename) {
if (fs.existsSync(filename)) {
return fs.readFileSync(filename, 'utf8');
}
return null;
}
});
},
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.spec.ts'
}
});
-10
View File
@@ -1,10 +0,0 @@
{
"plugins": ["cypress"],
"extends": ["plugin:cypress/recommended"],
"rules": {
"jest/expect-expect": "off"
},
"env": {
"cypress/globals": true
}
}
-49
View File
@@ -1,49 +0,0 @@
import { typeInEditor, verifyFileSizeGreaterThan } from './util';
describe('Check actions', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/edit');
cy.contains('Actions').click();
});
it('should update markdown code', () => {
cy.get('#markdown')
.invoke('val')
.then((oldText) => {
typeInEditor('C --> HistoryTest', { bottom: true, newline: true });
cy.get('#markdown')
.invoke('val')
.then((newText) => {
expect(oldText).to.not.eq(newText);
});
});
});
it.skip('should load gists from URL', () => {
cy.get('#gist').type('https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a');
cy.contains('Load Gist').click();
cy.contains('Go shopping!!');
});
it('should download png and svg', () => {
cy.clock(new Date(2022, 0, 1).getTime());
cy.get(`#downloadPNG`).click();
verifyFileSizeGreaterThan('diagram', 'png', 34_000);
cy.get(`#downloadSVG`).click();
verifyFileSizeGreaterThan('diagram', 'svg', 10_000);
// Verify downloaded file is different for different diagrams
cy.contains('Sample Diagrams').click();
cy.contains('ER').click();
cy.get(`#downloadPNG`).click();
verifyFileSizeGreaterThan('diagram', 'png', 40_000);
cy.get(`#downloadSVG`).click();
verifyFileSizeGreaterThan('diagram', 'svg', 11_000);
cy.clock().invoke('restore');
});
});
-97
View File
@@ -1,97 +0,0 @@
import { typeInEditor, cmd } from './util';
describe('Auto sync tests', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/');
cy.url().should('contain', '/edit#pako');
cy.window().should('have.property', 'editorLoaded', true);
});
it('should dim diagram when code is edited', () => {
cy.contains('Auto sync').click();
cy.get('#view').should('not.have.class', 'outOfSync');
cy.get('#errorContainer').should('not.exist');
typeInEditor(' C --> Test', { bottom: true });
cy.get('#view').should('have.class', 'outOfSync');
cy.get('#errorContainer').should('contain.text', 'Diagram out of sync.');
cy.getLocalStorage('codeStore').snapshot();
});
it('should update diagram when shortcut is used', () => {
cy.contains('Auto sync').click();
cy.get('#view').should('not.have.class', 'outOfSync');
typeInEditor(' C --> Test');
cy.get('#view').should('have.class', 'outOfSync');
cy.get('#errorContainer').should('contain.text', 'Diagram out of sync.');
typeInEditor(`${cmd}{enter}`);
cy.get('#errorContainer').should('not.exist');
cy.get('#view').should('not.have.class', 'outOfSync');
});
it('should show/hide sync button with auto sync', () => {
cy.get('[data-cy=sync]').should('not.exist');
cy.contains('Auto sync').click();
cy.get('[data-cy=sync]').should('exist');
cy.get('#autoSync').check();
cy.get('[data-cy=sync]').should('not.exist');
});
it('should not dim diagram when code is in sync', () => {
cy.contains('Auto sync').click();
cy.get('#view').should('not.have.class', 'outOfSync');
typeInEditor(' C --> Test');
cy.get('#view').should('have.class', 'outOfSync');
cy.get('[data-cy=sync]').click();
cy.get('#view').should('not.have.class', 'outOfSync');
cy.get('#autoSync').check();
typeInEditor('ing');
cy.get('#view').should('not.have.class', 'outOfSync');
cy.getLocalStorage('codeStore').snapshot();
});
it('supports commenting code out/in', () => {
cy.get('#editor').contains('Car').click();
cy.get('#editor').get('textarea').type(`${cmd}/`, { force: true });
cy.get('#view').contains('Car').should('not.exist');
typeInEditor(`{uparrow}${cmd}/`);
cy.get('#view').contains('Car').should('exist');
});
it('supports editing code when code is incorrect', () => {
cy.visit(
'/edit#pako:eNpljjEKwzAMRa8SNOcEnlt6gK5eVFvYJsgOqkwpIXevg9smEE1PnyfxF3DFExgISW-CczQ2D21cYU7a-SGYXRwyvTp9jUhuKlVP-eHy7zA-leQsMEmg_QOM0BLG5FujZVMsaCQmC6ahR5ks2Lw2r84ela4-aREwKpVGwKrl_s7ut3fnkjAIcg_XDzuaUhs'
);
cy.get('#errorContainer').should('not.exist');
typeInEditor(`branch test`, { bottom: true, newline: true });
cy.get('#editor').contains('branch test').should('exist');
cy.get('#errorContainer')
.contains(
'Error: Trying to checkout branch which is not yet created. (Help try using "branch master")'
)
.should('exist');
});
it('should update diagram after entire text is removed', () => {
// https://github.com/mermaid-js/mermaid-live-editor/issues/1102
typeInEditor(`${cmd} a {backspace}`);
typeInEditor('graph LR');
typeInEditor(' {enter} A-->Car');
cy.get('#view').contains('Car').should('exist');
});
});
describe('Pan and Zoom', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/');
});
it('should toggle pan and zoom', () => {
cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist');
cy.contains('Pan & Zoom').click();
cy.get('#svg-pan-zoom-reset-pan-zoom').should('exist');
cy.contains('Pan & Zoom').click();
cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist');
});
});
-36
View File
@@ -1,36 +0,0 @@
describe('Editor docs tests', () => {
beforeEach(() => {
cy.on('uncaught:exception', () => {
return false;
});
cy.clearLocalStorage();
cy.visit('/edit');
cy.contains('Sample Diagrams').click();
});
it('Test default loading', () => {
cy.get(`[data-cy=docs][href^="https://mermaid-js.github.io/mermaid"]`).should('exist');
});
it('Test to see if the correct URL loads when changing from one diagram to other', () => {
cy.contains('Flow').click();
cy.get(`[data-cy=docs][href$="/#/flowchart"]`).should('exist');
cy.contains('Config').click();
cy.get(`[data-cy=docs][href$="/#/flowchart?id=configuration"]`).should('exist');
cy.contains('Sequence').click();
cy.get(`[data-cy=docs][href$="/#/sequenceDiagram?id=configuration"]`).should('exist');
cy.contains('Code').click();
cy.get(`[data-cy=docs][href$="/#/sequenceDiagram"]`).should('exist');
});
it("Test to check URLs for a case where config URL doesn't exist", () => {
cy.contains('State').click();
cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist');
cy.contains('Config').click();
cy.get(`[data-cy=docs][href$="/#/stateDiagram"]`).should('exist');
});
});
-103
View File
@@ -1,103 +0,0 @@
import { typeInEditor, verifyFileSnapshot } from './util';
describe('Save History', () => {
beforeEach(() => {
cy.clock(new Date(2022, 0, 1).getTime());
cy.clearLocalStorage();
cy.visit('/edit');
cy.contains('History').click();
});
afterEach(() => {
cy.clock().invoke('restore');
});
it('should load history from localstorage', () => {
cy.setLocalStorage(
'manualHistoryStore',
'[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","id":"d7ea820e-21dd-418a-b984-fd58acde09df","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","id":"b749ffc6-522b-4a44-86cf-7c1ffc3146b3","name":"helpful-ocean"}]'
);
cy.setLocalStorage(
'autoHistoryStore',
'[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","id":"69ea820e-522b-4a44-86cf-fd58acde09df","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","id":"x749ffc6-21dd-418a-b984-7c1ffc3146b3","name":"needy-mosquito"}]'
);
cy.reload();
cy.contains('History').click();
cy.get('#historyList').find('li').should('have.length', 2);
cy.get('#historyList').find('No items in History').should('not.exist');
cy.get('#historyList').contains('helpful-ocean');
cy.get('#historyList').contains('hollow-art');
cy.contains('Restore').click();
cy.contains('Halloween');
cy.contains('Timeline').click();
cy.get('#historyList').find('li').should('have.length', 2);
cy.get('#historyList').find('No items in History').should('not.exist');
cy.get('#historyList').contains('needy-mosquito');
cy.get('#historyList').contains('barking-dog');
cy.contains('Restore').click();
cy.contains('New Year');
});
it('should save when clicked', () => {
cy.get('#historyList').find('li').should('have.length', 0);
cy.get('#historyList').contains('No items in History');
cy.get('#saveHistory').click();
cy.get('#historyList').find('No items in History').should('not.exist');
cy.get('#historyList').find('li').should('have.length', 1);
cy.get('#saveHistory').click();
cy.on('window:alert', (str) => {
expect(str).to.equal('State already saved.');
});
cy.on('window:confirm', () => true);
typeInEditor(' C --> HistoryTest');
cy.get('#saveHistory').click();
cy.get('#historyList').find('li').should('have.length', 2);
});
it('should be able to restore and delete', () => {
cy.get('#saveHistory').click();
typeInEditor(' C --> HistoryTest');
cy.get('#historyList').find('No items in History').should('not.exist');
cy.get('#historyList').find('li').should('have.length', 1);
cy.contains('HistoryTest');
cy.contains('Restore').click();
cy.contains('HistoryTest').should('not.exist');
cy.contains('Delete').click();
cy.get('#historyList').find('li').should('have.length', 0);
cy.get('#historyList').contains('No items in History');
cy.get('#saveHistory').click();
typeInEditor(' C --> HistoryTest');
cy.get('#saveHistory').click();
cy.get('#editor').type('ing');
cy.get('#clearHistory').click();
cy.on('window:alert', (str) => {
expect(str).to.equal('Clear all saved items?');
});
cy.on('window:confirm', () => true);
cy.get('#historyList').contains('No items in History');
});
// TODO: Fix #639
xit('should auto save history', () => {
typeInEditor(' C --> HistoryTest');
cy.tick(70_000);
cy.contains('Timeline').click();
cy.get('#historyList').find('li').should('have.length', 1);
cy.get('#editor').type('ing');
cy.tick(70_000);
cy.get('#historyList').find('li').should('have.length', 2);
for (let i = 0; i < 31; i++) {
cy.get('#editor').type('.');
cy.tick(70_000);
}
cy.get('#historyList').find('li').should('have.length', 30);
});
it('should download history', () => {
cy.get('#saveHistory').click();
cy.get(`#downloadHistory`).click();
verifyFileSnapshot('history', 'json', 'A[Christmas] -->|Get money| B(Go shopping)');
});
});
-136
View File
@@ -1,136 +0,0 @@
import { toBase64 } from 'js-base64';
describe('Site Loads', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/');
cy.url().should('include', '/edit#pako');
});
it('Check Home page load', () => {
cy.url().should('include', '/edit');
cy.contains('History').click();
cy.getLocalStorage('codeStore').snapshot();
});
it('should keep code after reload', () => {
cy.get('#editor').contains('Car');
cy.reload();
cy.get('#editor').contains('Car');
});
it('Check Redirect from old URL', () => {
cy.visit(
'/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ'
);
cy.url().should('include', '/edit#pako:eNp');
});
it('should load sample diagrams when clicked', () => {
cy.contains('Sample Diagrams').click();
cy.contains('Pie').click();
cy.contains('pie title Pets adopted by volunteers');
cy.contains('Class').click();
cy.contains('classDiagram');
});
(Cypress.env('CI') === 'true' ? describe : describe.skip)('github', () => {
it('should load diagram from gist', () => {
cy.visit(`/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a`);
cy.contains('History').click();
cy.contains('Go shopping!!');
cy.contains('Revisions');
cy.contains('sidharthv96 v8f8f1e2');
cy.contains('sidharthv96 v7851e19');
cy.getLocalStorage('codeStore').snapshot();
});
it('should load diagram from gist revision', () => {
cy.visit(
'/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/ec9b4ab0e41e4ff6287326cd3cb47affd7851e19'
);
cy.contains('History').click();
cy.contains('Party');
cy.contains('Revisions');
cy.contains('sidharthv96 v7851e19');
cy.getLocalStorage('codeStore').snapshot();
});
it('should load diagram from raw files', () => {
cy.visit(
'/edit?code=https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/code.mmd&config=https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/config.json'
);
cy.contains('Party');
cy.getLocalStorage('codeStore').snapshot();
});
});
// Disabled temporarily. Should be enabled after the issue is fixed in Mermaid.
// it('should prevent setting the "securityLevel" option via URL', () => {
// const b64State = toBase64(
// `{"code":"graph TD\\nA[\\"<img src='https://via.placeholder.com/64' width=64 />\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","autoSync":true,"updateDiagram":true}`,
// true
// );
// cy.on('window:confirm', () => true);
// cy.visit(`/edit#${b64State}`);
// cy.contains('Config').click();
// cy.contains('forest');
// cy.contains('securityLevel').should('not.exist');
// cy.get('#view').find('img').should('not.exist');
// cy.get('#view').contains('<img');
// cy.get('#view').contains(`src='https://via.placeholder.com/64'`);
// });
it.skip('should allow persisting "securityLevel" using confirm dialogue', () => {
const b64State = toBase64(
`{"code":"graph TD\\nA[\\"<img src='https://dummyimage.com/64' width=64/>\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","autoSync":true,"updateDiagram":true}`,
true
);
cy.on('window:confirm', () => false);
cy.visit(`/edit#${b64State}`);
cy.get('#editor').type(' ');
cy.contains('Config').click();
cy.contains('forest');
cy.contains('securityLevel');
cy.get('#view').find('img').should('be.visible');
});
it('should show troubleshooting steps if loading fails', () => {
cy.visit('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg');
cy.reload(true);
cy.contains('Please Click here to Raise an issue in github.');
});
});
describe('Verify types of URLs', () => {
it('should load compressed URL', () => {
cy.visit(
'/edit#pako:eNpVkM2KwkAQhF-l6dMK5gVyEDRxvYi7sF6WjIcm0zqDzg_jBJEk725Hd2G3Tw31VVFUj23QjCWeEkUD-1p5kFs2O77BN1M6QFEshg1ncMHzfYDV2ybA1YQYrT_NXvhqgqDqtxPGkI315_ElVU__h-cB6mZLMYd4-Kvsb2GAdWM_jcT_V0xicb03RyqPVLSUoJI-OEfHyZHV0rqfDAqzYccKS3k1pbNC5Ufhuqgp81rbHBJKxuXKc6Quh6-7b7HMqeNfqLYkC7gfanwAlW1ZvQ'
);
cy.contains('New Year');
cy.visit(
'/edit#pako:eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ'
);
cy.contains('Animal');
});
describe('Uncompressed URLs', () => {
it('should load URL without specifier', () => {
cy.visit(
'/edit/#eyJjb2RlIjoiZ3JhcGhcbiAgICBUZXN0TGFiZWwiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGVmYXVsdFwiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
cy.contains('TestLabel');
cy.visit(
'/edit#eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gICAgQW5pbWFsIDx8LS0gRHVja1xuICAgIEFuaW1hbCA8fC0tIEZpc2hcbiAgICBBbmltYWwgPHwtLSBaZWJyYVxuICAgIEFuaW1hbCA6ICtpbnQgYWdlXG4gICAgQW5pbWFsIDogK1N0cmluZyBnZW5kZXJcbiAgICBBbmltYWw6ICtpc01hbW1hbCgpXG4gICAgQW5pbWFsOiArbWF0ZSgpXG4gICAgY2xhc3MgRHVja3tcbiAgICAgICtTdHJpbmcgYmVha0NvbG9yXG4gICAgICArc3dpbSgpXG4gICAgICArcXVhY2soKVxuICAgIH1cbiAgICBjbGFzcyBGaXNoe1xuICAgICAgLWludCBzaXplSW5GZWV0XG4gICAgICAtY2FuRWF0KClcbiAgICB9XG4gICAgY2xhc3MgWmVicmF7XG4gICAgICArYm9vbCBpc193aWxkXG4gICAgICArcnVuKClcbiAgICB9XG4gICAgICAgICAgICAiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGFya1wiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
cy.contains('Animal');
});
it('should load URL with "base64" specifier', () => {
cy.visit(
'/edit/#base64:eyJjb2RlIjoiZ3JhcGhcbiAgICBUZXN0TGFiZWwiLCJtZXJtYWlkIjoie1xuICBcInRoZW1lXCI6IFwiZGVmYXVsdFwiXG59IiwidXBkYXRlRWRpdG9yIjpmYWxzZSwiYXV0b1N5bmMiOnRydWUsInVwZGF0ZURpYWdyYW0iOmZhbHNlfQ'
);
cy.contains('TestLabel');
});
});
});
-56
View File
@@ -1,56 +0,0 @@
describe('Test themes', () => {
describe('Test light themes', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/edit', {
onBeforeLoad(win) {
cy.stub(win, 'matchMedia')
.callThrough()
.withArgs('(prefers-color-scheme: dark)')
.returns({
matches: false
});
}
});
cy.contains('Theme').click();
});
it('should set light theme as default', () => {
cy.contains('light').parent().should('have.class', 'bordered');
cy.contains('dark').parent().should('not.have.class', 'bordered');
cy.getLocalStorage('themeStore').snapshot();
});
it('should change themes when clicked', () => {
cy.contains('light').parent().should('have.class', 'bordered');
cy.contains('cupcake').click();
cy.contains('cupcake').parent().should('have.class', 'bordered');
cy.contains('light').parent().should('not.have.class', 'bordered');
cy.contains('dark').parent().should('not.have.class', 'bordered');
cy.getLocalStorage('themeStore').snapshot();
});
});
describe('Test dark mode', () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.visit('/edit', {
onBeforeLoad(win) {
cy.stub(win, 'matchMedia')
.callThrough()
.withArgs('(prefers-color-scheme: dark)')
.returns({
matches: true
});
}
});
cy.contains('Theme').click();
});
it('should set dark theme as default', () => {
cy.contains('light').parent().should('not.have.class', 'bordered');
cy.contains('dark').parent().should('have.class', 'bordered');
cy.getLocalStorage('themeStore').snapshot();
});
});
});
-60
View File
@@ -1,60 +0,0 @@
export const cmd = `{${Cypress.platform === 'darwin' ? 'meta' : 'ctrl'}}`;
interface EditorOptions {
bottom?: boolean;
newline?: boolean;
}
export const typeInEditor = (
text: string,
{ bottom = true, newline = false }: EditorOptions = {}
) => {
cy.window().should('have.property', 'editorLoaded', true);
cy.get('#editor').click();
cy.get('#editor').within(($editor) => {
if (bottom) {
cy.get('textarea').type('{pageDown}', { force: true });
}
if (newline) {
cy.get('textarea').type('{enter}', { force: true });
}
cy.get('textarea').type(text, { force: true });
});
};
const downloadsFolder = Cypress.config('downloadsFolder');
export const verifyFileSizeGreaterThan = (
fileType: 'history' | 'diagram',
extension: string,
size: number
) => {
const fileName = `mermaid-${fileType}-2022-01-01-000000.${extension}`;
const filePath = `${downloadsFolder}/${fileName}`;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
cy.verifyDownload(fileName);
cy.readFile(filePath, null, {
log: false
}).then((buffer: ArrayBuffer) => {
expect(buffer.byteLength).to.be.gt(size);
expect(buffer.byteLength).to.be.lt(size * 1.3);
});
cy.task('deleteFile', filePath);
};
export const verifyFileSnapshot = (
fileType: 'history' | 'diagram',
extension: string,
content: string
) => {
const fileName = `mermaid-${fileType}-2022-01-01-000000.${extension}`;
const filePath = `${downloadsFolder}/${fileName}`;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
cy.verifyDownload(fileName);
cy.readFile(filePath, null, {
log: false
}).then((buffer: ArrayBuffer) =>
expect(new TextDecoder('utf8').decode(buffer)).to.contain(content)
);
cy.task('deleteFile', filePath);
};
-5
View File
@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
-52
View File
@@ -1,52 +0,0 @@
module.exports = {
"Site Loads": {
"Check Home page load": {
"1": "{\"code\":\"flowchart TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"autoSync\":true,\"updateDiagram\":true}"
},
"Check Redirect from old URL": {
"1": "{\"code\":\"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"autoSync\":true,\"updateDiagram\":true}"
},
"should load diagram from gist": {
"1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping!!)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello world\\\"\\n}\",\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"gist\",\"config\":{\"url\":\"https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a\"}}}"
},
"should load diagram from gist revision": {
"1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello\\\"\\n}\",\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"gist\",\"config\":{\"url\":\"https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/ec9b4ab0e41e4ff6287326cd3cb47affd7851e19\"}}}"
},
"should load diagram from raw files": {
"1": "{\"code\":\"graph TD\\n A[Party] -->|Get money| B(Go shopping!!)\\n \",\"mermaid\":\"{\\n \\\"theme\\\": \\\"forest\\\",\\n \\\"test\\\": \\\"hello world\\\"\\n}\",\"autoSync\":true,\"updateDiagram\":true,\"loader\":{\"type\":\"files\",\"config\":{\"codeURL\":\"https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/code.mmd\",\"configURL\":\"https://gist.githubusercontent.com/sidharthv96/6268a23e673a533dcb198f241fd7012a/raw/4eb03887e6a41397e80bdcdbf94017c498f8f1e2/config.json\"}}}"
}
},
"__version": "12.17.4",
"Auto sync tests": {
"should dim diagram when code is edited": {
"1": "{\"code\":\"flowchart TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n C --> Test\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"autoSync\":false,\"updateDiagram\":false}"
},
"should not dim diagram when code is in sync": {
"1": "{\"code\":\"flowchart TD\\n A[Christmas] -->|Get money| B(Go shopping)\\n B --> C{Let me think}\\n C -->|One| D[Laptop]\\n C -->|Two| E[iPhone]\\n C -->|Three| F[fa:fa-car Car]\\n C --> Testing\",\"mermaid\":\"{\\n \\\"theme\\\": \\\"default\\\"\\n}\",\"autoSync\":true,\"updateDiagram\":false}"
}
},
"Test themes": {
"should set light theme as default": {
"1": "{\"theme\":\"light\",\"isDark\":false}"
},
"should change themes when clicked": {
"1": "{\"theme\":\"cupcake\",\"isDark\":false}"
},
"should set dark theme as default": {
"1": "{\"theme\":\"dark\",\"isDark\":true}"
},
"Test light themes": {
"should set light theme as default": {
"1": "{\"theme\":\"light\",\"isDark\":false}"
},
"should change themes when clicked": {
"1": "{\"theme\":\"cupcake\",\"isDark\":false}"
}
},
"Test dark mode": {
"should set dark theme as default": {
"1": "{\"theme\":\"dark\",\"isDark\":true}"
}
}
}
}
-38
View File
@@ -1,38 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import { register } from '@cypress/snapshot';
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
register();
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
snapshot(): void;
}
}
}
import 'cypress-localstorage-commands';
-28
View File
@@ -1,28 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
require('cy-verify-downloads').addCustomCommand();
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (err.message.includes('ResizeObserver loop limit exceeded')) {
return false;
}
});
-7
View File
@@ -1,7 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"types": ["cypress", "cypress-localstorage-commands", "cy-verify-downloads", "node"]
},
"include": ["**/*.ts"]
}
+1 -1
View File
@@ -3,7 +3,7 @@ services:
mermaid:
build:
context: .
dockerfile: Dockerfile.dev
target: mermaid-dev
volumes:
- ./src:/app/src
ports:
+74
View File
@@ -0,0 +1,74 @@
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import sortKeysPlugin from 'eslint-plugin-sort-keys';
import svelte from 'eslint-plugin-svelte';
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default defineConfig(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.strict,
...ts.configs.stylistic,
...svelte.configs.recommended,
prettier,
...svelte.configs.prettier,
{
languageOptions: {
globals: { ...globals.browser, ...globals.node }
},
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
plugins: {
unicorn: eslintPluginUnicorn
},
rules: {
'unicorn/no-null': 'off',
'unicorn/filename-case': 'off'
}
},
{
files: ['src/**'],
plugins: {
'sort-keys': sortKeysPlugin
},
rules: {
'sort-keys/sort-keys-fix': ['error', 'asc', { minKeys: 5 }]
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
},
rules: {
'svelte/no-unused-props': 'off'
}
},
{
files: ['**/components/ui/**'],
rules: {
'unicorn/prefer-export-from': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/explicit-length-check': 'off',
'sort-keys/sort-keys-fix': 'off'
}
}
);
+13
View File
@@ -0,0 +1,13 @@
[build.environment]
MERMAID_ANALYTICS_URL = 'https://p.mermaid.live'
MERMAID_DOCS_URL = 'https://mermaid.js.org'
MERMAID_DOMAIN = 'mermaid.live'
MERMAID_RENDERER_URL = 'https://mermaid.ink'
MERMAID_KROKI_RENDERER_URL = 'https://kroki.io'
MERMAID_IS_ENABLED_MERMAID_CHART_LINKS ='true'
[[redirects]]
from = "/index.html"
to = "/edit"
status = 301
force = true
+113 -70
View File
@@ -5,92 +5,135 @@
"license": "MIT",
"scripts": {
"dev": "vite dev",
"dev:force": "MERMAID_LOCAL=true yarn dev --force",
"dev:test": "yarn dev",
"dev:force": "MERMAID_LOCAL=true pnpm dev --force",
"dev:test": "pnpm dev",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --check --cache --plugin-search-dir=. .;eslint --ignore-path .gitignore .",
"lint:fix": "prettier --write --cache --plugin-search-dir=. .;eslint --fix --ignore-path .gitignore .",
"format": "prettier --write --cache --plugin-search-dir=. .",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --cache . && eslint .",
"lint:fix": "prettier --write --cache . && eslint --fix .",
"format": "prettier --write --cache .",
"pre-commit": "lint-staged",
"postinstall": "husky install && svelte-kit sync && (git config blame.ignoreRevsFile .git-blame-ignore-revs || true)",
"test:unit": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"test:browser": "cypress run",
"test": "test:unit && test:browser",
"cy": "cypress open"
"test:unit:ui": "vitest --ui",
"test:unit:coverage": "vitest run --coverage",
"test": "pnpm test:unit && pnpm test:e2e",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug"
},
"devDependencies": {
"@cypress/snapshot": "2.1.7",
"@sveltejs/adapter-static": "2.0.3",
"@sveltejs/kit": "1.25.0",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/svelte": "3.2.2",
"@types/pako": "2.0.0",
"@types/uuid": "9.0.4",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"@vitest/ui": "^0.34.0",
"autoprefixer": "^10.4.14",
"c8": "7.14.0",
"chai": "^4.3.7",
"cssnano": "^6.0.0",
"cy-verify-downloads": "0.2.0",
"cypress": "12.17.4",
"cypress-localstorage-commands": "2.2.4",
"eslint": "8.49.0",
"eslint-config-prettier": "8.10.0",
"eslint-plugin-cypress": "2.14.0",
"eslint-plugin-es": "4.1.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-postcss-modules": "2.0.0",
"eslint-plugin-svelte3": "4.0.0",
"eslint-plugin-tailwindcss": "3.13.0",
"eslint-plugin-unicorn": "^46.0.0",
"eslint-plugin-vitest": "^0.3.0",
"esserializer": "1.3.11",
"font-awesome": "^4.7.0",
"husky": "^8.0.3",
"jsdom": "21.1.2",
"lint-staged": "13.3.0",
"node-html-parser": "^6.1.5",
"postcss": "^8.4.21",
"postcss-load-config": "4.0.1",
"prettier": "2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"svelte": "3.59.2",
"svelte-preprocess": "5.0.4",
"tailwindcss": "^3.3.1",
"tslib": "^2.5.0",
"typescript": "5.2.2",
"vite": "^4.3.9",
"vitest": "^0.34.0"
"@eslint/compat": "^2.1.0",
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"@fortawesome/fontawesome-free": "^7.2.0",
"@iconify-json/hugeicons": "^1.2.29",
"@iconify-json/logos": "^1.2.11",
"@iconify-json/material-symbols": "^1.2.79",
"@iconify-json/mdi": "^1.2.3",
"@playwright/test": "^1.61.1",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.67.0",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.3.1",
"@types/hammerjs": "^2.0.46",
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.13.2",
"@types/pako": "2.0.4",
"@vitest/coverage-v8": "^4.1.9",
"@vitest/ui": "^4.1.9",
"autoprefixer": "^10.5.0",
"bits-ui": "^2.18.1",
"c8": "11.0.0",
"chai": "^6.2.2",
"clsx": "^2.1.1",
"cssnano": "^8.0.2",
"dotenv": "^17.4.2",
"eslint": "^10.5.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-es": "^4.1.0",
"eslint-plugin-no-only-tests": "^3.4.0",
"eslint-plugin-sort-keys": "^2.3.5",
"eslint-plugin-svelte": "^3.19.0",
"eslint-plugin-tailwindcss": "^3.18.3",
"eslint-plugin-unicorn": "^65.0.1",
"globals": "^17.7.0",
"husky": "^9.1.7",
"jsdom": "^29.1.1",
"lint-staged": "^17.0.7",
"lucide-svelte": "^1.0.1",
"node-html-parser": "^7.1.0",
"paneforge": "1.0.2",
"prettier": "^3.8.4",
"prettier-plugin-svelte": "^4.1.1",
"prettier-plugin-tailwindcss": "^0.8.0",
"svelte": "^5.56.3",
"svelte-check": "^4.7.1",
"svelte-preprocess": "^6.0.5",
"svelte-sonner": "^1.1.1",
"tailwind-merge": "^3.6.0",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.3.1",
"tslib": "^2.8.1",
"tw-animate-css": "^1.4.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.62.0",
"unplugin-icons": "^23.0.1",
"vite": "^8.1.0",
"vite-plugin-devtools-json": "^1.0.0",
"vitest": "^4.1.9",
"vitest-dom": "^0.1.1"
},
"dependencies": {
"analytics": "0.8.9",
"analytics-plugin-plausible": "0.0.6",
"daisyui": "2.52.0",
"dayjs": "^1.11.7",
"js-base64": "3.7.5",
"mermaid": "10.5.0",
"monaco-editor": "0.43.0",
"pako": "2.1.0",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/lang-yaml": "^6.1.3",
"@codemirror/language": "^6.12.3",
"@codemirror/state": "^6.7.0",
"@codemirror/view": "^6.43.1",
"@fontsource-variable/recursive": "^5.2.8",
"@fsegurai/codemirror-theme-vscode-dark": "^6.2.6",
"@fsegurai/codemirror-theme-vscode-light": "^6.2.6",
"@mermaid-js/examples": "^1.2.0",
"@mermaid-js/layout-elk": "^0.2.1",
"@mermaid-js/layout-tidy-tree": "^0.2.2",
"@mermaid-js/mermaid-zenuml": "^0.2.3",
"codemirror": "^6.0.2",
"dayjs": "^1.11.21",
"hammerjs": "^2.0.8",
"js-base64": "3.7.8",
"lodash-es": "^4.18.1",
"mermaid": "^11.15.0",
"mode-watcher": "^1.1.0",
"monaco-editor": "0.55.1",
"pako": "2.2.0",
"plausible-tracker": "^0.3.9",
"random-word-slugs": "0.1.7",
"svg-pan-zoom": "3.6.1",
"uuid": "9.0.1"
"svg-pan-zoom": "3.6.2",
"svg2roughjs": "^3.2.3",
"uuid": "14.0.0"
},
"lint-staged": {
"*.{ts,svelte,js,css,md,json}": [
"prettier --plugin-search-dir=. --write",
"eslint --ignore-path .gitignore "
"eslint "
]
},
"volta": {
"node": "18.17.1",
"yarn": "1.22.19"
},
"engines": {
"node": ">=16.7"
"node": ">=24.18.0"
},
"packageManager": "pnpm@10.34.4+sha512.8768be55200ae3f2226b6527fcca2687e14bc4e5f12d7721a0f25da3df47915177058648db4177baf348120fa0ba2752d8d8d93f6beaf1fe64ae18da8de961af",
"pnpm": {
"onlyBuiltDependencies": [
"deasync",
"esbuild",
"svelte-preprocess"
],
"ignoredBuiltDependencies": [
"vue-demi"
]
}
}
+28
View File
@@ -0,0 +1,28 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
forbidOnly: !!process.env.CI,
fullyParallel: true,
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
}
],
reporter: process.env.CI ? [['github'], ['list']] : 'list',
retries: process.env.CI ? 2 : 0,
testDir: './tests',
use: {
baseURL: 'http://localhost:3000',
browserName: 'chromium',
permissions: ['clipboard-read', 'clipboard-write'],
trace: 'retain-on-failure',
viewport: { width: 1920, height: 1080 }
},
webServer: {
command: `pnpm ${process.env.CI ? 'preview' : 'dev'}`,
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI
},
workers: process.env.CI ? 3 : undefined
});
+7273
View File
File diff suppressed because it is too large Load Diff
-23
View File
@@ -1,23 +0,0 @@
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
module.exports = {
plugins: [
// Some plugins, like postcss-nested, need to run before Tailwind
tailwindcss,
// But others, like autoprefixer, need to run after
autoprefixer,
!dev &&
cssnano({
preset: 'default'
})
]
};
+2 -1
View File
@@ -24,5 +24,6 @@
"major": {
"dependencyDashboardApproval": true
},
"dependencyDashboardAutoclose": true
"dependencyDashboardAutoclose": true,
"rangeStrategy": "bump"
}
+119
View File
@@ -0,0 +1,119 @@
@import '@fontsource-variable/recursive/crsv.css';
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@layer base {
:root {
--background: hsl(0 0% 100%);
--foreground: hsl(222.2 84% 4.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(222.2 84% 4.9%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(222.2 84% 4.9%);
--primary: hsl(240 10% 91%);
--primary-foreground: hsl(255 20% 15%);
--secondary: hsl(210 40% 96.1%);
--secondary-foreground: hsl(222.2 47.4% 11.2%);
--muted: hsl(228 24% 96%);
--muted-foreground: hsl(215.4 16.3% 46.9%);
--accent: hsl(340 100% 44%);
--accent-foreground: hsl(210 40% 98%);
--destructive: hsl(0 72.22% 50.59%);
--destructive-foreground: hsl(210 40% 98%);
--border: hsl(214.3 31.8% 91.4%);
--border-dark: hsl(214.3 31.8% 81.4%);
--input: hsl(214.3 31.8% 91.4%);
--ring: hsl(222.2 84% 4.9%);
--radius: 0.75rem;
}
.dark {
--background: hsl(222.2 84% 4.9%);
--foreground: hsl(210 40% 98%);
--card: hsl(222.2 84% 4.9%);
--card-foreground: hsl(210 40% 98%);
--popover: hsl(222.2 84% 4.9%);
--popover-foreground: hsl(210 40% 98%);
--primary: hsl(210 40% 30%);
--primary-foreground: hsl(222.2 47.4% 90%);
--secondary: hsl(217.2 32.6% 17.5%);
--secondary-foreground: hsl(210 40% 98%);
--muted: hsl(217.2 32.6% 17.5%);
--muted-foreground: hsl(215 20.2% 65.1%);
--accent: hsl(340 100% 44%);
--accent-foreground: hsl(210 40% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(210 40% 98%);
--border: hsl(217.2 32.6% 17.5%);
--border-dark: hsl(217.2 32.6% 27.5%);
--input: hsl(217.2 32.6% 17.5%);
--ring: hsl(212.7 26.8% 83.9%);
}
}
@theme inline {
/* Radius (for rounded-*) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-border: var(--border);
--color-border-dark: var(--border-dark);
--color-input: var(--input);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-ring: var(--ring);
--color-radius: var(--radius);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border;
}
body {
@apply h-screen w-screen overflow-hidden bg-background text-foreground;
}
}
body {
font-family: 'Recursive Variable', sans-serif;
}
.d {
@apply border border-red-500;
}
+5 -13
View File
@@ -1,26 +1,18 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Online FlowChart &amp; Diagrams Editor - Mermaid Live Editor</title>
<meta
name="og:image"
content="https://github.com/mermaid-js/mermaid/raw/develop/img/header.png" />
<link rel="canonical" href="https://mermaid.live" />
<meta name="og:image" content="%sveltekit.assets%/favicon.svg" />
<meta
name="description"
content="Simplify documentation and avoid heavy tools. Open source Visio Alternative. Commonly used for explaining your code! Mermaid is a simple markdown-like script language for generating charts from text via javascript." />
<link rel="icon" type="image/png" href="%sveltekit.assets%/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
<link rel="apple-touch-icon" href="%sveltekit.assets%/favicon.png" />
<link rel="mask-icon" href="%sveltekit.assets%/favicon.svg" color="#000000" />
<meta name="theme-color" content="#6366F1" />
<meta name="theme-color" content="#ff3670" />
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"
integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA=="
crossorigin="anonymous"
referrerpolicy="no-referrer" />
%sveltekit.head%
</head>
<body>
-10
View File
@@ -1,10 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.input {
@apply flex-1 border-primary border-solid border-2 rounded;
}
.action-btn {
@apply btn btn-primary;
}
+6 -3
View File
@@ -3,9 +3,12 @@
interface ImportMetaEnv {
readonly MERMAID_RENDERER_URL?: string;
readonly MERMAID_KROKI_RENDERER_URL?: string;
readonly MERMAID_CDN_URL?: string;
readonly MERMAID_BASE_URL?: string;
readonly MERMAID_LOCAL?: boolean;
readonly MERMAID_ANALYTICS_URL?: string;
readonly MERMAID_DOCS_URL?: string;
readonly MERMAID_DOMAIN?: string;
readonly MERMAID_IS_ENABLED_MERMAID_CHART_LINKS?: string;
readonly MERMAID_PRIVACY_POLICY_URL?: string;
readonly MERMAID_HIDE_PRIVACY_POLICY?: string;
// more env variables...
}
-15
View File
@@ -1,16 +1 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-unused-vars */
/// <reference types="@sveltejs/kit" />
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers';
declare global {
namespace jest {
interface Matchers<R = void>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// eslint-disable-next-line no-undef
extends TestingLibraryMatchers<typeof expect.stringContaining, R> {}
}
}
-6
View File
@@ -1,6 +0,0 @@
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event, {});
return response;
};
+174
View File
@@ -0,0 +1,174 @@
<script lang="ts">
import { Button } from '$/components/ui/button';
import { cn } from '$lib/utils.js';
import CloseIcon from '~icons/material-symbols/close-rounded';
interface Props {
show: boolean;
input: string;
onClose: () => void;
onHeightChange?: (height: number) => void;
onTryFree: () => void;
}
let { show, input = $bindable(), onClose, onHeightChange, onTryFree }: Props = $props();
let textarea = $state<HTMLTextAreaElement>();
let container = $state<HTMLDivElement>();
$effect(() => {
if (!container || !onHeightChange) return;
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
if (entry.target instanceof HTMLElement) {
onHeightChange(entry.target.offsetHeight);
}
}
});
observer.observe(container);
return () => observer.disconnect();
});
function resizeTextarea() {
if (!textarea) return;
const computed = globalThis.getComputedStyle(textarea);
const lineHeight =
Number.parseFloat(computed.lineHeight) || Number.parseFloat(computed.fontSize) * 1.5 || 0;
const paddingTop = Number.parseFloat(computed.paddingTop) || 0;
const paddingBottom = Number.parseFloat(computed.paddingBottom) || 0;
const minHeight = lineHeight + paddingTop + paddingBottom;
const maxLines = 8;
const maxHeight = lineHeight * maxLines + paddingTop + paddingBottom;
textarea.style.height = 'auto';
const nextHeight = Math.max(minHeight, Math.min(textarea.scrollHeight, maxHeight));
textarea.style.height = `${nextHeight}px`;
textarea.style.overflowY = textarea.scrollHeight > maxHeight ? 'auto' : 'hidden';
}
$effect(() => {
if (input !== undefined) {
resizeTextarea();
}
});
function handleKeydown(e: KeyboardEvent) {
if (show && e.key === 'Escape') {
onClose();
}
}
function handleOutsideClick(e: MouseEvent) {
if (show && container && e.target instanceof Node && !container.contains(e.target)) {
onClose();
}
}
</script>
<svelte:window onkeydown={handleKeydown} onmousedown={handleOutsideClick} />
{#if show}
<div
bind:this={container}
class={cn(
'button-container-for-animation relative z-50 mr-6 flex w-auto flex-col gap-2 rounded-xl border-2 border-border bg-background p-2 shadow-xl dark:border-border-dark dark:bg-secondary',
!input.trim() && 'rainbow-border'
)}
role="dialog"
aria-modal="true"
tabindex="-1">
<div class="relative flex min-h-2 items-start gap-1 px-1">
<textarea
bind:this={textarea}
bind:value={input}
onkeydown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (input.trim()) {
onTryFree();
}
}
}}
placeholder="Describe what to add or change"
rows="1"
class="focus font-recursive min-h-0 flex-1 resize-none border-none bg-transparent px-1 text-sm font-normal text-foreground placeholder:text-muted-foreground focus:ring-0 focus:outline-none disabled:opacity-50 dark:text-foreground dark:placeholder:text-muted-foreground"
style="height: 20px; overflow-y: hidden;"></textarea>
<button onclick={onClose} class="text-muted-foreground hover:text-foreground">
<CloseIcon class="size-4" />
</button>
</div>
<div class="flex items-center justify-between">
<span class="font-recursive text-xs font-normal text-foreground dark:text-foreground"
>Sign Up at Mermaid.ai to try AI</span>
<Button
class="font-recursive h-6 w-16 gap-1.5 rounded-sm bg-accent p-1 text-xs font-medium text-white no-underline hover:bg-accent/90 hover:text-white hover:no-underline active:bg-accent/80 dark:bg-accent dark:text-white! dark:hover:bg-accent/90 dark:active:bg-accent/80"
onclick={onTryFree}>
Try free
</Button>
</div>
</div>
{/if}
<style>
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@keyframes gradient-angle-shift {
0% {
--gradient-angle: 0deg;
}
100% {
--gradient-angle: 360deg;
}
}
.button-container-for-animation {
border-radius: 12px;
}
.button-container-for-animation::before {
content: '';
position: absolute;
inset: -2px;
border-radius: 14px;
padding: 2px;
background: conic-gradient(
from var(--gradient-angle, 0deg),
color-mix(in srgb, var(--color-accent) 0%, transparent) 0%,
color-mix(in srgb, var(--color-accent) 0%, transparent) 12%,
color-mix(in srgb, var(--color-accent) 12%, transparent) 18%,
color-mix(in srgb, var(--color-accent) 42%, transparent) 24%,
color-mix(in srgb, var(--color-accent) 77%, transparent) 32%,
rgba(93, 85, 212, 0.923) 41%,
rgba(93, 85, 212, 0.5) 52%,
color-mix(in srgb, var(--color-accent) 58%, transparent) 72%,
color-mix(in srgb, var(--color-accent) 56%, transparent) 82%,
color-mix(in srgb, var(--color-accent) 36%, transparent) 87%,
color-mix(in srgb, var(--color-accent) 19%, transparent) 92%,
color-mix(in srgb, var(--color-accent) 10%, transparent) 96%,
color-mix(in srgb, var(--color-accent) 3%, transparent) 98%,
color-mix(in srgb, var(--color-accent) 0%, transparent) 100%
);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
pointer-events: none;
z-index: 0;
opacity: 0;
transition: opacity 0.3s ease-out;
}
.button-container-for-animation.rainbow-border::before {
opacity: 1;
animation: gradient-angle-shift 2s linear infinite;
}
</style>
+219 -161
View File
@@ -1,80 +1,100 @@
<script lang="ts">
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 { getDomain } from '$/util/util';
import { browser } from '$app/environment';
import Card from '$lib/components/Card/Card.svelte';
import { env } from '$lib/util/env';
import { pakoSerde } from '$lib/util/serde';
import { stateStore } from '$lib/util/state';
import { waitForRender } from '$lib/util/autoSync';
import { inputState, updateCodeStore, urls, validatedState } from '$lib/util/state.svelte';
import { logEvent } from '$lib/util/stats';
import { toBase64 } from 'js-base64';
import { version as FAVersion } from '@fortawesome/fontawesome-free/package.json';
import dayjs from 'dayjs';
const { krokiRendererUrl, rendererUrl } = env;
import { toBase64 } from 'js-base64';
import DownloadIcon from '~icons/material-symbols/download';
import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded';
import WidthIcon from '~icons/material-symbols/width-rounded';
const FONT_AWESOME_URL = `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/${FAVersion}/css/all.min.css`;
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
const getFileName = (ext: string) =>
`mermaid-diagram-${dayjs().format('YYYY-MM-DD-HHmmss')}.${ext}`;
const getFileName = (extension: string) =>
`mermaid-diagram-${dayjs().format('YYYY-MM-DD-HHmmss')}.${extension}`;
/**
* Fix text clipping in exported SVG for hand-drawn (rough) mode.
* svg2roughjs copies foreignObject elements but their height is often insufficient,
* causing text bottom edges to be cut off regardless of language.
*/
const fixForeignObjectClipping = (svg: HTMLElement) => {
const foreignObjects = svg.querySelectorAll('foreignObject');
foreignObjects.forEach((foreignObj) => {
const currentHeight = parseFloat(foreignObj.getAttribute('height') || '0');
if (currentHeight <= 0) return;
const currentY = parseFloat(foreignObj.getAttribute('y') || '0');
const newHeight = currentHeight * 1.5;
const heightDiff = newHeight - currentHeight;
foreignObj.setAttribute('height', newHeight.toString());
foreignObj.setAttribute('y', (currentY - heightDiff / 2).toString());
// Ensure inner HTML elements are vertically centered within the expanded area
const htmlElements = foreignObj.querySelectorAll('div, span, p');
htmlElements.forEach((htmlEl) => {
const el = htmlEl as HTMLElement;
el.style.display = 'flex';
el.style.alignItems = 'center';
el.style.justifyContent = 'center';
el.style.height = '100%';
});
});
};
const getSvgElement = () => {
const svgElement = document.querySelector('#container svg')?.cloneNode(true) as HTMLElement;
svgElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
return svgElement;
};
const getBase64SVG = (svg?: HTMLElement, width?: number, height?: number): string => {
height && svg?.setAttribute('height', `${height}px`);
width && svg?.setAttribute('width', `${width}px`); // Workaround https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
if (!svg) {
svg = getSvgEl();
if (svg) {
// Prevents the SVG size of the interface from being changed
svg = svg.cloneNode(true) as HTMLElement;
}
if (height) {
svg?.setAttribute('height', `${height}px`);
}
if (width) {
svg?.setAttribute('width', `${width}px`);
}
// Workaround https://stackoverflow.com/questions/28690643/firefox-error-rendering-an-svg-image-to-html5-canvas-with-drawimage
if (!svg) {
svg = getSvgElement();
}
if (validatedState.current.rough) {
fixForeignObjectClipping(svg);
}
svg.style.backgroundColor = window
.getComputedStyle(document.body)
.getPropertyValue('--background');
const svgString = svg.outerHTML
.replaceAll('<br>', '<br/>')
.replaceAll(/<img([^>]*)>/g, (m, g: string) => `<img ${g} />`);
return toBase64(svgString);
};
const exportImage = (event: Event, exporter: Exporter) => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const svg: HTMLElement | null = document.querySelector('#container svg');
if (!svg) {
throw new Error('svg not found');
}
const box: DOMRect = svg.getBoundingClientRect();
canvas.width = box.width;
canvas.height = box.height;
if (imagemodeselected === 'width') {
const ratio = box.height / box.width;
canvas.width = userimagesize;
canvas.height = userimagesize * ratio;
} else if (imagemodeselected === 'height') {
const ratio = box.width / box.height;
canvas.width = userimagesize * ratio;
canvas.height = userimagesize;
}
const context = canvas.getContext('2d');
if (!context) {
throw new Error('context not found');
}
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.onload = exporter(context, image);
image.src = `data:image/svg+xml;base64,${getBase64SVG(svg, canvas.width, canvas.height)}`;
event.stopPropagation();
event.preventDefault();
};
const getSvgEl = () => {
const svgEl: HTMLElement = document
.querySelector('#container svg')!
.cloneNode(true) as HTMLElement;
svgEl.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
const fontAwesomeCdnUrl = Array.from(document.head.getElementsByTagName('link'))
.map((l) => l.href)
.find((h) => h.includes('font-awesome'));
if (fontAwesomeCdnUrl == null) {
return svgEl;
}
const styleEl = document.createElement('style');
styleEl.innerText = `@import url("${fontAwesomeCdnUrl}");'`;
svgEl.prepend(styleEl);
return svgEl;
return toBase64(`<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="${FONT_AWESOME_URL}" type="text/css"?>
${svgString}`);
};
const simulateDownload = (download: string, href: string): void => {
@@ -84,6 +104,65 @@
a.click();
a.remove();
};
const exportImage = async (event: Event, exporter: Exporter) => {
updateCodeStore({ panZoom: false });
await new Promise((resolve) => setTimeout(resolve, 1000));
await waitForRender();
const canvas = document.createElement('canvas');
const svg = document.querySelector<HTMLElement>('#container svg');
if (!svg) {
throw new Error('svg not found');
}
const box = svg.getBoundingClientRect();
// In rough mode, SVG has width/height="100%" so getBoundingClientRect returns
// the container size, not the actual diagram size. Use viewBox dimensions instead.
const svgEl = svg as unknown as SVGSVGElement;
const viewBox = svgEl.viewBox?.baseVal;
const contentWidth = viewBox && viewBox.width > 0 ? viewBox.width : box.width;
const contentHeight = viewBox && viewBox.height > 0 ? viewBox.height : box.height;
if (imageSizeMode === 'width') {
const ratio = contentHeight / contentWidth;
canvas.width = imageSize;
canvas.height = imageSize * ratio;
} else if (imageSizeMode === 'height') {
const ratio = contentWidth / contentHeight;
canvas.width = imageSize * ratio;
canvas.height = imageSize;
} else {
const multiplier = 2;
canvas.width = contentWidth * multiplier;
canvas.height = contentHeight * multiplier;
}
const context = canvas.getContext('2d');
if (!context) {
throw new Error('context not found');
}
context.fillStyle = window.getComputedStyle(document.body).getPropertyValue('--background');
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.addEventListener('load', () => {
exporter(context, image)();
updateCodeStore({ panZoom: true });
});
image.src = `data:image/svg+xml;base64,${getBase64SVG(svg, canvas.width, canvas.height)}`;
// Fallback to set panZoom to true after 2 seconds
// This is a workaround for the case when the image is not loaded
setTimeout(() => {
if (!inputState.panZoom) {
updateCodeStore({ panZoom: true });
}
}, 2000);
event.stopPropagation();
event.preventDefault();
};
const downloadImage: Exporter = (context, image) => {
return () => {
const { canvas } = context;
@@ -96,7 +175,7 @@
};
const isClipboardAvailable = (): boolean => {
return Object.prototype.hasOwnProperty.call(window, 'ClipboardItem') as boolean;
return Object.prototype.hasOwnProperty.call(window, 'ClipboardItem');
};
const clipboardCopy: Exporter = (context, image) => {
@@ -120,13 +199,16 @@
};
};
const onCopyClipboard = (event: Event) => {
exportImage(event, clipboardCopy);
const onCopyClipboard = async (event?: Event) => {
if (!event) {
return;
}
await exportImage(event, clipboardCopy);
logEvent('copyClipboard');
};
const onDownloadPNG = (event: Event) => {
exportImage(event, downloadImage);
const onDownloadPNG = async (event: Event) => {
await exportImage(event, downloadImage);
logEvent('download', {
type: 'png'
});
@@ -139,124 +221,100 @@
});
};
const onCopyMarkdown = () => {
(document.getElementById('markdown') as HTMLInputElement).select();
document.execCommand('Copy');
logEvent('copyMarkdown');
};
let gistURL = '';
stateStore.subscribe(({ loader }) => {
let gistURL = $state('');
$effect(() => {
const { loader } = validatedState.current;
if (loader?.type === 'gist') {
// @ts-expect-error Gist will have url
gistURL = loader.config.url;
}
});
const loadGist = () => {
if (!gistURL) {
alert('Please enter a Gist URL first');
return alert('Please enter a Gist URL first');
}
window.location.href = `${window.location.pathname}?gist=${gistURL}`;
logEvent('loadGist');
};
let iUrl: string;
let svgUrl: string;
let krokiUrl: string;
let mdCode: string;
let imagemodeselected = 'auto';
let userimagesize = 1080;
let imageSizeMode: 'auto' | 'width' | 'height' = $state('auto');
let isNetlify = false;
if (browser && ['mermaid.live', 'netlify'].some((path) => window.location.host.includes(path))) {
isNetlify = true;
$effect(() => {
if (!imageSizeMode) {
imageSizeMode = 'auto';
}
stateStore.subscribe(({ code, serialized }) => {
iUrl = `${rendererUrl}/img/${serialized}?type=png`;
svgUrl = `${rendererUrl}/svg/${serialized}`;
krokiUrl = `${krokiRendererUrl}/mermaid/svg/${pakoSerde.serialize(code)}`;
mdCode = `[![](${iUrl})](${window.location.protocol}//${window.location.host}${window.location.pathname}#${serialized})`;
});
let imageSize = $state(1080);
const isNetlify = browser && window.location.host.includes('netlify');
</script>
<Card title="Actions" isOpen={false}>
<div class="flex flex-wrap gap-2 m-2">
{#if isClipboardAvailable()}
<button class="action-btn w-full" on:click={onCopyClipboard}
><i class="far fa-copy mr-2" /> Copy Image to clipboard
</button>
{/if}
<button id="downloadPNG" class="action-btn flex-grow" on:click={onDownloadPNG}>
<i class="fas fa-download mr-2" /> PNG
</button>
<button id="downloadSVG" class="action-btn flex-grow" on:click={onDownloadSVG}>
<i class="fas fa-download mr-2" /> SVG
</button>
<a target="_blank" rel="noreferrer" class="flex-grow" href={iUrl}>
<button class="action-btn w-full">
<i class="fas fa-external-link-alt mr-2" /> PNG
</button>
</a>
<a target="_blank" rel="noreferrer" class="flex-grow" href={svgUrl}>
<button class="action-btn w-full">
<i class="fas fa-external-link-alt mr-2" /> SVG
</button>
</a>
<a target="_blank" rel="noreferrer" class="flex-grow" href={krokiUrl}>
<button class="action-btn w-full">
<i class="fas fa-external-link-alt mr-2" /> Kroki
</button>
</a>
{#snippet dualActionButton(text: string, download: (event: Event) => unknown, url?: string)}
<div class="flex flex-grow gap-0.5">
<Button
class={['flex-grow', url && 'rounded-r-none']}
onclick={download}
data-testid="download-{text}">
<DownloadIcon />
{text}
</Button>
<ExternalLinkWrapper domain={getDomain(url)} isVisible={!!url}>
<Button class="rounded-l-none" href={url} target="_blank" rel="noreferrer noopener">
<ExternalLinkIcon />
</Button>
</ExternalLinkWrapper>
</div>
{/snippet}
<div class="flex gap-2 items-center">
<Card title="Actions" isStackable icon={{ component: DownloadIcon, class: 'rotate-180' }}>
<div class="flex min-w-fit flex-col gap-2 p-2">
<div class="flex w-full items-center gap-2 py-2 whitespace-nowrap">
PNG size
<label for="autosize">
<input type="radio" value="auto" id="autosize" bind:group={imagemodeselected} /> Auto
</label>
<label for="width">
<input type="radio" value="width" id="width" bind:group={imagemodeselected} /> Width
</label>
<label for="height">
<input type="radio" value="height" id="height" bind:group={imagemodeselected} /> Height
</label>
{#if imagemodeselected !== 'auto'}
<input
id="height"
class="input"
<ToggleGroup.Root type="single" variant="outline" bind:value={imageSizeMode}>
<ToggleGroup.Item value="auto">Auto</ToggleGroup.Item>
<ToggleGroup.Item value="width">Width</ToggleGroup.Item>
<ToggleGroup.Item value="height">Height</ToggleGroup.Item>
</ToggleGroup.Root>
{#if imageSizeMode !== 'auto'}
<WidthIcon
class={['size-6 shrink-0 transition-all', imageSizeMode === 'width' && 'rotate-90']} />
{/if}
<Input
type="number"
min="3"
max="10000"
bind:value={userimagesize} />
disabled={imageSizeMode === 'auto'}
bind:value={imageSize} />
</div>
<div class="flex gap-2">
{@render dualActionButton('PNG', onDownloadPNG, urls.current.png)}
{@render dualActionButton('SVG', onDownloadSVG, urls.current.svg)}
<ExternalLinkWrapper domain={getDomain(urls.current.kroki)} isVisible={!!urls.current.kroki}>
<a target="_blank" rel="noreferrer" class="flex-grow" href={urls.current.kroki}>
<Button class="action-btn flex w-full items-center gap-2">
<ExternalLinkIcon /> Kroki
</Button>
</a>
</ExternalLinkWrapper>
</div>
<Separator />
{#if isClipboardAvailable()}
<CopyButton onclick={onCopyClipboard} label="Copy Image" />
{/if}
</div>
<div class="w-full flex gap-2 items-center">
<input class="input" id="markdown" type="text" value={mdCode} on:click={onCopyMarkdown} />
<label for="markdown">
<button class="btn btn-primary btn-md flex-auto" on:click={onCopyMarkdown}>
Copy Markdown
</button>
</label>
</div>
<div class="w-full flex gap-2 items-center">
<input
class="input"
id="gist"
type="text"
bind:value={gistURL}
placeholder="Enter Gist URL" />
<label for="gist">
<button class="btn btn-primary btn-md flex-auto" on:click={loadGist}> Load Gist </button>
</label>
<ExternalLinkWrapper
labelPrefix="Thumbnail generated by"
domain={getDomain(urls.current.png)}
isVisible={!!urls.current.mdCode}>
<CopyInput value={urls.current.mdCode} label="Copy Markdown" testID={TID.copyMarkdown} />
</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>
</div>
{#if isNetlify}
<div class="w-full flex items-center justify-center">
<a class="link underline text-gray-500 text-sm" href="https://netlify.com">
<div class="flex w-full items-center justify-center">
<a class="link text-sm text-gray-500 underline" href="https://netlify.com">
This site is powered by Netlify
</a>
</div>
+74 -20
View File
@@ -1,31 +1,85 @@
<script lang="ts">
import type { Tab } from '$lib/types';
import type { Tab } from '$/types';
import type { Component, Snippet } from 'svelte';
import { quintOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
import CollapseAllIcon from '~icons/material-symbols/collapse-all-rounded';
import Tabs from './Tabs.svelte';
export let isCloseable = true;
export let isOpen = true;
export let tabs: Tab[] = [];
export let activeTabID = '';
export let title: string;
$: isOpen = isCloseable ? isOpen : true;
$: isTabsShown = isOpen && tabs.length > 0;
interface Props {
isClosable?: boolean;
isOpen?: boolean;
isStackable?: boolean;
tabs?: Tab[];
activeTabID?: string;
title?: string;
icon?: {
component: Component;
class?: string;
};
onselect?: (tab: Tab) => void;
actions?: Snippet;
children: Snippet;
}
let {
isClosable = true,
isOpen = false,
isStackable = false,
tabs = [],
activeTabID = '',
title,
icon,
onselect,
actions,
children
}: Props = $props();
const toggleCardOpen = () => {
if (isClosable) {
isOpen = !isOpen;
}
};
let isTabsShown = $derived(isOpen && tabs.length > 0);
</script>
<div class="card rounded overflow-hidden m-2 flex-grow flex flex-col shadow-2xl">
<div
class={[
'card flex h-fit flex-col overflow-hidden rounded-2xl border-2 border-muted',
isOpen && 'isOpen flex-grow',
isStackable ? 'flex-1 group-has-[.isOpen]:w-full group-has-[.isOpen]:flex-none' : 'w-full'
]}>
<div
class="bg-primary p-2 {isTabsShown ? 'pb-0' : ''} flex-none cursor-pointer"
on:click={() => (isOpen = !isOpen)}
on:keypress={() => (isOpen = !isOpen)}>
<div class="flex justify-between">
<Tabs on:select {tabs} bind:isOpen {title} {isCloseable} {activeTabID} />
<div class="flex gap-x-4 items-center {isTabsShown ? '-mt-2' : ''}">
<slot name="actions" />
</div>
</div>
role="toolbar"
tabindex="0"
class={[
'flex h-11 flex-none cursor-pointer items-center justify-between bg-muted p-2 whitespace-nowrap',
isTabsShown && 'pb-1'
]}
onclick={toggleCardOpen}
onkeypress={toggleCardOpen}>
{#if icon || title}
<span role="menubar" tabindex="0" class="flex w-fit items-center gap-3">
{#if icon}
<icon.component class={icon.class} />
{/if}
{title}
</span>
{/if}
{#if isOpen && tabs && tabs.length > 0}
<Tabs {onselect} {tabs} {activeTabID} />
{/if}
{@render actions?.()}
{#if isOpen && isClosable}
<CollapseAllIcon />
{/if}
</div>
{#if isOpen}
<div class="card-body p-0 flex-grow overflow-auto text-base-content" transition:slide>
<slot />
<div class="flex-grow overflow-x-auto" transition:slide={{ easing: quintOut }}>
{@render children()}
</div>
{/if}
</div>
+39 -40
View File
@@ -1,52 +1,51 @@
<script lang="ts">
import type { Tab, TabEvents } from '$lib/types';
import { createEventDispatcher } from 'svelte';
import { Button } from '$/components/ui/button';
import { Separator } from '$/components/ui/separator';
import type { Tab } from '$lib/types';
import { fade } from 'svelte/transition';
export let isCloseable = true;
export let tabs: Tab[];
export let title: string;
export let isOpen = false;
export let activeTabID: string;
if (!activeTabID && tabs.length > 0) {
activeTabID = tabs[0].id;
}
const dispatch = createEventDispatcher<TabEvents>();
let {
tabs,
activeTabID,
onselect
}: {
tabs: Tab[];
activeTabID: string;
onselect?: (tab: Tab) => void;
} = $props();
// Derive (don't mutate the prop) so the highlight tracks a bound activeTabID.
const effectiveTabID = $derived(activeTabID || tabs[0]?.id);
const toggleTabs = (tab: Tab) => {
activeTabID = tab.id;
dispatch('select', tab);
return (event: Event) => {
event.stopPropagation();
onselect?.(tab);
};
};
</script>
<div class="flex cursor-default">
<span
class="mr-2 font-semibold"
on:click|stopPropagation={() => (isOpen = !isOpen)}
on:keypress|stopPropagation={() => (isOpen = !isOpen)}>
{#if isCloseable}
<i class="fas fa-chevron-right icon" class:isOpen />
{/if}
{title}</span>
{#if isOpen && tabs}
<ul class="tabs" transition:fade>
{#each tabs as tab}
<div
class="tab tab-lifted {activeTabID === tab.id ? 'tab-active' : 'text-primary-content'}"
on:click|stopPropagation={() => toggleTabs(tab)}
on:keypress|stopPropagation={() => toggleTabs(tab)}>
<i class="mr-1 {tab.icon}" />
<div class="flex w-fit cursor-default items-center gap-2">
<ul class="flex gap-2 align-middle" transition:fade>
{#each tabs as tab, index (tab.id)}
<Button
role="tab"
variant="ghost"
class={[
'px-2',
effectiveTabID === tab.id && 'rounded-b-none border-b-2 border-b-primary-foreground/50'
]}
onclick={toggleTabs(tab)}
onkeypress={toggleTabs(tab)}>
<tab.icon />
{tab.title}
</Button>
{#if index < tabs.length - 1}
<div class="my-2">
<Separator orientation="vertical" class="w-0.5 bg-slate-300" />
</div>
{/if}
{/each}
</ul>
{/if}
</div>
<style>
.icon {
transition-duration: 0.5s;
}
.isOpen {
transform: rotate(90deg);
}
</style>
-23
View File
@@ -1,23 +0,0 @@
import { cleanup, render } from '@testing-library/svelte';
import { describe, expect, it, afterEach } from 'vitest';
import Card from './Card.svelte';
describe('card.svelte', () => {
// TODO: @testing-library/svelte claims to add this automatically but it doesn't work without explicit afterEach
afterEach(() => cleanup());
it('mounts', () => {
const { container } = render(Card, {
title: 'TabTest',
tabs: [
{ id: 't1', title: 'title1', icon: 'fab fa-git-alt' },
{ id: 't2', title: 'title2', icon: 'far fa-bookmark' }
]
});
expect(container).toBeTruthy();
expect(container).toHaveTextContent('TabTest');
expect(container).toHaveTextContent('title1');
expect(container).toHaveTextContent('title2');
expect(container).not.toHaveTextContent('title3');
});
});
+40
View File
@@ -0,0 +1,40 @@
<script lang="ts">
import { Button } from '$/components/ui/button';
import { notify } from '$/util/notify';
import { scale } from 'svelte/transition';
import CheckIcon from '~icons/material-symbols/check-rounded';
import CopyIcon from '~icons/material-symbols/content-copy-outline-rounded';
let {
onclick,
label = 'Copy'
}: { onclick: (event?: Event) => Promise<unknown>; label?: string } = $props();
let showCheckIcon = $state(false);
</script>
<Button
onclick={async (event) => {
try {
showCheckIcon = true;
setTimeout(() => {
showCheckIcon = false;
}, 1000);
await onclick(event);
} catch {
notify('Failed to copy');
}
}}>
<div class="grid">
{#key showCheckIcon}
<span transition:scale class="col-start-1 row-start-1">
{#if showCheckIcon}
<CheckIcon />
{:else}
<CopyIcon />
{/if}
</span>
{/key}
</div>
{label}
</Button>
+25
View File
@@ -0,0 +1,25 @@
<script lang="ts">
import CopyButton from '$/components/CopyButton.svelte';
import { Input } from '$/components/ui/input';
import type { InputType } from '$/types';
import { copyToClipboard } from '$/util/util';
let {
value,
label = 'Copy',
type = 'url',
testID
}: { value: string; label?: string; type?: InputType; testID?: string } = $props();
</script>
<div class="flex w-full items-center gap-2">
<Input
{type}
{value}
data-testid={testID}
onclick={(event) => {
event.currentTarget.setSelectionRange(0, event.currentTarget.value.length);
}} />
<CopyButton onclick={() => copyToClipboard(value)} {label} />
</div>
+273
View File
@@ -0,0 +1,273 @@
<script lang="ts">
import type { EditorProps } from '$/types';
import { env } from '$/util/env';
import { urls, validatedState } from '$/util/state.svelte';
import { logMermaidChartClick } from '$/util/stats';
import { AIPromptViewZoneManager } from '$lib/util/AIPromptViewZoneManager';
import { initEditor } from '$lib/util/monacoExtra';
import { errorDebug } from '$lib/util/util';
import { mode } from 'mode-watcher';
import * as monaco from 'monaco-editor';
import monacoEditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import monacoJsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import { onMount } from 'svelte';
import AIPromptPopup from './AIPromptPopup.svelte';
const { onUpdate }: EditorProps = $props();
let divElement: HTMLDivElement | undefined = $state();
let aiPromptPopupElement: HTMLDivElement | undefined = $state();
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
let editorOptions = {
minimap: {
enabled: false
},
overviewRulerLanes: 0,
glyphMargin: true,
lineNumbersMinChars: 4
} satisfies monaco.editor.IStandaloneEditorConstructionOptions;
let currentText = '';
let isUpdatingFromState = false;
let showPopup = $state(false);
let popupPosition = $state({ top: 0, lineNumber: 0 });
let decorationsCollection: monaco.editor.IEditorDecorationsCollection | undefined;
let input = $state('');
let lastMouseLine = 0;
const aiPromptManager = new AIPromptViewZoneManager();
const applyEditorTheme = (currentMode: typeof mode.current) => {
if (!editor) return;
monaco.editor.setTheme(`mermaid${currentMode === 'dark' ? '-dark' : ''}`);
divElement?.classList.toggle('mermaid-dark', currentMode === 'dark');
};
$effect(() => {
applyEditorTheme(mode.current);
});
const jsonModel = monaco.editor.createModel(
'',
'json',
monaco.Uri.parse('internal://config.json')
);
const mermaidModel = monaco.editor.createModel(
'',
'mermaid',
monaco.Uri.parse('internal://mermaid.mmd')
);
const renderAIPromptGutterGlyphIcon = () => {
decorationsCollection?.clear();
if (!editor || showPopup) {
return;
}
const model = editor.getModel();
if (!model) {
return;
}
if (lastMouseLine > 0 && model.id === mermaidModel.id) {
decorationsCollection?.set([
{
range: new monaco.Range(lastMouseLine, 1, lastMouseLine, 1),
options: {
glyphMarginClassName: 'suggestion-icon'
}
}
]);
}
};
const closePopup = () => {
showPopup = false;
input = '';
aiPromptManager.hide();
renderAIPromptGutterGlyphIcon();
};
const toggleAIPopup = (lineNumber: number) => {
if (!divElement || !aiPromptPopupElement) return;
popupPosition = {
top: 0,
lineNumber
};
showPopup = !showPopup;
if (showPopup) {
aiPromptManager.show(popupPosition.lineNumber, aiPromptPopupElement, 100);
editor?.setSelection(new monaco.Range(0, 0, 0, 0));
} else {
aiPromptManager.hide();
}
renderAIPromptGutterGlyphIcon();
};
onMount(() => {
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new monacoJsonWorker();
}
return new monacoEditorWorker();
}
};
if (!divElement) {
throw new Error('divEl is undefined');
}
monaco.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
enableSchemaRequest: true,
schemas: [
{
fileMatch: ['config.json'],
uri: `${env.docsUrl}/schemas/config.schema.json`
}
]
});
initEditor(monaco);
errorDebug();
editor = monaco.editor.create(divElement, editorOptions);
aiPromptManager.setEditor(editor);
decorationsCollection = editor.createDecorationsCollection([]);
editor.onMouseDown((e) => {
const isGutter = e.target.type === monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN;
if (isGutter && e.target.position?.lineNumber === lastMouseLine && lastMouseLine > 0) {
e.event.preventDefault();
e.event.stopPropagation();
toggleAIPopup(e.target.position.lineNumber);
}
});
editor.onDidChangeModelContent(({ isFlush }) => {
const newText = editor?.getValue();
if (!newText || currentText === newText || isFlush || isUpdatingFromState) {
return;
}
currentText = newText;
onUpdate(currentText);
});
editor.onMouseMove((e) => {
if (!editor) return;
if (showPopup) return;
if (editor.getModel()?.id !== mermaidModel.id) return;
lastMouseLine = e.target.position?.lineNumber ?? 0;
renderAIPromptGutterGlyphIcon();
});
editor.onMouseLeave(() => {
lastMouseLine = 0;
renderAIPromptGutterGlyphIcon();
});
applyEditorTheme(mode.current);
const resizeObserver = new ResizeObserver((entries) => {
editor?.layout({
height: entries[0].contentRect.height,
width: entries[0].contentRect.width
});
});
if (divElement.parentElement) {
resizeObserver.observe(divElement);
}
renderAIPromptGutterGlyphIcon();
return () => {
resizeObserver.disconnect();
jsonModel.dispose();
mermaidModel.dispose();
aiPromptManager.destroy();
editor?.dispose();
};
});
$effect(() => {
const { errorMarkers, editorMode, code, mermaid } = validatedState.current;
if (!editor) {
return;
}
const model = editorMode === 'code' ? mermaidModel : jsonModel;
if (editor.getModel()?.id !== model.id) {
editor.setModel(model);
renderAIPromptGutterGlyphIcon();
}
// Clear decorations if not in 'code' mode, or if the model changes
if (editorMode !== 'code' || editor.getModel()?.id !== mermaidModel.id) {
decorationsCollection?.clear();
}
// Update editor text if it's different
const newText = editorMode === 'code' ? code : mermaid;
if (newText !== currentText) {
isUpdatingFromState = true;
try {
editor.setScrollTop(0);
editor.pushUndoStop();
editor.executeEdits('updateCode', [
{
range: model.getFullModelRange(),
text: newText
}
]);
editor.pushUndoStop();
currentText = newText;
} finally {
isUpdatingFromState = false;
}
renderAIPromptGutterGlyphIcon();
}
// Display/clear errors
monaco.editor.setModelMarkers(model, 'mermaid', errorMarkers);
});
</script>
<div class="relative h-full grow overflow-hidden">
<div bind:this={divElement} id="editor" class="h-full w-full"></div>
<div bind:this={aiPromptPopupElement}>
<AIPromptPopup
show={showPopup}
bind:input
onHeightChange={(height) => aiPromptManager.updateHeight(height)}
onClose={closePopup}
onTryFree={() => {
logMermaidChartClick('vibeDiagramming');
window.open(
urls.current.mermaidChart({ medium: 'vibe_diagramming' }).save,
'_blank',
'noopener'
);
closePopup();
}} />
</div>
</div>
<style>
:global(.suggestion-icon) {
background-color: #e8eaf9;
width: 20px !important;
height: 20px !important;
margin-left: 4px;
background-image: url('/icons/use-chat.svg');
background-size: 16px 16px;
background-repeat: no-repeat;
background-position: center;
border-radius: 4px;
cursor: pointer;
}
:global(#editor.mermaid-dark .suggestion-icon) {
background-color: #2e4d6b;
background-image: url('/icons/use-chat-dark.svg');
}
</style>
@@ -0,0 +1,116 @@
<script lang="ts">
import { Button } from '$/components/ui/button';
import { TID } from '$/constants';
import type { DocumentationConfig } from '$/types';
import { env } from '$/util/env';
import { standardizeDiagramType } from '$/util/mermaid';
import { validatedState } from '$/util/state.svelte';
import BookIcon from '~icons/material-symbols/book-2-outline-rounded';
const docURLBase = env.docsUrl;
const docMap = {
architecture: {
code: '/syntax/architecture.html'
},
block: {
code: '/syntax/block.html'
},
c4: {
code: '/syntax/c4.html'
},
class: {
code: '/syntax/classDiagram.html',
config: '/syntax/classDiagram.html#configuration'
},
er: {
code: '/syntax/entityRelationshipDiagram.html',
config: '/syntax/entityRelationshipDiagram.html#styling'
},
flowchart: {
code: '/syntax/flowchart.html',
config: '/syntax/flowchart.html#configuration'
},
gantt: {
code: '/syntax/gantt.html',
config: '/syntax/gantt.html#configuration'
},
gitGraph: {
code: '/syntax/gitgraph.html',
config: '/syntax/gitgraph.html#gitgraph-specific-configuration-options'
},
journey: {
code: '/syntax/userJourney.html'
},
kanban: {
code: '/syntax/kanban.html',
config: '/syntax/kanban.html#configuration-options'
},
mindmap: {
code: '/syntax/mindmap.html'
},
packet: {
code: '/syntax/packet.html',
config: '/config/schema-docs/config-defs-packet-diagram-config.html'
},
pie: {
code: '/syntax/pie.html',
config: '/syntax/pie.html#configuration'
},
quadrantChart: {
code: '/syntax/quadrantChart.html',
config: '/syntax/quadrantChart.html#chart-configurations'
},
requirement: {
code: '/syntax/requirementDiagram.html'
},
sankey: {
code: '/syntax/sankey.html',
config: '/syntax/sankey.html#configuration'
},
sequence: {
code: '/syntax/sequenceDiagram.html',
config: '/syntax/sequenceDiagram.html#configuration'
},
stateDiagram: {
code: '/syntax/stateDiagram.html'
},
timeline: {
code: '/syntax/timeline.html',
config: '/syntax/timeline.html#themes'
},
treemap: {
code: '/syntax/treemap.html',
config: '/syntax/treemap.html#configuration-options'
},
xychart: {
code: '/syntax/xyChart.html',
config: '/syntax/xyChart.html#chart-configurations'
},
zenuml: {
code: '/syntax/zenuml.html'
}
} as const satisfies DocumentationConfig;
const doc = $derived.by(() => {
const { editorMode, diagramType } = validatedState.current;
if (!diagramType) {
return { key: '', url: docURLBase };
}
const key = standardizeDiagramType(diagramType);
const docConfig: { code: string; config?: string } = docMap[key as keyof typeof docMap] ?? {
code: ''
};
const url = docURLBase + (docConfig[editorMode] ?? docConfig.code ?? '');
return { key, url };
});
</script>
<Button
variant="ghost"
data-testid={TID.diagramDocumentationButton}
href={doc.url}
target="_blank"
title="View documentation for {doc.key.replace('Diagram', '')} diagram">
<BookIcon />
Docs
</Button>
+38
View File
@@ -0,0 +1,38 @@
<script lang="ts">
import { Button } from '$/components/ui/button';
import * as Popover from '$/components/ui/popover';
import type { Component } from 'svelte';
interface Props {
links: { title: string; href: string }[];
icon?: Component;
class?: string;
}
let props: Props = $props();
</script>
<Popover.Root>
<Popover.Trigger class="flex items-center gap-0">
<Button variant="ghost" size="sm">
<props.icon class={props.class} />
</Button>
</Popover.Trigger>
<Popover.Content>
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<ul tabindex="0" class="flex flex-col">
{#each props.links as { href, title } (title)}
<li class="rounded-md p-2 hover:bg-muted">
<a
role="menuitem"
tabindex="0"
class="whitespace-nowrap underline"
target="_blank"
{href}>
{title}
</a>
</li>
{/each}
</ul>
</Popover.Content>
</Popover.Root>
+66 -111
View File
@@ -1,128 +1,83 @@
<script lang="ts">
import type { EditorMode } from '$lib/types';
import { stateStore, updateCode, updateConfig } from '$lib/util/state';
import { themeStore } from '$lib/util/theme';
import { errorDebug, syncDiagram } from '$lib/util/util';
import * as monaco from 'monaco-editor';
import monacoJsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import monacoEditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import { onMount } from 'svelte';
import { initEditor } from '$lib/util/monacoExtra';
import { logEvent } from '$lib/util/stats';
import DesktopEditor from '$/components/DesktopEditor.svelte';
import McWrapper from '$/components/McWrapper.svelte';
import MermaidChartIcon from '$/components/MermaidChartIcon.svelte';
import MobileEditor from '$/components/MobileEditor.svelte';
import { Button } from '$/components/ui/button';
import { TID } from '$/constants';
import { env } from '$/util/env';
import { updateCode, updateConfig, urls, validatedState } from '$lib/util/state.svelte';
import { logMermaidChartClick } from '$lib/util/stats';
import { debounce } from 'lodash-es';
import ExclamationCircleIcon from '~icons/material-symbols/error-outline-rounded';
let divEl: HTMLDivElement | undefined = undefined;
let editor: monaco.editor.IStandaloneCodeEditor | undefined;
let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
minimap: {
enabled: false
},
theme: 'mermaid',
overviewRulerLanes: 0
};
let text = '';
stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
// console.log('editor store subscription', { code, mermaid });
if (!editor) {
return;
}
// Update editor text if it's different
const newText = editorMode === 'code' ? code : mermaid;
if (newText !== text) {
// console.log('updating editor text', newText);
editor.setScrollTop(0);
editor.setValue(newText);
text = newText;
}
// Update editor mode if it's different
const language = editorMode === 'code' ? 'mermaid' : 'json';
const model = editor.getModel();
if (!model) {
console.error("editor model doesn't exist");
return;
}
if (model.getLanguageId() !== language) {
monaco.editor.setModelLanguage(model, language);
}
// Display/clear errors
monaco.editor.setModelMarkers(model, 'mermaid', errorMarkers);
});
themeStore.subscribe(({ isDark }) => {
editor && monaco.editor.setTheme(isDark ? 'mermaid-dark' : 'mermaid');
});
const handleUpdate = (text: string, mode: EditorMode) => {
// console.log('editor HandleUpdate', { text, mode });
if (mode === 'code') {
const { isMobile } = $props<{ isMobile: boolean }>();
const onUpdate = (text: string) => {
if (validatedState.current.editorMode === 'code') {
updateCode(text);
} else {
updateConfig(text);
}
};
onMount(async () => {
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new monacoJsonWorker();
}
return new monacoEditorWorker();
}
};
let showError = $state(false);
if (!divEl) {
throw new Error('divEl is undefined');
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
initEditor(monaco);
errorDebug(100);
editor = monaco.editor.create(divEl, editorOptions);
editor.onDidChangeModelContent(({ isFlush, changes }) => {
const newText = editor?.getValue();
// console.log('editor onDidChangeModelContent', { text, newText, isFlush, changes });
if (!newText || text === newText || isFlush) {
return;
}
text = newText;
handleUpdate(text, $stateStore.editorMode);
});
editor.addAction({
id: 'mermaid-render-diagram',
label: 'Render Diagram',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
run: function () {
syncDiagram();
logEvent('renderDiagram', {
method: 'keyboardShortcut'
});
}
});
monaco.editor.setTheme($themeStore.isDark ? 'mermaid-dark' : 'mermaid');
const resizeObserver = new ResizeObserver((entries) => {
editor?.layout({
height: entries[0].contentRect.height,
width: entries[0].contentRect.width
});
});
const showErrorDebounced = debounce(() => {
showError = true;
}, 3000);
if (divEl.parentElement) {
resizeObserver.observe(divEl.parentElement);
$effect(() => {
if (validatedState.current.error) {
showErrorDebounced();
} else {
showErrorDebounced.cancel();
showError = false;
}
// @ts-ignore
if (window.Cypress) {
// @ts-ignore
window.editorLoaded = true;
}
return () => {
// console.log(`editor disposed`);
editor?.dispose();
showErrorDebounced.cancel();
};
});
</script>
<div bind:this={divEl} id="editor" class="overflow-hidden" />
<div class="flex h-full flex-col">
{#if isMobile}
<MobileEditor {onUpdate} />
{:else}
<DesktopEditor {onUpdate} />
{/if}
{#if showError && validatedState.current.error instanceof Error}
<div class="flex flex-col text-sm" data-testid={TID.errorContainer}>
<div class="flex items-center justify-between gap-2 bg-slate-900 p-2 text-white">
<div class="flex w-fit items-center gap-2">
<ExclamationCircleIcon class="size-6 text-destructive" aria-hidden="true" />
<div class="flex flex-col">
<p>Syntax error</p>
{#if env.isEnabledMermaidChartLinks && validatedState.current.editorMode === 'code'}
<p class="text-xs text-white/60" data-testid={TID.aiHelpText}>
Create a free account to repair with AI
</p>
{/if}
</div>
</div>
{#if validatedState.current.editorMode === 'code'}
<McWrapper>
<Button
variant="accent"
size="sm"
data-testid={TID.aiRepairButton}
href={urls.current.mermaidChart({ medium: 'ai_repair' }).save}
target="_blank"
onclick={() => logMermaidChartClick('aiRepair')}>
<MermaidChartIcon />
AI Repair
</Button>
</McWrapper>
{/if}
</div>
<output class="max-h-32 overflow-auto bg-muted p-2" name="mermaid-error" for="editor">
<pre>{validatedState.current.error?.toString()}</pre>
</output>
</div>
{/if}
</div>
@@ -0,0 +1,119 @@
<script lang="ts">
import McWrapper from '$/components/McWrapper.svelte';
import MermaidChartIcon from '$/components/MermaidChartIcon.svelte';
import { Button } from '$/components/ui/button';
import { standardizeDiagramType } from '$/util/mermaid';
import { validatedState, urls } from '$/util/state.svelte';
import { logMermaidChartClick } from '$/util/stats';
import { quintInOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
const visualEditDiagramTypes = new Set([
'flowchart',
'stateDiagram',
'classDiagram',
'sequenceDiagram',
'er',
'requirement',
'mindmap'
]);
const diagramType = $derived.by(() => {
const dt = validatedState.current.diagramType;
return dt ? standardizeDiagramType(dt) : undefined;
});
const showVisualEdit = $derived.by(() => {
return diagramType ? visualEditDiagramTypes.has(diagramType) : false;
});
interface EnhancedEditAction {
campaign: string;
label: string;
medium: 'ai_edit' | 'visual_edit' | 'voice_edit';
source: string;
}
const cycleIntervalMs = 30_000;
let currentActionIndex = $state(0);
const availableActions = $derived.by<EnhancedEditAction[]>(() => {
if (!validatedState.current.diagramType) {
return [];
}
const actions: EnhancedEditAction[] = [
{ campaign: 'voice_1', label: 'with voice', medium: 'voice_edit', source: 'voiceEdit' },
{ campaign: 'ai_1', label: 'with AI', medium: 'ai_edit', source: 'aiEdit' }
];
if (showVisualEdit) {
actions.unshift({
campaign: 'visual_1',
label: 'visually',
medium: 'visual_edit',
source: 'visualEdit'
});
}
return actions;
});
const currentAction = $derived.by(() => {
const actions = availableActions;
if (actions.length === 0) {
return undefined;
}
return actions[currentActionIndex % actions.length];
});
$effect(() => {
const actionCount = availableActions.length;
if (actionCount === 0) {
currentActionIndex = 0;
return;
}
if (currentActionIndex >= actionCount) {
currentActionIndex = 0;
}
if (actionCount <= 1) {
return;
}
const intervalID = setInterval(() => {
currentActionIndex = (currentActionIndex + 1) % actionCount;
}, cycleIntervalMs);
return () => clearInterval(intervalID);
});
</script>
{#if currentAction}
<McWrapper>
<Button
variant="secondary"
size="sm"
href={urls.current.mermaidChart({
medium: currentAction.medium,
campaign: currentAction.campaign
}).save}
target="_blank"
onclick={() => logMermaidChartClick(currentAction.source)}>
<MermaidChartIcon />
Edit
{#key currentAction.label}
<span
class="-ml-1"
in:slide={{ axis: 'x', easing: quintInOut, delay: 400 }}
out:slide={{ axis: 'x', easing: quintInOut }}>
{currentAction.label}
</span>
{/key}
</Button>
</McWrapper>
{/if}
@@ -0,0 +1,63 @@
<script lang="ts">
import { validatedState } from '$/util/state.svelte';
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,
showPopup = true
}: {
children: Snippet;
domain: string;
shouldCheckDiagramType?: boolean;
side?: ComponentProps<typeof Tooltip.Content>['side'];
labelPrefix?: string;
isVisible?: boolean;
sharesData?: boolean;
showPopup?: boolean;
} = $props();
let shouldDisableComponent = $derived(
shouldCheckDiagramType && validatedState.current.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>
{#if showPopup}
<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>
{/if}
</Tooltip.Root>
</Tooltip.Provider>
{/if}
+13
View File
@@ -0,0 +1,13 @@
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
children: Snippet;
}
let { children }: Props = $props();
</script>
<div class="flex h-12 items-center justify-between gap-2 rounded-2xl bg-muted p-3">
{@render children()}
</div>
+53
View File
@@ -0,0 +1,53 @@
<!--
@component
Exports a `waitForFontAwesomeToLoad` function that waits for FontAwesome to load.
Usage:
```svelte
<script lang="ts">
import FontAwesome from '$lib/client/components/FontAwesome';
let waitForFontAwesomeToLoad: FontAwesome["waitForFontAwesomeToLoad"];
</script>
<FontAwesome bind:waitForFontAwesomeToLoad />
```
-->
<script context="module" lang="ts">
/**
* Returns `true` if the code may contain FontAwesome icons, and we should
* wait for the fonts to load before rendering.
*/
export function mayContainFontAwesome(code: string) {
// taken from https://github.com/mermaid-js/mermaid/blob/7043892e871d0c413ec63dc1570a8ef738d15568/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js#L63
// Not ideal, since we're looking at unparsed code.
const regex = /fa[blrs]?:fa-[\w-]+/g;
return regex.test(code);
}
</script>
<script lang="ts">
// Vite will automatically take care of adding this to our `<head>`
let lazyLoadFontAwesomeCSS = import('./FontAwesomeCSS.svelte');
let fontsLoaded = (async () => {
await lazyLoadFontAwesomeCSS;
// Once the stylesheet has been parsed, the fonts will be in document.fonts
// TODO: Maybe we should scan the CSS style sheet only, and only load FontAwesome fonts
await Promise.allSettled(Array.from(document.fonts, (font) => font.load()));
})();
/**
* Wait for FontAwesome to load.
*
* @returns A promise that resolves when FontAwesome is
* loaded, or there was an error that we ignore.
*/
export async function waitForFontAwesomeToLoad() {
return await fontsLoaded;
}
</script>
{#await lazyLoadFontAwesomeCSS then FontAwesomeCSS}
<FontAwesomeCSS.default />
{/await}
+14
View File
@@ -0,0 +1,14 @@
<!--
@component
Imports the FontAwesome CSS file.
Needed, since the `@sveltejs/adapter-node` plugin doesn't seem to support
lazy-importing CSS, even though `@sveltejs/adapter-vercel` is fine with it!
However, making a `.svelte` file that imports the CSS file, and then
lazy-loading the `.svelte` file works.
-->
<script lang="ts">
import '@fortawesome/fontawesome-free/css/all.css';
</script>
+140 -125
View File
@@ -1,45 +1,65 @@
<script lang="ts">
import Card from '$lib/components/Card/Card.svelte';
import { inputStateStore, getStateString } from '$lib/util/state';
import {
addHistoryEntry,
historyModeStore,
clearHistoryData,
getPreviousState,
historyStore,
loaderHistoryStore,
restoreHistory
} from './history';
import type { HistoryEntry, HistoryType, State, Tab } from '$lib/types';
import { notify, prompt } from '$lib/util/notify';
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import { serializeState } from '$lib/util/serde';
import { inputState, replaceInputState } from '$lib/util/state.svelte';
import { logEvent } from '$lib/util/stats';
import dayjs from 'dayjs';
import dayjsRelativeTime from 'dayjs/plugin/relativeTime';
import type { HistoryEntry, HistoryType, State, Tab } from '$lib/types';
import { logEvent } from '$lib/util/stats';
import BookmarkIcon from '~icons/material-symbols/bookmark-outline-rounded';
import TrashAltIcon from '~icons/material-symbols/delete-outline-rounded';
import DownloadIcon from '~icons/material-symbols/download-rounded';
import SaveIcon from '~icons/material-symbols/save-outline-rounded';
import UndoIcon from '~icons/material-symbols/settings-backup-restore-rounded';
import UploadIcon from '~icons/material-symbols/upload-rounded';
import HistoryIcon from '~icons/mdi/clock-outline';
import GitAltIcon from '~icons/mdi/git';
import OpenInNewIcon from '~icons/material-symbols/open-in-new-rounded';
import { Button } from '../ui/button';
import { Separator } from '../ui/separator';
import {
addManualEntry,
clearActive,
historyState,
removeEntry,
restoreEntries,
setMode
} from './historyState.svelte';
dayjs.extend(dayjsRelativeTime);
const HISTORY_SAVE_INTERVAL = 60000;
const tabSelectHandler = (message: CustomEvent<Tab>) => {
historyModeStore.set(message.detail.id as HistoryType);
};
let tabs: Tab[] = [
{
id: 'manual',
title: 'Saved',
icon: 'far fa-bookmark'
},
{
id: 'auto',
title: 'Timeline',
icon: 'fas fa-history'
}
const baseTabs: Tab[] = [
{ id: 'manual', title: 'Saved', icon: BookmarkIcon },
{ id: 'auto', title: 'Timeline', icon: HistoryIcon }
];
const loaderTab: Tab = { id: 'loader', title: 'Revisions', icon: GitAltIcon };
const tabs = $derived(
historyState.loaderEntries.length > 0 ? [loaderTab, ...baseTabs] : baseTabs
);
// Surface revisions once when they first appear; the user can switch away after.
let revisionsShown = false;
$effect(() => {
if (historyState.loaderEntries.length > 0 && !revisionsShown) {
revisionsShown = true;
setMode('loader');
}
});
const emptyMessage = $derived(
historyState.mode === 'auto'
? 'No timeline snapshots yet.\nThe Timeline is saved automatically every minute.'
: 'No saved states yet.\nClick the Save button to bookmark the current diagram and restore it later.'
);
const tabSelectHandler = (tab: Tab) => {
setMode(tab.id as HistoryType);
};
const downloadHistory = () => {
const data = get(historyStore);
const data = historyState.entries;
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
@@ -47,148 +67,143 @@
a.download = `mermaid-history-${dayjs().format('YYYY-MM-DD-HHmmss')}.json`;
a.click();
URL.revokeObjectURL(url);
logEvent('history', {
action: 'download'
});
logEvent('history', { action: 'download' });
};
const uploadHistory = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json';
input.addEventListener('change', ({ target }: Event) => {
const file = (<HTMLInputElement>target).files[0];
input.addEventListener('change', async ({ target }: Event) => {
const file = (target as HTMLInputElement)?.files?.[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const data: HistoryEntry[] = JSON.parse(e.target.result as string);
restoreHistory(data);
};
reader.readAsText(file);
const data: HistoryEntry[] = JSON.parse(await file.text());
const { restored, invalid, duplicates } = restoreEntries(data);
notify(`${restored} restored, ${duplicates} duplicate, ${invalid} invalid.`);
});
input.click();
};
const saveHistory = (auto = false) => {
const currentState: string = getStateString();
const previousState: string = getPreviousState(auto);
if (previousState !== currentState) {
addHistoryEntry({
state: $inputStateStore,
time: Date.now(),
type: auto ? 'auto' : 'manual'
});
} else if (!auto) {
const saveHistory = () => {
if (!addManualEntry($state.snapshot(inputState))) {
notify('State already saved.');
}
};
const clearHistory = (id?: string): void => {
if (!id && !prompt('Clear all saved items?')) {
return;
const clearAll = () => {
if (prompt('Clear all saved items?')) {
clearActive();
}
clearHistoryData(id);
};
const restoreHistoryItem = (state: State): void => {
inputStateStore.set({ ...state, updateDiagram: true });
replaceInputState({ ...state, updateDiagram: true });
};
const relativeTime = (time: number) => {
const t = new Date(time);
return `${new Date(t).toLocaleString()} (${dayjs(t).fromNow()})`;
};
// Absolute editor URL for an entry, so the link can be opened in a new tab or copied.
const entryUrl = (state: State): string =>
`${window.location.origin}${window.location.pathname}#${serializeState(state)}`;
onMount(() => {
historyModeStore.set('manual');
setInterval(() => {
saveHistory(true);
}, HISTORY_SAVE_INTERVAL);
});
loaderHistoryStore.subscribe((entries) => {
if (entries.length > 0 && tabs.length === 2) {
tabs = [
{
id: 'loader',
title: 'Revisions',
icon: 'fab fa-git-alt'
},
...tabs
];
historyModeStore.set('loader');
}
});
let isOpen = false;
// Serialize each entry's URL once per change rather than per row on every render.
const entriesWithUrl = $derived(
historyState.entries.map((entry) => ({ ...entry, openUrl: entryUrl(entry.state) }))
);
</script>
<Card on:select={tabSelectHandler} bind:isOpen {tabs} title="History">
<div slot="actions">
<button
<Card onselect={tabSelectHandler} isOpen isClosable={false} {tabs} activeTabID={historyState.mode}>
{#snippet actions()}
<div class="flex items-center gap-2">
<Button
size="icon"
variant="ghost"
id="uploadHistory"
class="btn btn-xs btn-secondary w-12"
on:click|stopPropagation={() => uploadHistory()}
title="Upload history"><i class="fa fa-upload" /></button>
{#if $historyStore.length > 0}
<button
onclick={uploadHistory}
title="Upload history"><UploadIcon /></Button>
{#if historyState.entries.length > 0}
<Button
id="downloadHistory"
class="btn btn-xs btn-secondary w-12"
on:click|stopPropagation={() => downloadHistory()}
title="Download history"><i class="fa fa-download" /></button>
size="icon"
variant="ghost"
onclick={downloadHistory}
title="Download history"><DownloadIcon /></Button>
{/if}
|
<button
<Separator orientation="vertical" />
<Button
id="saveHistory"
class="btn btn-xs btn-success w-12"
on:click|stopPropagation={() => saveHistory()}
title="Save current state"><i class="far fa-save" /></button>
{#if $historyModeStore !== 'loader'}
<button
size="icon"
variant="ghost"
onclick={saveHistory}
title="Save current state"><SaveIcon /></Button>
{#if historyState.mode !== 'loader'}
<Button
id="clearHistory"
class="btn btn-xs btn-error w-12"
on:click|stopPropagation={() => clearHistory()}
title="Delete all saved states"><i class="fas fa-trash-alt" /></button>
size="icon"
variant="ghost"
class="hover:text-destructive"
onclick={clearAll}
title="Delete all saved states"><TrashAltIcon /></Button>
{/if}
</div>
<ul class="p-2 space-y-2 overflow-auto h-56" id="historyList">
{#if $historyStore.length > 0}
{#each $historyStore as { id, state, time, name, url, type }}
<li class="rounded p-2 shadow flex-col">
<div class="flex">
<div class="flex-1">
<div class="flex flex-col text-base-content">
{/snippet}
<ul class="flex h-full min-w-fit flex-col gap-2 overflow-auto p-2" id="historyList">
{#if entriesWithUrl.length > 0}
{#each entriesWithUrl as { id, state, time, name, url, type, openUrl } (id)}
<li class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<div class="flex flex-col">
{#if url}
<a
href={url}
target="_blank"
title="Open revision in new tab"
class="hover:underline text-blue-500">{name}</a>
class="text-blue-500 hover:underline">{name}</a>
{:else}
<span>{name}</span>
<span class="whitespace-nowrap">{name}</span>
{/if}
<span class="text-gray-400 text-sm">{relativeTime(time)}</span>
<span class="text-xs whitespace-nowrap text-primary-foreground/30">
{new Date(time).toLocaleString()}
</span>
</div>
</div>
<div class="flex gap-2 content-center">
<button class="btn btn-success" on:click={() => restoreHistoryItem(state)}
><i class="fas fa-undo mr-1" />Restore</button>
<div class="flex items-center gap-2">
<span class="text-sm whitespace-nowrap text-primary-foreground/50">
{dayjs(time).fromNow()}
</span>
<Button
href={openUrl}
target="_blank"
rel="noopener"
size="icon"
variant="ghost"
title="Open in new tab">
<OpenInNewIcon />
</Button>
<Button
size="icon"
variant="ghost"
title="Restore this version"
onclick={() => restoreHistoryItem(state)}>
<UndoIcon />
</Button>
{#if type !== 'loader'}
<button class="btn btn-error" on:click={() => clearHistory(id)}
><i class="fas fa-trash-alt mr-1" />Delete</button>
<Button
size="icon"
variant="ghost"
class="hover:text-destructive"
title="Delete this version"
onclick={() => removeEntry(id)}>
<TrashAltIcon />
</Button>
{/if}
</div>
</div>
<Separator />
</li>
{/each}
{:else}
<div class="m-2">
No items in History<br />
Click the Save button to save current state and restore it later.<br />
Timeline will automatically be saved every minute.
</div>
<div class="m-2 text-center whitespace-pre-line">{emptyMessage}</div>
{/if}
</ul>
</Card>
-127
View File
@@ -1,127 +0,0 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import type { HistoryEntry } from '$lib/types';
import { describe, it, expect } from 'vitest';
import {
addHistoryEntry,
injectHistoryIDs,
clearHistoryData,
historyModeStore,
historyStore
} from './history';
import { defaultState } from '$lib/util/state';
import { get } from 'svelte/store';
describe('history', () => {
it('should handle saving individual history entry', () => {
expect(window.localStorage.getItem('manualHistoryStore')).toBe('[]');
expect(window.localStorage.getItem('autoHistoryStore')).toBe('[]');
addHistoryEntry({
state: defaultState,
time: 12_345,
type: 'manual'
});
const [manualEntry] = JSON.parse(
window.localStorage.getItem('manualHistoryStore') ?? '[]'
) as HistoryEntry[];
expect(manualEntry.time).toBe(12_345);
expect(manualEntry.type).toBe('manual');
expect(manualEntry.name).not.toBeNull();
expect(manualEntry.state).not.toBeNull();
addHistoryEntry({
state: defaultState,
time: 54_321,
type: 'auto'
});
const [autoEntry] = JSON.parse(
window.localStorage.getItem('autoHistoryStore') ?? '[]'
) as HistoryEntry[];
expect(autoEntry.time).toBe(54_321);
expect(autoEntry.type).toBe('auto');
expect(autoEntry.name).not.toBeNull();
expect(autoEntry.state).not.toBeNull();
historyModeStore.set('manual');
clearHistoryData();
historyModeStore.set('auto');
clearHistoryData();
expect(window.localStorage.getItem('manualHistoryStore')).toBe('[]');
expect(window.localStorage.getItem('autoHistoryStore')).toBe('[]');
});
it('should clear history entries', () => {
addHistoryEntry({
state: defaultState,
time: 12_345,
type: 'manual'
});
addHistoryEntry({
state: { ...defaultState, code: 'graph TD\\n A[Christmas] -->|Get money| B(Go shopping)' },
time: 123_456,
type: 'manual'
});
historyModeStore.set('manual');
const store: HistoryEntry[] = get(historyStore);
expect(store.length).toBe(2);
clearHistoryData(store[1].id);
expect(get(historyStore).length).toBe(1);
clearHistoryData();
expect(get(historyStore).length).toBe(0);
historyModeStore.set('auto');
addHistoryEntry({
state: defaultState,
time: 54_321,
type: 'auto'
});
addHistoryEntry({
state: { ...defaultState, code: 'graph TD\\n A[Christmas] -->|Get money| B(Go shopping)' },
time: 654_321,
type: 'auto'
});
expect(get(historyStore).length).toBe(2);
clearHistoryData();
expect(get(historyStore).length).toBe(0);
// Test calling when history is empty
clearHistoryData();
expect(get(historyStore).length).toBe(0);
});
});
describe('history migration', () => {
it('should inject history IDs as migration', () => {
window.localStorage.setItem(
'manualHistoryStore',
'[{"state":{"code":"graph TD\\n A[Halloween] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"manual","name":"hollow-art"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"helpful-ocean"}]'
);
window.localStorage.setItem(
'autoHistoryStore',
'[{"state":{"code":"graph TD\\n A[New Year] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":false},"time":0,"type":"auto","name":"barking-dog"},{"state":{"code":"graph TD\\n A[Christmas] -->|Get money| B(Go shopping)","mermaid":"{\\n \\"theme\\": \\"dark\\"\\n}","autoSync":true,"updateDiagram":true},"time":0,"type":"manual","name":"needy-mosquito"}]'
);
let manualHistoryStore = JSON.parse(
window.localStorage.getItem('manualHistoryStore') ?? '[]'
) as HistoryEntry[];
let autoHistoryStore = JSON.parse(
window.localStorage.getItem('autoHistoryStore') ?? '[]'
) as HistoryEntry[];
expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(false);
expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(false);
injectHistoryIDs();
manualHistoryStore = JSON.parse(
window.localStorage.getItem('manualHistoryStore') ?? '[]'
) as HistoryEntry[];
autoHistoryStore = JSON.parse(
window.localStorage.getItem('autoHistoryStore') ?? '[]'
) as HistoryEntry[];
expect(manualHistoryStore.every(({ id }) => id !== undefined)).toBe(true);
expect(autoHistoryStore.every(({ id }) => id !== undefined)).toBe(true);
});
});
-152
View File
@@ -1,152 +0,0 @@
import { derived, writable, get } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import { persist, localStorage } from '$lib/util/persist';
import { generateSlug } from 'random-word-slugs';
import type { HistoryEntry, HistoryType, Optional } from '$lib/types';
import { v4 as uuidV4 } from 'uuid';
import { logEvent } from '$lib/util/stats';
const MAX_AUTO_HISTORY_LENGTH = 30;
export const historyModeStore: Writable<HistoryType> = persist(
writable('manual'),
localStorage(),
'autoHistoryMode'
);
const autoHistoryStore: Writable<HistoryEntry[]> = persist(
writable([]),
localStorage(),
'autoHistoryStore'
);
const manualHistoryStore: Writable<HistoryEntry[]> = persist(
writable([]),
localStorage(),
'manualHistoryStore'
);
export const loaderHistoryStore: Writable<HistoryEntry[]> = writable([]);
export const historyStore: Readable<HistoryEntry[]> = derived(
[historyModeStore, autoHistoryStore, manualHistoryStore, loaderHistoryStore],
([historyMode, autoHistories, manualHistories, loadedHistories], set) => {
switch (historyMode) {
case 'auto': {
set(autoHistories);
break;
}
case 'manual': {
set(manualHistories);
break;
}
case 'loader': {
set(loadedHistories);
break;
}
default: {
set(autoHistories);
}
}
}
);
export const addHistoryEntry = (entryToAdd: Optional<HistoryEntry, 'id'>): void => {
const entry: HistoryEntry = {
...entryToAdd,
id: uuidV4()
};
if (entry.type === 'loader') {
loaderHistoryStore.update((entries) => [entry, ...entries]);
return;
}
if (!entry.name) {
entry.name = generateSlug(2);
}
if (entry.type === 'auto') {
autoHistoryStore.update((entries) => {
if (entries.length >= MAX_AUTO_HISTORY_LENGTH) {
entries = entries.slice(0, MAX_AUTO_HISTORY_LENGTH - 1);
}
return [entry, ...entries];
});
}
manualHistoryStore.update((entries) => [entry, ...entries]);
logEvent('history', { action: 'save' });
};
export const clearHistoryData = (idToClear?: string): void => {
(get(historyModeStore) === 'auto' ? autoHistoryStore : manualHistoryStore).update((entries) => {
if (get(historyModeStore) !== 'loader') {
entries = entries.filter(({ id }) => idToClear && id != idToClear);
logEvent('history', { action: 'clear', type: idToClear ? 'single' : 'all' });
}
return entries;
});
};
export const getPreviousState = (auto: boolean): string => {
const entries = get(auto ? autoHistoryStore : manualHistoryStore);
if (entries.length > 0) {
return JSON.stringify(entries[0].state);
}
return '';
};
export const restoreHistory = (data: HistoryEntry[]) => {
const entries = data.filter((element) => validateEntry(element));
const invalidEntryCount = data.length - entries.length;
if (invalidEntryCount > 0) {
console.error(`${invalidEntryCount} invalid history entries were removed.`);
console.error(data);
}
if (entries.length > 0) {
let entryCount = 0;
(entries[0].type === 'auto' ? autoHistoryStore : manualHistoryStore).update((existing) => {
const existingIDs = new Set(existing.map(({ id }) => id));
const newEntries = entries.filter(({ id }) => !existingIDs.has(id));
entryCount = newEntries.length;
const combined = [...existing, ...newEntries];
combined.sort((a, b) => b.time - a.time);
return combined;
});
alert(
`${entryCount} entries restored. ${invalidEntryCount} invalid, ${
entries.length - entryCount
} duplicates.`
);
logEvent('history', {
action: 'restore',
success: entryCount,
invalid: invalidEntryCount,
duplicates: entries.length - entryCount
});
} else {
alert('No valid entries found.');
}
};
const setIDs = (entries: HistoryEntry[]) => {
for (const entry of entries) {
if (!entry.id) {
entry.id = uuidV4();
}
}
return entries;
};
export const injectHistoryIDs = (): void => {
autoHistoryStore.update(setIDs);
manualHistoryStore.update(setIDs);
};
const validateEntry = (entry: HistoryEntry): boolean => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return entry.type && entry.state && entry.time && true;
};
@@ -0,0 +1,177 @@
import type { HistoryEntry, HistoryType, Optional, State } from '$lib/types';
import { persisted, readJSON, type Persisted } from '$lib/util/persist.svelte';
import { inputState } from '$lib/util/state.svelte';
import { logEvent } from '$lib/util/stats';
import { generateSlug } from 'random-word-slugs';
import { v4 as uuidV4 } from 'uuid';
const MAX_AUTO_HISTORY_LENGTH = 30;
const AUTO_SAVE_INTERVAL = 60_000;
const auto = persisted<HistoryEntry[]>('autoHistoryStore', []);
const manual = persisted<HistoryEntry[]>('manualHistoryStore', []);
const mode = persisted<HistoryType>('autoHistoryMode', 'manual');
let loader = $state<HistoryEntry[]>([]);
// Loader entries are in-memory, so a persisted 'loader' mode is empty after reload.
if (mode.value === 'loader') {
mode.value = 'manual';
}
// The persisted slot backing a mode; loader is in-memory and has no slot.
const slotFor = (m: HistoryType): Persisted<HistoryEntry[]> | null => {
switch (m) {
case 'auto': {
return auto;
}
case 'manual': {
return manual;
}
default: {
return null;
}
}
};
export const historyState = {
get entries(): HistoryEntry[] {
return slotFor(mode.value)?.value ?? loader;
},
get loaderEntries(): HistoryEntry[] {
return loader;
},
get mode(): HistoryType {
return mode.value;
}
};
export const setMode = (next: HistoryType): void => {
mode.value = next;
};
// Dedup key: only the fields that define the diagram, so volatile/view-only
// fields (renderCount, pan/zoom, …) don't count as a change.
export const stateKey = (state: State): string =>
JSON.stringify({ code: state.code, mermaid: state.mermaid });
const createEntry = (state: State, type: 'auto' | 'manual'): HistoryEntry => ({
id: uuidV4(),
name: generateSlug(2),
state,
time: Date.now(),
type
});
// Returns true if added, false if it duplicated the most recent entry.
const addEntry = (
slot: Persisted<HistoryEntry[]>,
state: State,
type: 'auto' | 'manual',
maxLength?: number
): boolean => {
const entries = slot.value;
if (entries.length > 0 && stateKey(entries[0].state) === stateKey(state)) {
return false;
}
const trimmed =
maxLength && entries.length >= maxLength ? entries.slice(0, maxLength - 1) : entries;
slot.value = [createEntry(state, type), ...trimmed];
logEvent('history', { action: 'save', type });
return true;
};
export const addManualEntry = (state: State): boolean => addEntry(manual, state, 'manual');
export const addAutoEntry = (state: State): boolean =>
addEntry(auto, state, 'auto', MAX_AUTO_HISTORY_LENGTH);
// Replaces the in-memory revisions (e.g. when a gist is loaded), assigning ids.
export const setLoaderEntries = (entries: Optional<HistoryEntry, 'id'>[]): void => {
loader = entries.map((entry) =>
entry.id ? (entry as HistoryEntry) : { ...entry, id: uuidV4() }
);
};
export const removeEntry = (id: string): void => {
const slot = slotFor(mode.value);
if (!slot) {
return;
}
slot.value = slot.value.filter((entry) => entry.id !== id);
logEvent('history', { action: 'clear', type: 'single' });
};
export const clearActive = (): void => {
const slot = slotFor(mode.value);
if (!slot) {
return;
}
slot.value = [];
logEvent('history', { action: 'clear', type: 'all' });
};
const validateEntry = (entry: HistoryEntry): boolean =>
Boolean(entry && entry.type && entry.state) && typeof entry.time === 'number';
export interface RestoreResult {
restored: number;
invalid: number;
duplicates: number;
}
// Routes each uploaded entry to the store matching its own type, skipping ids
// that already exist.
export const restoreEntries = (data: HistoryEntry[]): RestoreResult => {
const valid = data.filter((entry) => validateEntry(entry));
const invalid = data.length - valid.length;
let restored = 0;
const slots: [HistoryType, Persisted<HistoryEntry[]>][] = [
['auto', auto],
['manual', manual]
];
for (const [type, slot] of slots) {
const incoming = valid.filter((entry) => entry.type === type);
if (incoming.length === 0) {
continue;
}
const existingIDs = slot.value.map(({ id }) => id);
const fresh = incoming.filter(({ id }) => !existingIDs.includes(id));
restored += fresh.length;
slot.value = [...slot.value, ...fresh].sort((a, b) => b.time - a.time);
}
const duplicates = valid.length - restored;
logEvent('history', { action: 'restore', duplicates, invalid, success: restored });
return { restored, invalid, duplicates };
};
const setIDs = (entries: HistoryEntry[]): HistoryEntry[] =>
entries.map((entry) => (entry.id ? entry : { ...entry, id: uuidV4() }));
// One-time migration: re-reads localStorage so entries written by an older
// version get ids, then persists and updates the reactive state.
export const injectHistoryIDs = (): void => {
auto.value = setIDs(readJSON<HistoryEntry[]>('autoHistoryStore', []));
manual.value = setIDs(readJSON<HistoryEntry[]>('manualHistoryStore', []));
};
let autoSaveTimer: ReturnType<typeof setInterval> | undefined;
// Idempotent; returns the stop function for use as a lifecycle cleanup.
export const startAutoSave = (): (() => void) => {
if (autoSaveTimer === undefined) {
autoSaveTimer = setInterval(
() => addAutoEntry($state.snapshot(inputState)),
AUTO_SAVE_INTERVAL
);
}
return stopAutoSave;
};
export const stopAutoSave = (): void => {
if (autoSaveTimer !== undefined) {
clearInterval(autoSaveTimer);
autoSaveTimer = undefined;
}
};
@@ -0,0 +1,314 @@
import type { HistoryEntry } from '$lib/types';
import { defaultState, replaceInputState } from '$lib/util/state.svelte';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
addAutoEntry,
addManualEntry,
clearActive,
historyState,
injectHistoryIDs,
removeEntry,
restoreEntries,
setLoaderEntries,
setMode,
startAutoSave,
stateKey,
stopAutoSave
} from './historyState.svelte';
const codeState = (code: string) => ({ ...defaultState, code });
/** Read the entries currently shown for a given mode. */
const entriesFor = (mode: 'auto' | 'manual' | 'loader'): HistoryEntry[] => {
setMode(mode);
return historyState.entries;
};
beforeEach(() => {
// Reset every store through the public API so tests don't leak into each other.
setMode('manual');
clearActive();
setMode('auto');
clearActive();
setLoaderEntries([]);
setMode('manual');
});
describe('stateKey', () => {
it('ignores volatile and view-only fields, keying only on code + config', () => {
const a = {
...defaultState,
code: 'graph TD\n A-->B',
panZoom: true,
renderCount: 1,
updateDiagram: true
};
const b = {
...defaultState,
code: 'graph TD\n A-->B',
pan: { x: 5, y: 5 },
panZoom: false,
renderCount: 99,
updateDiagram: false
};
expect(stateKey(a)).toBe(stateKey(b));
});
it('differs when code differs', () => {
expect(stateKey(codeState('graph TD\n A-->B'))).not.toBe(
stateKey(codeState('graph TD\n A-->C'))
);
});
it('differs when config differs', () => {
const a = { ...defaultState, mermaid: '{"theme":"dark"}' };
const b = { ...defaultState, mermaid: '{"theme":"forest"}' };
expect(stateKey(a)).not.toBe(stateKey(b));
});
});
describe('addManualEntry', () => {
it('adds to the manual store only, never the auto store', () => {
expect(addManualEntry(codeState('graph TD\n A-->B'))).toBe(true);
expect(entriesFor('manual')).toHaveLength(1);
expect(entriesFor('auto')).toHaveLength(0);
});
it('returns false and does not add a duplicate of the latest entry', () => {
const state = codeState('graph TD\n A-->B');
expect(addManualEntry(state)).toBe(true);
expect(addManualEntry(state)).toBe(false);
expect(entriesFor('manual')).toHaveLength(1);
});
it('treats states differing only in volatile/view fields as duplicates', () => {
expect(addManualEntry({ ...defaultState, code: 'graph TD\n A-->B', renderCount: 1 })).toBe(
true
);
expect(
addManualEntry({
...defaultState,
code: 'graph TD\n A-->B',
panZoom: false,
renderCount: 2,
updateDiagram: true
})
).toBe(false);
expect(entriesFor('manual')).toHaveLength(1);
});
it('adds a new entry when the code changes', () => {
expect(addManualEntry(codeState('graph TD\n A-->B'))).toBe(true);
expect(addManualEntry(codeState('graph TD\n A-->C'))).toBe(true);
expect(entriesFor('manual')).toHaveLength(2);
});
it('generates an id and a name for each entry', () => {
addManualEntry(codeState('graph TD\n A-->B'));
const [entry] = entriesFor('manual');
expect(entry.id).toBeTruthy();
expect(entry.name).toBeTruthy();
expect(entry.type).toBe('manual');
});
});
describe('addAutoEntry', () => {
it('adds to the auto store only, never the manual store', () => {
expect(addAutoEntry(codeState('graph TD\n A-->B'))).toBe(true);
expect(entriesFor('auto')).toHaveLength(1);
expect(entriesFor('manual')).toHaveLength(0);
});
it('returns false and does not add a duplicate of the latest entry', () => {
const state = codeState('graph TD\n A-->B');
expect(addAutoEntry(state)).toBe(true);
expect(addAutoEntry(state)).toBe(false);
expect(entriesFor('auto')).toHaveLength(1);
});
it('caps the auto store at 30 entries, dropping the oldest', () => {
for (let i = 0; i < 35; i++) {
addAutoEntry(codeState(`graph TD\n A-->B${i}`));
}
const entries = entriesFor('auto');
expect(entries).toHaveLength(30);
expect(entries[0].state.code).toBe('graph TD\n A-->B34');
});
});
describe('historyState.entries', () => {
it('reflects the active mode', () => {
addManualEntry(codeState('manual-code'));
addAutoEntry(codeState('auto-code'));
setMode('manual');
expect(historyState.entries).toHaveLength(1);
expect(historyState.entries[0].state.code).toBe('manual-code');
setMode('auto');
expect(historyState.entries).toHaveLength(1);
expect(historyState.entries[0].state.code).toBe('auto-code');
setMode('loader');
expect(historyState.entries).toHaveLength(0);
});
});
describe('removeEntry / clearActive', () => {
it('removes a single entry from the active store by id', () => {
addManualEntry(codeState('graph TD\n A-->B'));
addManualEntry(codeState('graph TD\n A-->C'));
setMode('manual');
const target = historyState.entries[1].id;
removeEntry(target);
expect(historyState.entries).toHaveLength(1);
expect(historyState.entries.some((e) => e.id === target)).toBe(false);
});
it('clears all entries in the active store only', () => {
addManualEntry(codeState('graph TD\n A-->B'));
addAutoEntry(codeState('graph TD\n A-->C'));
setMode('manual');
clearActive();
expect(entriesFor('manual')).toHaveLength(0);
expect(entriesFor('auto')).toHaveLength(1);
});
it('does nothing in loader mode', () => {
setLoaderEntries([
{ name: 'rev', state: defaultState, time: 1, type: 'loader', url: 'http://x' }
]);
setMode('loader');
clearActive();
expect(historyState.entries).toHaveLength(1);
});
});
describe('setLoaderEntries', () => {
it('replaces the in-memory revisions and assigns ids', () => {
setLoaderEntries([
{ name: 'v1', state: defaultState, time: 1, type: 'loader', url: 'http://x/1' },
{ name: 'v2', state: defaultState, time: 2, type: 'loader', url: 'http://x/2' }
]);
setMode('loader');
expect(historyState.entries).toHaveLength(2);
expect(historyState.entries.every((e) => e.id)).toBe(true);
setLoaderEntries([
{ name: 'only', state: defaultState, time: 3, type: 'loader', url: 'http://x/3' }
]);
expect(historyState.entries).toHaveLength(1);
expect(historyState.entries[0].name).toBe('only');
});
});
describe('restoreEntries', () => {
it('routes each entry to the store matching its own type', () => {
const result = restoreEntries([
{ id: 'a1', name: 'a', state: defaultState, time: 10, type: 'auto' },
{ id: 'm1', name: 'm', state: defaultState, time: 20, type: 'manual' }
]);
expect(result.restored).toBe(2);
expect(entriesFor('auto').map((e) => e.id)).toEqual(['a1']);
expect(entriesFor('manual').map((e) => e.id)).toEqual(['m1']);
});
it('skips duplicates by id and reports them', () => {
restoreEntries([{ id: 'm1', name: 'm', state: defaultState, time: 20, type: 'manual' }]);
const result = restoreEntries([
{ id: 'm1', name: 'm', state: defaultState, time: 20, type: 'manual' },
{ id: 'm2', name: 'm2', state: defaultState, time: 30, type: 'manual' }
]);
expect(result.restored).toBe(1);
expect(result.duplicates).toBe(1);
expect(entriesFor('manual')).toHaveLength(2);
});
it('reports invalid entries and does not restore them', () => {
const result = restoreEntries([
{ id: 'm1', name: 'm', state: defaultState, time: 20, type: 'manual' },
{ foo: 'bar' } as unknown as HistoryEntry
]);
expect(result.restored).toBe(1);
expect(result.invalid).toBe(1);
});
it('sorts restored entries newest first', () => {
restoreEntries([
{ id: 'm1', name: 'old', state: defaultState, time: 10, type: 'manual' },
{ id: 'm2', name: 'new', state: defaultState, time: 30, type: 'manual' },
{ id: 'm3', name: 'mid', state: defaultState, time: 20, type: 'manual' }
]);
expect(entriesFor('manual').map((e) => e.time)).toEqual([30, 20, 10]);
});
it('restores entries whose time is 0 (epoch) instead of treating them as invalid', () => {
const result = restoreEntries([
{ id: 'm0', name: 'epoch', state: defaultState, time: 0, type: 'manual' }
]);
expect(result.restored).toBe(1);
expect(result.invalid).toBe(0);
expect(entriesFor('manual')).toHaveLength(1);
});
});
describe('injectHistoryIDs migration', () => {
it('adds ids to persisted entries that lack them', () => {
window.localStorage.setItem(
'manualHistoryStore',
'[{"state":{"code":"a"},"time":1,"type":"manual","name":"x"}]'
);
window.localStorage.setItem(
'autoHistoryStore',
'[{"state":{"code":"b"},"time":2,"type":"auto","name":"y"}]'
);
injectHistoryIDs();
const manual = JSON.parse(
window.localStorage.getItem('manualHistoryStore') ?? '[]'
) as HistoryEntry[];
const auto = JSON.parse(
window.localStorage.getItem('autoHistoryStore') ?? '[]'
) as HistoryEntry[];
expect(manual).toHaveLength(1);
expect(auto).toHaveLength(1);
expect(manual.every(({ id }) => id !== undefined)).toBe(true);
expect(auto.every(({ id }) => id !== undefined)).toBe(true);
});
});
describe('auto-save lifecycle', () => {
afterEach(() => {
stopAutoSave();
vi.useRealTimers();
});
it('records an auto entry on each interval from the current editor state', () => {
vi.useFakeTimers();
replaceInputState(codeState('graph TD\n auto-saved'));
startAutoSave();
vi.advanceTimersByTime(60_000);
const entries = entriesFor('auto');
expect(entries).toHaveLength(1);
expect(entries[0].state.code).toBe('graph TD\n auto-saved');
});
it('is idempotent: calling startAutoSave twice does not double-record', () => {
vi.useFakeTimers();
replaceInputState(codeState('graph TD\n once'));
startAutoSave();
startAutoSave();
vi.advanceTimersByTime(60_000);
expect(entriesFor('auto')).toHaveLength(1);
});
it('stops recording after stopAutoSave', () => {
vi.useFakeTimers();
replaceInputState(codeState('graph TD\n stoppable'));
startAutoSave();
vi.advanceTimersByTime(60_000);
stopAutoSave();
replaceInputState(codeState('graph TD\n after-stop'));
vi.advanceTimersByTime(60_000);
expect(entriesFor('auto')).toHaveLength(1);
});
});
+143
View File
@@ -0,0 +1,143 @@
<script lang="ts">
import McWrapper from '$/components/McWrapper.svelte';
import * as Popover from '$/components/ui/popover';
import { Switch } from '$/components/ui/switch';
import { env } from '$/util/env';
import { urls } from '$/util/state.svelte';
import { logMermaidChartClick } from '$/util/stats';
import { cn } from '$/utils';
import { mode, setMode } from 'mode-watcher';
import type { Component, Snippet } from 'svelte';
import MermaidTailIcon from '~icons/custom/mermaid-tail';
import AddIcon from '~icons/material-symbols/add-2-rounded';
import BookIcon from '~icons/material-symbols/book-2-outline-rounded';
import DuplicateIcon from '~icons/material-symbols/content-copy-outline-rounded';
import ContrastIcon from '~icons/material-symbols/contrast';
import PluginIcon from '~icons/material-symbols/electrical-services-rounded';
import MenuIcon from '~icons/material-symbols/menu-rounded';
import CommunityIcon from '~icons/material-symbols/person-play-outline-rounded';
import PlaygroundIcon from '~icons/material-symbols/shape-line-outline';
import MermaidChartIcon from './MermaidChartIcon.svelte';
interface MenuItem {
label: string;
icon: Component;
href: string;
class?: string;
onclick?: () => void;
sharesData?: boolean;
checkDiagramType?: boolean;
isSectionEnd?: boolean;
renderer: Snippet<[Omit<MenuItem, 'renderer'>]>;
}
const menuItems: MenuItem[] = $derived([
{ label: 'New', icon: AddIcon, href: urls.current.new, renderer: menuItem },
{ label: 'Duplicate', icon: DuplicateIcon, href: window.location.href, renderer: menuItem },
{
href: urls.current.mermaidChart({ medium: 'main_menu' }).playground,
icon: PlaygroundIcon,
isSectionEnd: true,
label: 'Edit in Playground',
onclick: () => logMermaidChartClick('editInPlayground'),
renderer: mcMenuItem
},
{
label: 'Mermaid.js',
icon: MermaidTailIcon,
href: env.docsUrl,
renderer: menuItem
},
{
label: 'Documentation',
icon: BookIcon,
href: `${env.docsUrl}/intro/`,
renderer: menuItem
},
{
label: 'Community',
icon: CommunityIcon,
href: 'https://discord.gg/sKeNQX4Wtj',
renderer: menuItem
},
{
checkDiagramType: false,
href: urls.current.mermaidChart({ medium: 'main_menu' }).plugins,
icon: PluginIcon,
label: 'Plugins',
onclick: () => logMermaidChartClick('plugins'),
renderer: mcMenuItem,
sharesData: false
},
{
href: '#',
icon: ContrastIcon,
isSectionEnd: true,
label: 'Dark Mode',
renderer: darkModeMenuItem
},
{
checkDiagramType: false,
class: 'text-accent border-b-0',
href: urls.current.mermaidChart({ medium: 'main_menu' }).home,
icon: MermaidChartIcon,
label: 'Mermaid',
onclick: () => logMermaidChartClick('mermaidHome'),
renderer: mcMenuItem,
sharesData: false
}
]);
</script>
{#snippet menuItem(options: Omit<MenuItem, 'renderer'>)}
<a
href={options.href}
target="_blank"
onclick={options.onclick}
class={cn(
'flex items-center justify-start gap-2 border-b-2 p-2 px-3 hover:bg-muted',
options.isSectionEnd && 'border-border-dark',
options.class
)}>
<options.icon class="size-5" />
{options.label}
</a>
{/snippet}
{#snippet mcMenuItem(item: Omit<MenuItem, 'renderer'>)}
<McWrapper
side="right"
labelPrefix={item.sharesData === false ? 'Opens a new tab in' : undefined}
sharesData={item.sharesData}
shouldCheckDiagramType={item.checkDiagramType}>
{@render menuItem(item)}
</McWrapper>
{/snippet}
{#snippet darkModeMenuItem(options: Omit<MenuItem, 'renderer'>)}
<div
class={cn(
'flex cursor-pointer items-center justify-between border-b-2 px-3 py-2 hover:bg-muted',
options.isSectionEnd && 'border-border-dark',
options.class
)}>
<span class="flex items-center gap-2">
<ContrastIcon />
Dark Mode
</span>
<Switch
checked={mode.current === 'dark'}
onCheckedChange={(dark) => setMode(dark ? 'dark' : 'light')} />
</div>
{/snippet}
<Popover.Root>
<Popover.Trigger class="shrink-0">
<MenuIcon class="size-6" />
</Popover.Trigger>
<Popover.Content align="start" class="flex flex-col overflow-hidden border-2 p-0" sideOffset={16}>
{#each menuItems as { renderer, ...item } (item.label)}
{@render renderer(item)}
{/each}
</Popover.Content>
</Popover.Root>
+24
View File
@@ -0,0 +1,24 @@
<script lang="ts">
import { env } from '$/util/env';
import { isOnMermaidAI } from '$/util/migration/domainMigration';
import type { ComponentProps } from 'svelte';
import ExternalLinkWrapper from './ExternalLinkWrapper.svelte';
let {
children,
...props
}: Omit<
ComponentProps<typeof ExternalLinkWrapper>,
'isVisible' | 'domain' | 'showPopup'
> = $props();
const mermaidChartDomain = 'mermaid.ai';
</script>
<ExternalLinkWrapper
{...props}
domain={mermaidChartDomain}
isVisible={env.isEnabledMermaidChartLinks}
showPopup={!isOnMermaidAI()}>
{@render children()}
</ExternalLinkWrapper>
@@ -0,0 +1,8 @@
<script lang="ts">
import { asset } from '$app/paths';
import type { ClassValue } from 'svelte/elements';
let { class: className }: { class?: ClassValue } = $props();
</script>
<img class={['size-4', className]} src={asset('/mermaidchart-logo.svg')} alt="Mermaid Chart" />
+100
View File
@@ -0,0 +1,100 @@
<script lang="ts">
import type { EditorProps } from '$/types';
import { validatedState } from '$/util/state.svelte';
import { json, jsonLanguage } from '@codemirror/lang-json';
import { markdown } from '@codemirror/lang-markdown';
import { yamlFrontmatter } from '@codemirror/lang-yaml';
import { language } from '@codemirror/language';
import { Compartment, EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { vsCodeDark } from '@fsegurai/codemirror-theme-vscode-dark';
import { vsCodeLight } from '@fsegurai/codemirror-theme-vscode-light';
import { basicSetup } from 'codemirror';
import { mode } from 'mode-watcher';
import { onMount } from 'svelte';
let editorView: EditorView | undefined;
let editorContainer: HTMLDivElement;
// Deliberately not $state: the sync effect below both reads and writes it,
// so a reactive currentText would make every keystroke re-run the effect
// against the not-yet-revalidated state and revert the user's input.
let currentText = '';
const themeCompartment = new Compartment();
const languageCompartment = new Compartment();
const { onUpdate }: EditorProps = $props();
$effect(() => {
editorView?.dispatch({
effects: themeCompartment.reconfigure(mode.current === 'dark' ? vsCodeDark : vsCodeLight)
});
});
onMount(() => {
editorView = new EditorView({
state: EditorState.create({
doc: currentText,
extensions: [
basicSetup,
languageCompartment.of([]),
themeCompartment.of([]),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
const newText = update.state.doc.toString();
if (currentText === newText) {
return;
}
currentText = newText;
onUpdate(newText);
}
}),
EditorView.theme({
'&.cm-focused': {
outline: 'none'
},
'&.cm-editor': {
height: '100%'
},
'&.cm-scroller': {
overflow: 'auto'
}
})
]
}),
parent: editorContainer
});
return () => {
editorView?.destroy();
};
});
$effect(() => {
const { editorMode, code, mermaid } = validatedState.current;
const text = editorMode === 'code' ? code : mermaid;
if (currentText === text || !editorView) {
return;
}
currentText = text;
editorView.dispatch({
changes: {
from: 0,
to: editorView.state.doc.length,
insert: text
}
});
const stateLanguage = editorView.state.facet(language);
const isStateJson = stateLanguage === jsonLanguage;
const isCodeJson = editorMode === 'config';
if (stateLanguage && isStateJson === isCodeJson) {
return;
}
editorView.dispatch({
effects: languageCompartment.reconfigure(
isCodeJson ? json() : yamlFrontmatter({ content: markdown() })
)
});
});
</script>
<div bind:this={editorContainer} class="size-full"></div>
+81 -75
View File
@@ -1,96 +1,102 @@
<script context="module" lang="ts">
<script lang="ts" module>
import { logEvent, logMermaidChartClick } from '$lib/util/stats';
import { version } from 'mermaid/package.json';
import { analytics } from '$lib/util/stats';
void analytics?.track('version', {
void logEvent('version', {
mermaidVersion: version
});
</script>
<script lang="ts">
import Theme from './Theme.svelte';
import { resolve } from '$app/paths';
import MainMenu from '$/components/MainMenu.svelte';
import { Button } from '$/components/ui/button';
import { Separator } from '$/components/ui/separator';
import { dismissPromotion, getActivePromotion } from '$lib/util/promos/promo.svelte';
import { untrack, type ComponentProps, type Snippet } from 'svelte';
import MermaidIcon from '~icons/custom/mermaid';
import CloseIcon from '~icons/material-symbols/close-rounded';
import GithubIcon from '~icons/mdi/github';
import DropdownNavMenu from './DropdownNavMenu.svelte';
interface Link {
href: string;
title?: string;
icon?: string;
img?: string;
interface Props {
mobileToggle?: Snippet;
children: Snippet;
hidePromotion?: boolean;
}
const links: Link[] = [
let { children, mobileToggle, hidePromotion = false }: Props = $props();
type Links = ComponentProps<typeof DropdownNavMenu>['links'];
const githubLinks: Links = [
{ title: 'Mermaid JS', href: 'https://github.com/mermaid-js/mermaid' },
{
title: 'Documentation',
href: 'https://mermaid.js.org/intro/n00b-gettingStarted.html'
title: 'Mermaid Live Editor',
href: 'https://github.com/mermaid-js/mermaid-live-editor'
},
{
title: 'Tutorial',
href: 'https://mermaid.js.org/config/Tutorials.html'
},
{
title: 'Mermaid',
href: 'https://github.com/mermaid-js/mermaid'
},
{
title: 'CLI',
title: 'Mermaid CLI',
href: 'https://github.com/mermaid-js/mermaid-cli'
},
{
href: 'https://github.com/mermaid-js/mermaid-live-editor',
icon: 'fab fa-github fa-lg'
},
{
href: 'https://mermaidchart.com',
img: '/mermaidchart-logo.svg'
}
];
let activePromotion = $state(untrack(() => (hidePromotion ? undefined : getActivePromotion())));
const trackBannerClick = () => {
if (!activePromotion) {
return;
}
logEvent('bannerClick', {
promotion: activePromotion.id
});
logMermaidChartClick('banner');
};
</script>
<div class="navbar shadow-lg bg-primary p-0">
<div class="flex-1 px-2 mx-2">
<span class="text-lg font-bold">
<a href="/">Mermaid<span class="text-xs font-thin">v{version}</span> Live Editor</a>
</span>
{#if activePromotion}
<div class="top-bar z-10 flex h-fit w-full bg-primary">
<div
class="flex grow"
role="button"
tabindex="0"
onclick={trackBannerClick}
onkeypress={trackBannerClick}>
<activePromotion.component {closeBanner} />
</div>
<label for="menu-toggle" class="pointer-cursor lg:hidden block"
><svg
class="fill-current"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" /></svg
></label>
<input class="hidden" type="checkbox" id="menu-toggle" />
{#snippet closeBanner()}
<Button
title="Dismiss banner"
variant="ghost"
class="hover:bg-transparent hover:text-[#261A56]"
size="sm"
onclick={() => {
dismissPromotion(activePromotion?.id);
activePromotion = undefined;
}}>
<CloseIcon />
</Button>
{/snippet}
</div>
{/if}
<Theme />
<div class="hidden lg:flex lg:items-center lg:w-auto w-full" id="menu">
<ul class="lg:flex items-center justify-between text-base pt-4 lg:pt-0">
{#each links as { title, href, icon, img }}
<li>
<a class="btn btn-ghost" target="_blank" {href}>
{#if icon}
<i class={icon} />
{:else if img}
<img src={img} alt={title} />
{/if}
{#if title}
{title}
<nav class="z-50 flex p-4 sm:p-6">
<div class="flex flex-1 items-center gap-2">
<MainMenu />
<MermaidIcon class="size-6" />
<a href={resolve('/', {})} class="whitespace-nowrap text-accent">
{#if !mobileToggle}
Mermaid
{/if}
Live Editor
</a>
</li>
{/each}
</ul>
</div>
</div>
<style>
#menu-toggle:checked + #menu {
display: block;
}
.navbar {
z-index: 10000;
}
img {
width: 1.5rem;
height: 1.5rem;
}
</style>
<div
id="menu"
class="hidden flex-nowrap items-center justify-between gap-3 overflow-hidden md:flex">
<DropdownNavMenu icon={GithubIcon} links={githubLinks} />
<Separator orientation="vertical" />
{@render children()}
</div>
{@render mobileToggle?.()}
</nav>
+34
View File
@@ -0,0 +1,34 @@
<script lang="ts">
import FloatingToolbar from '$/components/FloatingToolbar.svelte';
import { Button } from '$/components/ui/button';
import { Separator } from '$/components/ui/separator';
import type { PanZoomState } from '$/util/panZoom';
import { urls } from '$/util/state.svelte';
import ExpandIcon from '~icons/material-symbols/open-in-full-rounded';
import ArrowsToCircleIcon from '~icons/material-symbols/screenshot-frame-2';
import MagnifyingGlassPlusIcon from '~icons/material-symbols/zoom-in';
import MagnifyingGlassMinusIcon from '~icons/material-symbols/zoom-out';
let { panZoomState }: { panZoomState: PanZoomState } = $props();
</script>
<FloatingToolbar>
<Button variant="ghost" size="icon" title="Reset view" onclick={() => panZoomState.reset()}>
<ArrowsToCircleIcon />
</Button>
<Separator orientation="vertical" />
<Button
variant="ghost"
size="icon"
class="hidden sm:block"
onclick={() => panZoomState.zoomOut()}>
<MagnifyingGlassMinusIcon />
</Button>
<Button variant="ghost" size="icon" class="hidden sm:block" onclick={() => panZoomState.zoomIn()}>
<MagnifyingGlassPlusIcon />
</Button>
<Separator orientation="vertical" class="hidden sm:block" />
<Button variant="ghost" size="icon" title="Full Screen" href={urls.current.view} target="_blank">
<ExpandIcon />
</Button>
</FloatingToolbar>
+90 -140
View File
@@ -1,160 +1,110 @@
<script lang="ts">
import { updateCode } from '$lib/util/state';
import Card from '$lib/components/Card/Card.svelte';
import Card from '$/components/Card/Card.svelte';
import { Button, buttonVariants } from '$/components/ui/button';
import * as Popover from '$/components/ui/popover';
import { getSampleDiagrams, type SampleExample } from '$/util/mermaid';
import { updateCode } from '$lib/util/state.svelte';
import { logEvent } from '$lib/util/stats';
import { cn } from '$lib/utils';
import ShapesIcon from '~icons/material-symbols/account-tree-outline-rounded';
import ChevronDownIcon from '~icons/material-symbols/keyboard-arrow-down-rounded';
const samples = {
Flow: `flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]`,
Sequence: `sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!`,
Class: `classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
const extras: Record<string, SampleExample[]> = {
ZenUML: [
{
title: 'Order Service',
isDefault: true,
code: `zenuml
title Order Service
@Actor Client #FFEBE6
@Boundary OrderController #0747A6
@EC2 <<BFF>> OrderService #E3FCEF
group BusinessService {
@Lambda PurchaseService
@AzureFunction InvoiceService
}
class Fish{
-int sizeInFeet
-canEat()
@Starter(Client)
// \`POST /orders\`
OrderController.post(payload) {
OrderService.create(payload) {
order = new Order(payload)
if(order != null) {
par {
PurchaseService.createPO(order)
InvoiceService.createInvoice(order)
}
class Zebra{
+bool is_wild
+run()
}`,
State: `stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`,
Gantt: `gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
another task : 24d`,
Pie: `pie title Pets adopted by volunteers
"Dogs" : 386
"Cats" : 85
"Rats" : 15`,
ER: `erDiagram
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
CUSTOMER ||--o{ ORDER : places
CUSTOMER ||--o{ INVOICE : "liable for"
DELIVERY-ADDRESS ||--o{ ORDER : receives
INVOICE ||--|{ ORDER : covers
ORDER ||--|{ ORDER-ITEM : includes
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
PRODUCT ||--o{ ORDER-ITEM : "ordered in"`,
'User Journey': `journey
title My working day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 3: Me`,
Git: `gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
commit`,
Mindmap: `mindmap
root((mindmap))
Origins
Long history
::icon(fa fa-book)
Popularisation
British popular psychology author Tony Buzan
Research
On effectivness<br/>and features
On Automatic creation
Uses
Creative techniques
Strategic planning
Argument mapping
Tools
Pen and paper
Mermaid`,
QuadrantChart: `quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]`
}
}
}
`
}
]
};
type SampleTypes = keyof typeof samples;
const loadSampleDiagram = (diagramType: SampleTypes): void => {
updateCode(samples[diagramType], {
updateDiagram: true,
resetPanZoom: true
const samples = { ...getSampleDiagrams(), ...extras };
const loadSampleDiagram = (diagramType: string, example: SampleExample): void => {
updateCode(example.code, {
resetPanZoom: true,
updateDiagram: true
});
logEvent('loadSampleDiagram', { diagramType });
logEvent('loadSampleDiagram', { diagramType, exampleTitle: example.title });
};
// Adding in this array will add an icon to the preset menu
const newDiagrams: SampleTypes[] = ['Mindmap', 'QuadrantChart'];
const diagramOrder: SampleTypes[] = [
'Sequence',
'Flow',
const mainDiagrams = [
'Flowchart',
'Class',
'Sequence',
'Entity Relationship',
'State',
'ER',
'Gantt',
'User Journey',
'Git',
'Pie',
'Mindmap',
'QuadrantChart'
'Mindmap'
];
const diagramOrder = [
...mainDiagrams,
...Object.keys(samples)
.filter((key) => !mainDiagrams.includes(key))
.sort()
];
</script>
<Card title="Sample Diagrams" isOpen={false}>
<div class="flex flex-wrap p-2 gap-2">
{#each diagramOrder as sample}
<button
class="btn btn-sm btn-primary w-28 normal-case flex-grow"
on:click={() => loadSampleDiagram(sample)}>
<Card title="Sample Diagrams" isOpen isStackable icon={{ component: ShapesIcon }}>
<div class="flex h-fit max-h-52 flex-wrap gap-2 overflow-y-auto p-2">
{#each diagramOrder as sample (sample)}
{@const examples = samples[sample]}
<div class="flex min-w-20 flex-grow">
<Button
size="sm"
class={cn('flex-grow normal-case', examples.length > 1 && 'rounded-r-none')}
onclick={() => loadSampleDiagram(sample, examples[0])}>
{sample}
{#if newDiagrams.includes(sample)}
<span class="ml-2 fa fa-heart" />
</Button>
{#if examples.length > 1}
<Popover.Root>
<Popover.Trigger
aria-label="Choose a {sample} example"
class={cn(
buttonVariants({ size: 'sm' }),
'rounded-l-none border-l border-primary-foreground/30 px-0.5 [&_svg]:size-5'
)}>
<ChevronDownIcon />
</Popover.Trigger>
<Popover.Content align="start" class="flex w-fit flex-col gap-1 p-1">
{#each examples as example (example.title)}
<Popover.Close
class={cn(
buttonVariants({ variant: 'ghost', size: 'sm' }),
'justify-start normal-case'
)}
onclick={() => loadSampleDiagram(sample, example)}>
{example.title}
</Popover.Close>
{/each}
</Popover.Content>
</Popover.Root>
{/if}
</button>
</div>
{/each}
</div>
</Card>
+66
View File
@@ -0,0 +1,66 @@
<script lang="ts">
import ExternalLinkWrapper from '$/components/ExternalLinkWrapper.svelte';
import * as Dialog from '$/components/ui/dialog';
import { env } from '$/util/env';
import { isOnMermaidLive } from '$/util/migration/domainMigration';
import ShieldIcon from '~icons/material-symbols/shield-lock-outline-rounded';
</script>
{#if env.privacyPolicyUrl}
<a href={env.privacyPolicyUrl} target="_blank">
<ShieldIcon />
</a>
{:else}
<Dialog.Root>
<Dialog.Trigger>
<ShieldIcon />
</Dialog.Trigger>
<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" />
Data security
</Dialog.Title>
</Dialog.Header>
{#if isOnMermaidLive()}
<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>
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>
that works offline as a
<a href="https://web.dev/explore/progressive-web-apps" target="_blank"
>Progressive Web App</a
>.
</p>
<p>
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>
{:else}
<p>No privacy policy has been configured for this deployment.</p>
<p>
If you are self-hosting the Mermaid Live Editor, set the
<code class="rounded bg-muted px-1.5 py-0.5 text-sm">MERMAID_PRIVACY_POLICY_URL</code>
environment variable at build time to link to your privacy policy, or set
<code class="rounded bg-muted px-1.5 py-0.5 text-sm">MERMAID_HIDE_PRIVACY_POLICY</code>
to <code class="rounded bg-muted px-1.5 py-0.5 text-sm">true</code> to hide this button.
</p>
{/if}
</Dialog.Content>
</Dialog.Root>
{/if}
+49
View File
@@ -0,0 +1,49 @@
<script lang="ts">
import { buttonVariants } from '$/components/ui/button';
import * as Dialog from '$/components/ui/dialog';
import { Separator } from '$/components/ui/separator';
import { env } from '$/util/env';
import { urls } from '$/util/state.svelte';
import { asset } from '$app/paths';
import ShareIcon from '~icons/material-symbols/share';
import CopyInput from './CopyInput.svelte';
import MermaidChartIcon from './MermaidChartIcon.svelte';
</script>
<Dialog.Root>
<Dialog.Trigger class={buttonVariants({ size: 'sm' })}>Share</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title class="flex items-center gap-2 text-xl">
<ShareIcon class="size-5" /> Shareable links
</Dialog.Title>
<Dialog.Description>Share your diagrams with others.</Dialog.Description>
</Dialog.Header>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<h2 class="flex items-center gap-2">
<img class="size-5" src={asset('/favicon.svg')} alt="Mermaid Live Editor" />
Mermaid Live Editor
</h2>
<CopyInput value={window.location.href} />
<Dialog.Description>
The content of the diagrams you create never leaves your browser.
</Dialog.Description>
</div>
{#if env.isEnabledMermaidChartLinks}
<Separator />
<div class="flex flex-col gap-2">
<h2 class="flex items-center gap-2">
<MermaidChartIcon class="size-5" />
Mermaid Chart Playground
</h2>
<CopyInput value={urls.current.mermaidChart({ medium: 'share' }).playground} />
<Dialog.Description>
Opens the Mermaid Chart Playground with Mermaid AI, Visual Editor, and more.
</Dialog.Description>
</div>
{/if}
</div>
</Dialog.Content>
</Dialog.Root>
@@ -0,0 +1,27 @@
<script lang="ts">
import FloatingToolbar from '$/components/FloatingToolbar.svelte';
import { Toggle } from '$/components/ui/toggle';
import { defaultState, inputState, updateCodeStore } from '$/util/state.svelte';
import RoughIcon from '~icons/material-symbols/draw-outline-rounded';
import BackgroundIcon from '~icons/material-symbols/grid-4x4-rounded';
if (inputState.grid === undefined) {
// Handle cases where old states were saved without grid option
updateCodeStore({ grid: defaultState.grid });
}
</script>
<FloatingToolbar>
<Toggle
bind:pressed={() => inputState.rough, (rough) => updateCodeStore({ rough })}
size="sm"
title="Hand-Drawn">
<RoughIcon />
</Toggle>
<Toggle
bind:pressed={() => inputState.grid ?? defaultState.grid, (grid) => updateCodeStore({ grid })}
size="sm"
title="Background Grid">
<BackgroundIcon />
</Toggle>
</FloatingToolbar>
-64
View File
@@ -1,64 +0,0 @@
<script lang="ts">
import { setTheme, themeStore } from '$lib/util/theme';
const themes = [
'🌝 light',
'🌚 dark',
'🧁 cupcake',
'🐝 bumblebee',
'✳️ emerald',
'🏢 corporate',
'🌃 synthwave',
'👴 retro',
'🤖 cyberpunk',
'🌸 valentine',
'🎃 halloween',
'🌷 garden',
'🌲 forest',
'🐟 aqua',
'👓 lofi',
'🖍 pastel',
'🧚‍♀️ fantasy',
'📝 wireframe',
'🏴 black',
'💎 luxury',
'🧛‍♂️ dracula'
];
</script>
<div class="hidden lg:block dropdown">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div tabindex="0" class="btn btn-ghost">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="inline-block w-6 h-6 stroke-current md:mr-2"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" /></svg>
<span class="hidden md:inline">Theme</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1792 1792"
class="inline-block w-4 h-4 ml-1 fill-current"
><path
d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z" /></svg>
</div>
<div
class="mt-14 overflow-y-auto shadow-2xl top-px dropdown-content h-96 w-56 bg-base-200 text-base-content">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul tabindex="0" class="p-4 menu compact">
{#each themes as theme}
<li class:bordered={$themeStore.theme !== undefined && theme.includes($themeStore.theme)}>
<span
class="btn btn-ghost justify-start"
on:click={() => setTheme(theme)}
on:keypress={() => setTheme(theme)}>{theme}</span>
</li>
{/each}
</ul>
</div>
</div>
+39
View File
@@ -0,0 +1,39 @@
<script lang="ts">
import { mode } from 'mode-watcher';
import { cubicInOut } from 'svelte/easing';
import { type TransitionConfig } from 'svelte/transition';
import MoonIcon from '~icons/material-symbols/dark-mode-outline-rounded';
import SunIcon from '~icons/material-symbols/light-mode-outline-rounded';
const spin = (
node: Element,
{ duration = 400, easing = cubicInOut, clockWise = true } = {}
): TransitionConfig => {
const style = getComputedStyle(node);
const opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform;
return {
duration,
easing,
css: (t, u) => `
transform: ${transform} rotate(${u * 90 * (clockWise ? 1 : -1)}deg);
opacity: ${opacity * t}
`
};
};
</script>
<div class="inline-grid">
{#key mode.current}
<div
in:spin={{ clockWise: true }}
out:spin={{ clockWise: false }}
class="col-start-1 row-start-1">
{#if mode.current === 'dark'}
<MoonIcon />
{:else}
<SunIcon />
{/if}
</div>
{/key}
</div>
@@ -0,0 +1,31 @@
<script lang="ts">
import FloatingToolbar from '$/components/FloatingToolbar.svelte';
import Privacy from '$/components/Privacy.svelte';
import { Button } from '$/components/ui/button';
import { Separator } from '$/components/ui/separator';
import { TID } from '$/constants';
import { env } from '$/util/env';
import { version } from 'mermaid/package.json';
import { mode, setMode } from 'mode-watcher';
import ThemeIcon from './ThemeIcon.svelte';
</script>
<FloatingToolbar>
<span class="text-sm font-semibold opacity-60">v{version}</span>
{#if !env.hidePrivacyPolicy}
<Button variant="ghost" size="icon" title="Privacy & Security">
<Privacy />
</Button>
<Separator orientation="vertical" />
{/if}
<Button
variant="ghost"
size="icon"
data-testid={TID.themeToggleButton}
title="Switch to {mode.current === 'dark' ? 'light' : 'dark'} theme"
class="[&_svg]:size-5"
onclick={() => setMode(mode.current === 'dark' ? 'light' : 'dark')}>
<ThemeIcon />
</Button>
</FloatingToolbar>
+113 -105
View File
@@ -1,164 +1,172 @@
<script lang="ts">
import { inputStateStore, stateStore, updateCodeStore } from '$lib/util/state';
import { onMount } from 'svelte';
import panzoom from 'svg-pan-zoom';
import type { State, ValidatedState } from '$lib/types';
import { logEvent } from '$lib/util/stats';
import { cmdKey } from '$lib/util/util';
import { render as renderDiagram } from '$lib/util/mermaid';
import type { State, ValidatedState } from '$/types';
import { recordRenderTime, shouldRefreshView } from '$/util/autoSync';
import { render as renderDiagram } from '$/util/mermaid';
import { PanZoomState } from '$/util/panZoom';
import { updateCodeStore, validatedState } from '$/util/state.svelte';
import { saveStatistics } from '$/util/stats';
import FontAwesome, { mayContainFontAwesome } from '$lib/components/FontAwesome.svelte';
import uniqueID from 'lodash-es/uniqueId';
import type { MermaidConfig } from 'mermaid';
import { mode } from 'mode-watcher';
import { onMount } from 'svelte';
import { Svg2Roughjs } from 'svg2roughjs';
let {
panZoomState = new PanZoomState(),
shouldShowGrid = true
}: { panZoomState?: PanZoomState; shouldShowGrid?: boolean } = $props();
let code = '';
let config = '';
let container: HTMLDivElement;
let view: HTMLDivElement;
let error = false;
let outOfSync = false;
let hide = false;
let container: HTMLDivElement | undefined = $state();
let rough: boolean;
let view: HTMLDivElement | undefined = $state();
let error = $state(false);
let panZoom = true;
let manualUpdate = true;
let panZoomEnabled = $stateStore.panZoom;
let pzoom: typeof panzoom | undefined;
let waitForFontAwesomeToLoad: FontAwesome['waitForFontAwesomeToLoad'] | undefined = $state();
const handlePanZoomChange = () => {
if (!pzoom) {
return;
}
const pan = pzoom.getPan();
const zoom = pzoom.getZoom();
// Set up panZoom state observer to update the store when pan/zoom changes
const setupPanZoomObserver = () => {
panZoomState.onPanZoomChange = (pan, zoom) => {
updateCodeStore({ pan, zoom });
logEvent('panZoom');
};
};
const handlePanZoom = (state: State) => {
if (!state.panZoom) {
return;
const handlePanZoom = (state: State, graphDiv: SVGSVGElement) => {
try {
panZoomState.updateElement(graphDiv, state);
} catch (error) {
console.error('PanZoom error:', error);
}
hide = true;
pzoom?.destroy();
pzoom = undefined;
void Promise.resolve().then(() => {
const graphDiv = document.getElementById('graph-div');
if (!graphDiv) {
return;
}
pzoom = panzoom(graphDiv, {
onPan: handlePanZoomChange,
onZoom: handlePanZoomChange,
controlIconsEnabled: true,
fit: true,
center: true
});
const { pan, zoom } = state;
if (pan !== undefined && zoom !== undefined && Number.isFinite(zoom)) {
pzoom.zoom(zoom);
pzoom.pan(pan);
}
hide = false;
});
};
const handleStateChange = async (state: ValidatedState) => {
const startTime = Date.now();
if (state.error !== undefined) {
error = true;
return;
}
error = false;
let diagramType: string | undefined;
try {
if (container && state && (state.updateDiagram || state.autoSync)) {
if (!state.autoSync) {
$inputStateStore.updateDiagram = false;
}
outOfSync = false;
if (container) {
manualUpdate = true;
// Do not render if there is no change in Code/Config/PanZoom
if (code === state.code && config === state.mermaid && panZoomEnabled === state.panZoom) {
if (
code === state.code &&
config === state.mermaid &&
rough === state.rough &&
panZoom === state.panZoom
) {
return;
}
if (!shouldRefreshView()) {
return;
}
code = state.code;
config = state.mermaid;
panZoomEnabled = state.panZoom;
const scroll = view.parentElement!.scrollTop;
delete container.dataset.processed;
const { svg, bindFunctions } = await renderDiagram(
Object.assign({}, JSON.parse(state.mermaid)) as MermaidConfig,
code,
'graph-div'
);
rough = state.rough;
panZoom = state.panZoom ?? true;
if (mayContainFontAwesome(code)) {
await waitForFontAwesomeToLoad?.();
}
const scroll = view?.parentElement?.scrollTop;
delete container.dataset.processed;
const viewID = uniqueID('graph-');
const {
svg,
bindFunctions,
diagramType: detectedDiagramType
} = await renderDiagram(JSON.parse(state.mermaid) as MermaidConfig, code, viewID);
diagramType = detectedDiagramType;
if (svg.length > 0) {
handlePanZoom(state);
// eslint-disable-next-line svelte/no-dom-manipulating
container.innerHTML = svg;
console.log({ svg });
const graphDiv = document.getElementById('graph-div');
let graphDiv = document.querySelector<SVGSVGElement>(`#${viewID}`);
if (!graphDiv) {
throw new Error('graph-div not found');
}
if (state.rough) {
const svg2roughjs = new Svg2Roughjs('#container');
svg2roughjs.svg = graphDiv;
await svg2roughjs.sketch();
graphDiv.remove();
const sketch = document.querySelector<SVGSVGElement>('#container > svg');
if (!sketch) {
throw new Error('sketch not found');
}
const height = sketch.getAttribute('height');
const width = sketch.getAttribute('width');
sketch.setAttribute('id', 'graph-div');
sketch.setAttribute('height', '100%');
sketch.setAttribute('width', '100%');
sketch.setAttribute('viewBox', `0 0 ${width} ${height}`);
sketch.style.maxWidth = '100%';
graphDiv = sketch;
} else {
graphDiv.setAttribute('height', '100%');
graphDiv.style.maxWidth = '100%';
if (bindFunctions) {
bindFunctions(graphDiv);
}
}
view.parentElement!.scrollTop = scroll;
if (state.panZoom) {
handlePanZoom(state, graphDiv);
}
}
if (view?.parentElement && scroll) {
view.parentElement.scrollTop = scroll;
}
error = false;
} else if (manualUpdate) {
manualUpdate = false;
} else if (code !== state.code || config !== state.mermaid) {
outOfSync = true;
}
} catch (e) {
console.error('view fail', e);
} catch (error_) {
console.error('view fail', error_);
error = true;
}
const renderTime = Date.now() - startTime;
saveStatistics({ code, diagramType, isRough: state.rough, renderTime });
recordRenderTime(renderTime, () => {
updateCodeStore({ updateDiagram: true });
});
};
onMount(() => {
stateStore.subscribe((state) => {
void handleStateChange(state);
});
window.addEventListener('resize', () => {
if ($stateStore.panZoom && pzoom) {
pzoom.resize();
}
setupPanZoomObserver();
});
// Queue state changes to avoid race condition
let pendingStateChange = Promise.resolve();
$effect(() => {
const state = validatedState.current;
// eslint-disable-next-line @typescript-eslint/no-empty-function
pendingStateChange = pendingStateChange.then(() => handleStateChange(state).catch(() => {}));
});
</script>
{#if (error && $stateStore.error instanceof Error) || outOfSync}
<div
class="absolute w-full p-2 z-10 font-mono {error
? 'text-red-600'
: 'text-yellow-600'} bg-base-100 bg-opacity-80 text-left"
id="errorContainer">
{#if error}
{@html $stateStore.error?.toString().replace(/\n/g, '<br />')}
{:else}
Diagram out of sync. <br />
Press <i class="fas fa-sync" /> (Sync button) or <kbd>{cmdKey} + Enter</kbd> to sync.
{/if}
</div>
{/if}
<FontAwesome bind:waitForFontAwesomeToLoad />
<div id="view" bind:this={view} class="p-2 h-full" class:error class:outOfSync>
<div id="container" bind:this={container} class="h-full overflow-auto" class:hide />
<div
id="view"
bind:this={view}
class={['h-full w-full', shouldShowGrid && `grid-bg-${mode.current}`, error && 'opacity-50']}>
<div id="container" bind:this={container} class="h-full overflow-auto"></div>
</div>
<style>
#view {
flex: 1;
.grid-bg-light {
background-size: 30px 30px;
background-image: radial-gradient(circle, #e4e4e48c 2px, #0000 2px);
}
#container {
transition: visibility 0.3s;
}
.error,
.outOfSync {
opacity: 0.5;
}
.hide {
visibility: hidden;
.grid-bg-dark {
background-size: 30px 30px;
background-image: radial-gradient(circle, #46464646 2px, #0000 2px);
}
</style>
@@ -0,0 +1,101 @@
<script lang="ts">
import McWrapper from '$/components/McWrapper.svelte';
import MermaidChartIcon from '$/components/MermaidChartIcon.svelte';
import PrivacyPolicyLink from '$/components/migration/PrivacyPolicyLink.svelte';
import { Button } from '$/components/ui/button';
import * as Dialog from '$/components/ui/dialog';
import { dismissEditorChooser } from '$/util/migration/domainMigration';
import type { Component } from 'svelte';
import AtlassianIcon from '~icons/logos/atlassian';
import AmazonIcon from '~icons/logos/aws';
import GoogleIcon from '~icons/logos/google';
import MicrosoftIcon from '~icons/logos/microsoft';
import { createEditorChooserActions } from './editorChooserActions';
interface Props {
open: boolean;
}
let { open = $bindable() }: Props = $props();
// Tracks whether the current close was triggered by an explicit action
// (button click logs its own event) vs. implicit dismissal (ESC/click-outside).
let handled = false;
const actions = createEditorChooserActions(() => {
handled = true;
open = false;
});
const features = [
{ title: 'AI diagram generation', description: 'Describe what you need, AI builds it' },
{
title: 'Visual drag-and-drop editor',
description: 'Edit diagrams without writing code'
},
{
title: 'Unlimited diagram storage',
description: 'Save and organize all your diagrams'
},
{
title: 'Team collaboration',
description: 'Share, comment, and edit together in real-time'
}
];
const trustedLogos: { name: string; icon: Component }[] = [
{ name: 'Google', icon: GoogleIcon },
{ name: 'Microsoft', icon: MicrosoftIcon },
{ name: 'Atlassian', icon: AtlassianIcon },
{ name: 'Amazon', icon: AmazonIcon }
];
</script>
<Dialog.Root
bind:open
onOpenChange={(v) => {
if (v) return;
if (!handled) actions.log('dismissed');
handled = false;
dismissEditorChooser();
}}>
<Dialog.Content class="flex max-w-lg flex-col gap-3 bg-background p-8">
<Dialog.Header class="flex-col items-start gap-2 space-y-0 text-left sm:text-left">
<MermaidChartIcon class="size-10" />
<Dialog.Title class="pt-2 text-2xl font-bold">Try the full Mermaid experience</Dialog.Title>
<Dialog.Description class="text-xs font-light text-muted-foreground">
Free forever, with Plus features free for 7 days.
</Dialog.Description>
</Dialog.Header>
<ul class="mt-2 flex flex-col gap-3">
{#each features as feature (feature.title)}
<li class="flex items-center gap-3 rounded-lg border border-border p-3">
<span class="size-2 shrink-0 rounded-full bg-accent"></span>
<div class="flex flex-col gap-0.5">
<p class="text-xs">{feature.title}</p>
<p class="text-xs font-light text-muted-foreground">{feature.description}</p>
</div>
</li>
{/each}
</ul>
<div class="mt-2 flex items-center gap-3">
<McWrapper labelPrefix="Opens ">
<Button variant="accent" onclick={() => actions.startTrial()}>Start free trial</Button>
</McWrapper>
<Button variant="outline" onclick={() => actions.dismiss('stayOnLive')}>
Stay on mermaid.live
</Button>
</div>
<div class="mt-3 flex flex-col items-start gap-4">
<p class="text-xs">Trusted by 5M people and over 200k companies</p>
<div class="flex w-full items-center justify-between gap-2">
{#each trustedLogos as logo (logo.name)}
<logo.icon class="h-6 w-auto grayscale" aria-label={logo.name} />
{/each}
</div>
</div>
<PrivacyPolicyLink />
</Dialog.Content>
</Dialog.Root>
@@ -0,0 +1,10 @@
<script lang="ts"></script>
<div class="text-center">
<a
href="https://mermaid.ai/privacy-policy"
target="_blank"
class="text-sm text-foreground underline hover:text-accent">
mermaid.ai Privacy Policy
</a>
</div>
@@ -0,0 +1,38 @@
import { logEvent, logMermaidChartClick } from '$/util/stats';
import { getCheckoutUrl, getMermaidAiLiveUrl } from '$/util/util';
const utmMedium = 'editorSelection';
const utmCampaign = 'live_2026';
export interface EditorChooserActions {
log: (buttonClick: string) => void;
startTrial: (buttonClick?: string) => void;
dismiss: (buttonClick: string) => void;
openMermaidAiLive: (buttonClick: string) => void;
}
export const createEditorChooserActions = (close: () => void): EditorChooserActions => {
const log = (buttonClick: string) => {
logEvent('chooseEditor', { buttonClick });
};
const startTrial = (buttonClick = 'startTrial') => {
log(buttonClick);
logMermaidChartClick('editorPicker');
close();
window.open(getCheckoutUrl({ utmCampaign, utmMedium }), '_blank', 'noopener');
};
const dismiss = (buttonClick: string) => {
log(buttonClick);
close();
};
const openMermaidAiLive = (buttonClick: string) => {
log(buttonClick);
close();
window.open(getMermaidAiLiveUrl({ utmCampaign, utmMedium }), '_blank', 'noopener');
};
return { log, startTrial, dismiss, openMermaidAiLive };
};
@@ -0,0 +1,67 @@
<script lang="ts" module>
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { tv, type VariantProps } from 'tailwind-variants';
export const buttonVariants = tv({
base: 'focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-6 [&_svg]:shrink-0',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/80',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm',
outline:
'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm',
accent: 'bg-accent text-accent-foreground hover:bg-accent/80 shadow-sm',
ghost: 'hover:bg-primary hover:text-primary-foreground',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'size-8'
}
},
defaultVariants: {
variant: 'default',
size: 'default'
}
});
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
let {
class: className,
variant = 'default',
size = 'default',
ref = $bindable(null),
href,
type = 'button',
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a bind:this={ref} class={cn(buttonVariants({ variant, size }), className)} {href} {...restProps}>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
class={cn(buttonVariants({ variant, size }), className)}
{type}
{...restProps}>
{@render children?.()}
</button>
{/if}
+9
View File
@@ -0,0 +1,9 @@
export {
default as Button,
buttonVariants,
default as Root,
type ButtonProps,
type ButtonSize,
type ButtonVariant,
type ButtonProps as Props
} from './button.svelte';
@@ -0,0 +1,36 @@
<script lang="ts">
import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
import { Dialog as DialogPrimitive } from 'bits-ui';
import X from 'lucide-svelte/icons/x';
import type { Snippet } from 'svelte';
import * as Dialog from './index.js';
let {
ref = $bindable(null),
class: className,
portalProps,
children,
...restProps
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
portalProps?: DialogPrimitive.PortalProps;
children: Snippet;
} = $props();
</script>
<Dialog.Portal {...portalProps}>
<Dialog.Overlay />
<DialogPrimitive.Content
bind:ref
class={cn(
'fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...restProps}>
{@render children?.()}
<DialogPrimitive.Close
class="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-none disabled:pointer-events-none data-[state=open]:text-muted-foreground">
<X class="size-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>
@@ -0,0 +1,15 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
</script>
<DialogPrimitive.Description
bind:ref
class={cn('text-sm text-muted-foreground', className)}
{...restProps} />
@@ -0,0 +1,18 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...restProps}>
{@render children?.()}
</div>
@@ -0,0 +1,18 @@
<script lang="ts">
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
{...restProps}>
{@render children?.()}
</div>
@@ -0,0 +1,18 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
bind:ref
class={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
className
)}
{...restProps} />
@@ -0,0 +1,15 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
</script>
<DialogPrimitive.Title
bind:ref
class={cn('text-lg leading-none font-semibold tracking-tight', className)}
{...restProps} />
+36
View File
@@ -0,0 +1,36 @@
import { Dialog as DialogPrimitive } from 'bits-ui';
import Content from './dialog-content.svelte';
import Description from './dialog-description.svelte';
import Footer from './dialog-footer.svelte';
import Header from './dialog-header.svelte';
import Overlay from './dialog-overlay.svelte';
import Title from './dialog-title.svelte';
const Root: typeof DialogPrimitive.Root = DialogPrimitive.Root;
const Trigger: typeof DialogPrimitive.Trigger = DialogPrimitive.Trigger;
const Close: typeof DialogPrimitive.Close = DialogPrimitive.Close;
const Portal: typeof DialogPrimitive.Portal = DialogPrimitive.Portal;
export {
Close,
Content,
Description,
//
Root as Dialog,
Close as DialogClose,
Content as DialogContent,
Description as DialogDescription,
Footer as DialogFooter,
Header as DialogHeader,
Overlay as DialogOverlay,
Portal as DialogPortal,
Title as DialogTitle,
Trigger as DialogTrigger,
Footer,
Header,
Overlay,
Portal,
Root,
Title,
Trigger
};

Some files were not shown because too many files have changed in this diff Show More