Skip to content

Demo of ProseMirror WYSIWYG editor#8991

Draft
linonetwo wants to merge 116 commits into
TiddlyWiki:masterfrom
linonetwo:feat/prosemirror-wysiwyg-editor
Draft

Demo of ProseMirror WYSIWYG editor#8991
linonetwo wants to merge 116 commits into
TiddlyWiki:masterfrom
linonetwo:feat/prosemirror-wysiwyg-editor

Conversation

@linonetwo

@linonetwo linonetwo commented Mar 23, 2025

Copy link
Copy Markdown
Contributor

Demo: https://deploy-preview-8991--tiddlywiki-previews.netlify.app/

This is based on #8258 , since it is not merged, commits here will be long.

Demo to show it is hard to manage dependency without a bundler like https://tiddly-gittly.github.io/Modern.TiddlyDev/

Script to crawl all JS dependency: #8991 (comment)
But due to those .cjs file still require each other with names like require('prosemirror-state'), I have to keep its name prosemirror-state in tiddlywiki.files. Any solution for this? An idea is use regexp on tiddlywiki.files to auto replace require("xxx on prosemirror js files, to require("$:/plugins/tiddlywiki/prosemirror/lib/xxx.

And I don't quite understand engine.js edit-codemirror.js in the old codemirror plugin, so I make prosemirror a widget instead, is this OK?

@github-actions

Copy link
Copy Markdown

Confirmed: linonetwo has already signed the Contributor License Agreement (see contributing.md)

@linonetwo

Copy link
Copy Markdown
Contributor Author

Crawl all prosemirror as dependencies using this JS, and download https://cdn.jsdelivr.net/npm/prosemirror-view@latest/style/prosemirror.css and https://cdn.jsdelivr.net/npm/prosemirror-menu@latest/style/menu.css manually.

const fs = require('fs');
const path = require('path');

const dependencies = [
  'prosemirror-state',
  'prosemirror-view',
  'prosemirror-model',
  'prosemirror-schema-basic',
  'prosemirror-schema-list',
  'prosemirror-example-setup',
  'orderedmap',
  'prosemirror-transform',
  'prosemirror-keymap',
  'prosemirror-history',
  'prosemirror-commands',
  'prosemirror-dropcursor',
  'prosemirror-gapcursor',
  'prosemirror-menu',
  'prosemirror-inputrules',
  'w3c-keyname',
  'crelt',
  'rope-sequence',
];

async function getVersionAndDownload(dep) {
  try {
    const response = await fetch(`https://registry.npmjs.org/${dep}/latest`);
    const data = await response.json();
    const version = data.version;
    // const url = `https://cdn.jsdelivr.net/npm/${dep}@${version}/dist/index.cjs`;
    const url = `https://cdn.jsdelivr.net/npm/${dep}@${version}`;
    console.log(`Downloading ${dep}@${version} from ${url}`);
    const fileResponse = await fetch(url);
    const reader = fileResponse.body.getReader();
    const writer = fs.createWriteStream(path.resolve(__dirname, 'plugins/tiddlywiki/prosemirror/files', `${dep}.cjs`));
    const stream = new ReadableStream({
      async start(controller) {
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          controller.enqueue(value);
        }
        controller.close();
      }
    });
    const responseStream = stream.pipeTo(new WritableStream({
      write(chunk) {
        writer.write(chunk);
      },
      close() {
        writer.end();
      }
    }));
    await responseStream;
  } catch (error) {
    console.error(`Failed to download ${dep}:`, error);
  }
}

async function main() {
  for (const dep of dependencies) {
    await getVersionAndDownload(dep);
  }
  console.log('All files downloaded and renamed successfully.');
}

main();

place it on tiddlywiki repo's root as crawlAndRename.js then node crawlAndRename.js with node 22+

@linonetwo

linonetwo commented Mar 23, 2025

Copy link
Copy Markdown
Contributor Author

Where is the netlify preview? Seems it won't build preview site for draft PR.

@linonetwo linonetwo marked this pull request as ready for review March 23, 2025 14:47
@netlify

netlify Bot commented Mar 23, 2025

Copy link
Copy Markdown

Deploy Preview for tiddlywiki-previews ready!

Name Link
🔨 Latest commit 2a0bef7
🔍 Latest deploy log https://app.netlify.com/projects/tiddlywiki-previews/deploys/6a37f73916184e0008b12878
😎 Deploy Preview https://deploy-preview-8991--tiddlywiki-previews.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from 39d80b3 to de0c989 Compare March 23, 2025 14:53
@linonetwo linonetwo marked this pull request as draft March 23, 2025 17:19
@linonetwo linonetwo marked this pull request as ready for review March 25, 2025 16:03
@linonetwo linonetwo marked this pull request as draft March 25, 2025 17:47
@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from 2d88859 to 04acfb2 Compare May 10, 2025 12:55
@github-actions

github-actions Bot commented May 10, 2025

Copy link
Copy Markdown

📊 Build Size Comparison: empty.html

Branch Size
Base (master) 2528.4 KB
PR 2533.1 KB

Diff: ⬆️ Increase: +4.7 KB

@pmario

pmario commented May 14, 2025

Copy link
Copy Markdown
Member

@linonetwo -- Can you make this PR work with the latest changes in PR #8258 ?

@linonetwo linonetwo marked this pull request as ready for review May 14, 2025 14:25
@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from d467ecc to 033dbca Compare May 14, 2025 14:31
@linonetwo

Copy link
Copy Markdown
Contributor Author

I rebased this onto #8258 , hope didn't break anything.

@pmario

pmario commented May 14, 2025

Copy link
Copy Markdown
Member

I think, the new plugin needs to be included, for prosemirror to work here

@linonetwo

linonetwo commented May 15, 2025

Copy link
Copy Markdown
Contributor Author

Now it works, see https://deploy-preview-8991--tiddlywiki-previews.netlify.app/

This is just an demo showing #8258 works. Future routes:

  1. find a way to make imports easier, and integrate editor factory (optional, needs help, or I could try ask deepwiki)
  2. put it in another repo, so we could use https://github.com/tiddly-gittly/Modern.TiddlyDev to build it, also make it possible to use TS. My github copilot pro agent mode works better with TS.

and after this choise, there are more to improve

  1. allow register slash / menu as sub-plugin
  2. add build-in editor for macro, widget, macro definition, pragmas, based on [IDEA] Inline document for filter operator and action widget #8154 to show available parameters
  3. inline AI editing that Notion and Obsidian and Github copilot have

I could complete these features in a double-month, but according to current PR merging speed, it might takes several years if it becomes a core plugin. So I'd like to make it a 3rd party plugin in https://github.com/tiddly-gittly/

@linonetwo linonetwo marked this pull request as draft May 15, 2025 05:22
@pmario

pmario commented May 15, 2025

Copy link
Copy Markdown
Member

This is just an demo showing #8258 works.

I know. But it is a good test, to see, what can be achieved.


  1. put it in another repo, so we could use https://github.com/tiddly-gittly/Modern.TiddlyDev to build it, also make it possible to use TS. My github copilot pro agent mode works better with TS.

I did have a look at the Modern.TiddlyDev dependency list. For me that's very concerning.

tiddlywiki-plugin-dev has 25 dependencies which themself also have dependencies. So I would not be surprised if there would be 200 dependencies installed. Our users do not know any of them.

And I think none of them is really needed. TW can handle to compile plugins right from the core. TW itself is built that way. I use it every day.

As Jeremy pointed out. ProseMirror is already pretty heavy weight on its own. Adding additional additional dependencies maintenance nightmare and IMO looks a bit like over-engineering.

IMO Adding features like "slash menu" as plugin to TW is OK, but as a sub-plugin, it is way to strongly coupled. It has nothing to do with ProseMirror. It can support ProseMirror, but it should not be linked.

Just my personal thoughts.

@linonetwo

linonetwo commented Jul 12, 2025

Copy link
Copy Markdown
Contributor Author

@Jermolene When you merge the upstream ast core plugin PR, I'd like to move this WYSIWYG plugin to another repo, because I'm tired of writing JS when working on this, I want to use TS, so GitHub Copilot can auto-fix bugs more efficiently and the human-computer feedback loop will be faster.

But you said you hope the WYSIWYG editor to be a core plugin, so I'm in a dilemma.


Some afterthoughts:

I must have been spoiled by modern tools; I haven't written code by hand for quite some time..

And it is my fault to use only gpt4.1 on tiddlywiki, I should have use claude4 like I'm used during the work. That won't have problem writing JS. While it is a bit more expensive...

@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from 0d5cc89 to 1c87da1 Compare July 12, 2025 07:41
Comment thread plugins/tiddlywiki/prosemirror/ast/from-prosemirror.js Outdated
Comment thread plugins/tiddlywiki/prosemirror/setup/keymap.js Outdated
Comment thread plugins/tiddlywiki/prosemirror/setup/keymap.js Outdated
Comment thread plugins/tiddlywiki/prosemirror/setup/menu.js Outdated
@@ -177,25 +177,25 @@ SlashMenuUI.prototype.createMenuItem = function(element, state) {

SlashMenuUI.prototype.getIconForElement = function(element) {
// Simple text icons for now - can be enhanced with SVG later

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will they ever be SVG icons "later" -- At least add a TODO the comment, so anyone may find it later ;)

@linonetwo linonetwo Jul 13, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't decide how slash menu looks like, I may copy notion's style and add icons.

filter: data
});
}
return true; // Prevent the composition from being inserted

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the comments removed. If they are needed to better understand the code IMO they should stay.

@linonetwo

Copy link
Copy Markdown
Contributor Author

I'm putting more time on AI-writing tiddler recently. So effort on this will be liminted in the future. So I will try migrate to https://tiptap.dev/docs/editor/getting-started/install/vanilla-javascript , which is also prosemirror based, but will save a lot of my time.

@Jermolene

Copy link
Copy Markdown
Member

Thanks @linonetwo. I am still very keen to be able to offer a WYSIWYG editor. If it's not ready for v5.4.0 then it would make an excellent tentpole feature for v5.5.0.

@pmario

pmario commented Oct 23, 2025

Copy link
Copy Markdown
Member

@linonetwo .. I did have a new look at the demo page: The example text with ordered and unordered lists seems to have a lot of space in between, which IMO should not be there.

The result does not have that spacing, so it's not WYSIWIG anymore.

grafik

@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from bea743a to f6c1cc9 Compare November 12, 2025 06:21
linonetwo added 4 commits June 5, 2026 01:22
- Remove IIFE wrapper in dom-to-image/startup.js
- Convert 2-space to tab indentation in prosemirror/files/*.cjs (17 files)
- Replace var with const/let in new code across pragma.js, nodeview.js,
  wikiparser.js, and test-wikitext-blanklines.js
- Use ES2017+ const/let syntax consistently in new/modified code

All 1605 Jasmine specs pass.
@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from b7891bc to 6db74be Compare June 5, 2026 14:55
@linonetwo linonetwo marked this pull request as ready for review June 5, 2026 15:44
@linonetwo linonetwo marked this pull request as draft June 5, 2026 15:58
linonetwo added 14 commits June 6, 2026 15:42
The widget root node contenteditable=false fix (30e9926) was correct but
incomplete. The browser's native selection painting still covers rendered
widget content because prosemirror-view.cjs injects its own ::selection
rule that only hides the background, leaving the foreground color to
inherit from the global ::selection { color: highlighttext } (white).

Two-layer fix:
   ::selection rules so link text retains its own color.
2. JS: explicitly clear any residual native selection range after
   selectBlockNode() in handleRenderedContentMouseDown.

Also rebuild test.html so Playwright tests use the updated CSS.
The CSS tiddler color:inherit fix can be overridden by the same
selector injected by prosemirror-view.cjs at runtime. Inject the
rule from JavaScript after EditorView creation so it always appears
last in the DOM and wins the cascade.
… in hideselection

The previous fix (color: inherit) does not work in Chromium's ::selection
pseudo-element because 'inherit' there falls back to the system's
highlighttext color (white) rather than the element's own color.

Use 'currentColor' instead, which correctly resolves to the element's
computed color value in all browsers.

Also removed redundant workarounds:
- removeAllRanges() in handleRenderedContentMouseDown (ProseMirror recreates
  the native selection immediately via selectionchange listener)
- JS-injected CSS override in engine.js (unnecessary once CSS file is correct)

Tests updated to verify currentColor rule exists and that selected widget
links do not turn white.
commitEdit() was missing a renderViewMode() call after saveEdit(),
leaving the edit textarea visible after clicking save. cancelEdit()
already had this call. All 77 tests now pass.
Add tabindex attribute and focus delegation to the ProseMirror wrapper
DOM node, matching the behavior of SimpleEngine and FramedEngine. Without
this, the editor was not keyboard-focusable via Tab, making it impossible
for users to Tab from the title field into the editor.

- Set tabindex from widget.editTabIndex (defaults to EditTabIndex config)
- Delegate focus events from wrapper to ProseMirror EditorView.focus()
In auto-height mode, the ProseMirror editor wrapper only cleared height
and overflow without setting a minHeight, causing the editor to collapse
to a minimal height on empty or short content. This made it difficult
for users to click into the editor.

Set minHeight from widget.editMinHeight (defaults to 100px, configurable
via minHeight widget attribute) to match SimpleEngine/FramedEngine
behavior where resizeTextAreaToFit enforces a minimum height.
The two "Preview toggle should preserve custom syntax" tests in
source-panel.spec.js were flaky due to two race conditions:

1. Playwright's click() hangs: Clicking the "Edit this tiddler" button
   triggers TiddlyWiki's SPA hash navigation. Playwright detects this as
   a navigation event and waits for a load event that never fires, causing
   a 15-60s timeout.

2. Label check after edit mode: After entering edit mode, the custom
   syntax widget view-mode rendering is replaced by the ProseMirror editor.
   The waitForLabelOccurrences(1) check was racing against this DOM update.

Fix: Replace the Playwright button click with a direct call to the
navigator widget's handleEditTiddlerEvent() via page.evaluate(). This
bypasses Playwright's navigation detection while properly triggering
TiddlyWiki's draft creation. Wait for the preview pane button to appear
as a reliable signal that edit mode has fully loaded.

Verified stable: 25/25 passes with --repeat-each=5 (was 1-5 failures
per 5 runs before the fix).
Add real-time conversion for TW native inline syntax:
- [[Target]] / [[Display|Target]] → link mark
- ''bold'', //italic//, __underline__, ^^sup^^, ,,sub,,, ~~strike~~, `code` → respective marks

Changes:
- inputrules.js: refactor with declarative WIKITEXT_INLINE_MARK_RULES
  array and per-rule/global config via tiddlers
- base-setup.js / plugin-list.js: pass wiki and editorType to
  buildInputRules for config lookup
- misc.spec.js: add Playwright e2e tests for wikilink, bold, code
- test-prosemirror-inputrules.js: add Node unit tests for regex
  patterns and config behavior

Note: //italic// rule conflicts with SlashMenuPlugin (skipped test),
      to be resolved separately.
  handler guards checking doc[start-1] for ES2017 compatibility
- Add Ctrl+0 shortcut (alongside existing Shift-Ctrl-0) to reset
  current line to plain paragraph text
- Update corresponding tests to match new regex/handler behavior
- Fixes CI lint errors from eslint es-x/no-regexp-lookbehind-assertions
…t and supports undo

- handleMakeLink: when no text param (linkify toolbar), use selected text as href
  Previously returned false, falling back to native TW text-op path which
  miscalculates selection and rebuilds the entire editor (losing undo history).
- handleMakeLink: removed linkTarget guard so any non-empty selection triggers
  link creation with the selected text as the link target.
- Add Playwright tests for linkify correctness and undo after toolbar operations.
Move temporary core-module overrides and shadow tiddlers into a dedicated
patch/ directory so they can be easily deleted once the corresponding core
changes are merged upstream.

Changes:
- Move core/debounce.js -> patch/debounce.js (shadows core debounce utility)
- Move preview-type-dropdown.tid from toolbar/ to patch/ (shadow override)
- Move patch/startup.js into patch/ (monkey-patches for edit.js, factory.js,
  wikiparser.js)
- Update widget.js and engine.js to require $:/core/modules/utils/debounce.js
- Add patch/README.tid listing all pending core merges
- Remove unused origGetEditorType variable in patch/startup.js
…-shared.js

Move duplicated parse/serialize/external-sync/rebuild logic from the factory
engine and the standalone <-prosemirror> widget into a single library
module. Both entry points now share the same wikitext-to-ProseMirror and
save-path code, while keeping their own DOM lifecycle, image picker, and
tiddler-change adapters.
… and list items, drop unwrap outside list, add tests
… blocks accordingly

- Add inside-list drop zone detection: pointer over list item content area
  wraps non-list blocks into a new list item; pointer over marker/gutter
  keeps the block as a sibling (no wrapping)
- Fix buildMoveTransaction for inside-list drops: auto-wrap non-list blocks
  into a list item to preserve list structure
- Fix isInsideList boundary check at list end position
- Add findListAncestor / getListContentBox helper functions
- Extend test coverage: dropping into a list item wraps, dropping outside keeps type
@linonetwo linonetwo force-pushed the feat/prosemirror-wysiwyg-editor branch from 44d6f67 to 2a0bef7 Compare June 21, 2026 14:37
@Hydrowood0

Hydrowood0 commented Jun 24, 2026

Copy link
Copy Markdown

When the latest version of prosemirroe is used together with tiddlers\system$__xp.json, pressing the shortcut key to insert [] will lead to strange results.

"$:/xp/ui/EditorToolbar/list-checkbox.md": {
           "title": "$:/xp/ui/EditorToolbar/list-checkbox.md",
           "caption": "check list (Markdown)",
           "condition": "[<targetTiddler>type[text/x-markdown]] [<targetTiddler>type[text/markdown]]",
           "description": "Apply checklist formatting to lines containing selection",
           "icon": "$:/xp/images/checkbox",
           "shortcuts": "((list-checklist))",
           "tags": "$:/tags/EditorToolbar",
           "text": "<$action-sendmessage\n\t$message=\"tm-edit-text-operation\"\n\t$param=\"prefix-lines\"\n\tcharacter=\"- [ ]\"\n\tcount=\"1\"\n/>\n"
       },
       "$:/xp/ui/EditorToolbar/list-checkbox": {
           "title": "$:/xp/ui/EditorToolbar/list-checkbox",
           "caption": "check list",
           "condition": "[<targetTiddler>!has[type]] [<targetTiddler>type[text/vnd.tiddlywiki]]",
           "description": "Apply checklist formatting to lines containing selection",
           "icon": "$:/xp/images/checkbox",
           "shortcuts": "((list-checklist))",
           "tags": "$:/tags/EditorToolbar",
           "text": "<$action-sendmessage\n\t$message=\"tm-edit-text-operation\"\n\t$param=\"prefix-lines\"\n\tcharacter=\"[ ]\"\n\tcount=\"1\"\n/>\n"
       },

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants