Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
f38fe21
feat(plate): add as new widget, basic marks
demshy Mar 22, 2024
4ec4983
feat(richtext): add paragraphs and headings, refactor some
demshy Mar 27, 2024
899990b
feat(richtext): add basic listsr
demshy Mar 27, 2024
e7e110b
feat(richtext): add serializers, update tests
demshy Mar 27, 2024
7cd2eee
feat(richtext): add link plugin
demshy Mar 28, 2024
75c1fa3
feat(richtext): add blockquote
demshy Mar 29, 2024
ab5132e
feat(richtext): add blockquote plugin special keydown events
demshy Apr 4, 2024
181d644
feat(richtext): use own querynode
demshy Apr 4, 2024
c6ac950
feat(richtext): use getBlockAbove for heading state
demshy Aug 8, 2024
08c14c8
feat(richtext): add blockquote toolbar button state
demshy Aug 8, 2024
c0b12f9
feat(richtext): add editor components to toolbar
demshy Aug 8, 2024
50092e0
chore(eslimt): update eslint config to allow package.json exports pro…
demshy Oct 23, 2024
dee442d
feat(richtext): upgrade plate to 39, add shortcodes
demshy Oct 23, 2024
86fcc39
feat(richtext): clean up some logs
demshy Oct 23, 2024
c8b1c59
Merge branch 'main' into widget-richtext
martinjagodic Jun 26, 2025
a418dc0
Merge branch 'main' into widget-richtext
demshy Sep 30, 2025
4802720
chore(): update slate to 0.110
demshy Oct 1, 2025
0025e18
chore: update slate and related dependencies to latest versions
demshy Oct 1, 2025
6960730
Merge branch 'chore/slate-116' into widget-richtext
demshy Oct 1, 2025
f0fb7a8
chore: change remark versions to the ones that work with our tokenize…
demshy Oct 1, 2025
db48a9a
feat(widget-richtext): update plate to v50, temporary disable editor …
Oct 8, 2025
63b5a64
feat(widget-richtext): add shortcode plugin for editor components
demshy Oct 15, 2025
87cb771
fix: lint
demshy Oct 15, 2025
d5f4b0c
fix(widget-richtext): fix preview for using richtext as editor component
demshy Oct 15, 2025
6fa2dc1
style(widget-richtext): lint code
demshy Oct 15, 2025
3a2c535
fix: temporarily remove serializer tests for richt
demshy Oct 15, 2025
6554142
fix: add initialValue to markdown-widget slate editor
demshy Oct 17, 2025
66d2bc6
feat(widget-richtext): add raw markdown editor and sticky header for …
demshy Oct 17, 2025
d2cc764
feat(widget-richtext): fix pendingFocus
demshy Oct 21, 2025
e05868a
feat(widget-richtext): remove prop drilled unused getAsset
demshy Oct 21, 2025
4d7bb88
feat(widget-richtext): add webpack config to fix build process
demshy Oct 21, 2025
7f2fa4b
feat(richtext-widget): lint
demshy Oct 21, 2025
7a8241d
Merge branch 'main' into widget-richtext
demshy Oct 21, 2025
1e4fcdd
Merge branch 'main' into widget-richtext
demshy Nov 4, 2025
c3316a8
feat(widget-richtext): add unit tests from the markdown and adapt mer…
demshy Nov 18, 2025
852fe0f
Merge branch 'widget-richtext' of github.com:poslovnimediji/decap-cms…
demshy Nov 18, 2025
12d1387
Merge branch 'main' into widget-richtext
demshy Nov 18, 2025
a4a87c3
Merge branch 'main' into widget-richtext
martinjagodic Dec 5, 2025
b4e7ab9
fix(widget-richtext): adapt link serializers to new slate format
demshy Dec 9, 2025
71c2d92
test(widget-richtext): add unit tests from markdown widget and adapt …
demshy Dec 9, 2025
cc18a25
Merge branch 'main' into widget-richtext
demshy Dec 9, 2025
50c22a9
fix(widget-richtext): codeblock & basic elements
demshy Dec 10, 2025
dfe7678
feat(widget-richtext): handle keyboard shortcuts for links, headings …
demshy Dec 11, 2025
86774c5
test(widget-richtext): add 6/8 e2e tests copied and adapted from mark…
demshy Dec 11, 2025
940cd44
Merge branch 'widget-richtext' of github.com:poslovnimediji/decap-cms…
demshy Dec 11, 2025
586e718
style(widget-richtext): lint code
demshy Dec 11, 2025
b55ae26
feat: add table preview in visual editor (#8)
martinjagodic Jan 30, 2026
fbf9bb1
Apply suggestions from code review
martinjagodic Feb 25, 2026
02f54af
Apply suggestion from @yanthomasdev
martinjagodic Feb 25, 2026
e2f0fba
Merge branch 'main' into widget-richtext
martinjagodic Feb 25, 2026
759e037
refactor(VisualEditor): remove unused handleBlockClick function and i…
martinjagodic Feb 25, 2026
a7a1f1d
fix(RichtextControl): remove unnecessary ref attributes from editor divs
martinjagodic Feb 25, 2026
6e6936b
fix: format
martinjagodic Feb 25, 2026
e8b150b
fix(RichtextControl): add missing onAddAsset and getAsset props to co…
martinjagodic Feb 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ module.exports = {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
exports: {},
},
'import/core-modules': [...packages, 'decap-cms-app/dist/esm'],
},
Expand Down
96 changes: 96 additions & 0 deletions cypress/e2e/richtext_widget_backspace_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import '../utils/dismiss-local-backup';

describe('Markdown widget', () => {

before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.task('useRichTextWidget');
});

beforeEach(() => {
cy.loginAndNewPost();
cy.clearMarkdownEditorContent();
});

after(() => {
cy.task('teardownBackend', { backend: 'test' });
});

// describe('pressing backspace', () => {
it('sets non-default block to default when empty', () => {
cy.focused()
.clickHeadingOneButton()
.backspace()
.confirmMarkdownEditorContent(`
<p></p>
`);
});
it('moves to previous block when no character left to delete', () => {
cy.focused()
.type('foo')
.enter()
.clickHeadingOneButton()
.type('a')
.backspace({times: 2})
.confirmMarkdownEditorContent(`
<p>foo</p>
`);
});
// behaviour change: resets the block to default
it('does nothing at start of first block in document when non-empty and non-default', () => {
cy.focused()
.clickHeadingOneButton()
.type('foo')
.setCursorBefore('foo')
.backspace({ times: 4 })
.confirmMarkdownEditorContent(`
<p>foo</p>
`);
});
// behaviour change: also resets the block to default (as in previous test)
it('deletes individual characters in middle of non-empty non-default block in document', () => {
cy.focused()
.clickHeadingOneButton()
.type('foo')
.setCursorAfter('fo')
.backspace({ times: 3 })
.confirmMarkdownEditorContent(`
<p>o</p>
`);
});
it('at beginning of non-first block, moves default block content to previous block', () => {
cy.focused()
.clickHeadingOneButton()
.type('foo')
.enter()
.type('bar')
.setCursorBefore('bar')
.backspace()
.confirmMarkdownEditorContent(`
<h1>foobar</h1>
`);
});
it('at beginning of non-first block, moves non-default block content to previous block', () => {
cy.focused()
.type('foo')
.enter()
.clickHeadingOneButton()
.type('bar')
.enter()
.clickHeadingTwoButton()
.type('baz')
.setCursorBefore('baz')
.backspace()
.confirmMarkdownEditorContent(`
<p>foo</p>
<h1>barbaz</h1>
`)
.setCursorBefore('bar')
.backspace()
.confirmMarkdownEditorContent(`
<p>foobarbaz</p>
`);
// });
});
});
117 changes: 117 additions & 0 deletions cypress/e2e/richtext_widget_code_block_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { oneLineTrim, stripIndent } from 'common-tags';
import '../utils/dismiss-local-backup';

describe('Markdown widget code block', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.task('useRichTextWidget');
});

beforeEach(() => {
cy.loginAndNewPost();
cy.clearMarkdownEditorContent();
});

after(() => {
cy.task('teardownBackend', { backend: 'test' });
});
describe('code block', () => {
// behaviour change: changes how the raw editor is rendered - single block mode
it('outputs code', () => {
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy
.insertCodeBlock()
.type('foo')
.enter()
.type('bar')
.confirmMarkdownEditorContent(
`
${codeBlock(`
foo
bar
`)}
`,
)
.wait(500)
.clickModeToggle().confirmRawEditorContent('``` foo bar ```');
});
});
});

function codeBlock(content) {
const lines = stripIndent(content)
.split('\n')
.map(
(line, idx) => `
<div>
<div>
<div>${idx + 1}</div>
</div>
<pre><span>${line}</span></pre>
</div>
`,
)
.join('');

return oneLineTrim`
<div>
<div></div>
<div>
<div>
<div><label>Code Block</label></div>
<div><button><span><svg>
<path></path>
</svg></span></button>
<div>
<div>
<div><textarea></textarea></div>
<div>
<div></div>
</div>
<div>
<div></div>
</div>
<div></div>
<div></div>
<div>
<div>
<div>
<div>
<div>
<div>
<pre><span>xxxxxxxxxx</span></pre>
</div>
<div></div>
<div></div>
<div>
<div> </div>
</div>
<div>
${lines}
</div>
</div>
</div>
</div>
</div>
<div></div>
<div>
<div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<span>
<span>
<span></span>
</span>
</span>
</div>
</div>
<div></div>
</div>
`;
}
113 changes: 113 additions & 0 deletions cypress/e2e/richtext_widget_enter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import '../utils/dismiss-local-backup';

describe('Richtext widget breaks', () => {
before(() => {
Cypress.config('defaultCommandTimeout', 4000);
cy.task('setupBackend', { backend: 'test' });
cy.task('useRichTextWidget');
});

beforeEach(() => {
cy.loginAndNewPost();
cy.clearMarkdownEditorContent();
});

after(() => {
cy.task('teardownBackend', { backend: 'test' });
});

describe('pressing enter', () => {
it('creates new default block from empty block', () => {
cy.focused()
.enter()
.confirmMarkdownEditorContent(`
<p></p>
<p></p>
`);
});
it('creates new default block when selection collapsed at end of block', () => {
cy.focused()
.type('foo')
.enter()
.confirmMarkdownEditorContent(`
<p>foo</p>
<p></p>
`);
});
it('creates new default block when selection collapsed at end of non-default block', () => {
cy.clickHeadingOneButton()
.type('foo')
.enter()
.confirmMarkdownEditorContent(`
<h1>foo</h1>
<p></p>
`);
});
// behaviour change: plate now creates the new default block before the non-default block
it('creates new default block when selection collapsed in empty non-default block', () => {
cy.clickHeadingOneButton()
.enter()
.confirmMarkdownEditorContent(`
<p></p>
<h1></h1>
`);
});
// behaviour change: plate now creates the new default block before the non-default block
it('splits block into two same-type blocks when collapsed selection at block start', () => {
cy.clickHeadingOneButton()
.type('foo')
.setCursorBefore('foo')
.enter()
.confirmMarkdownEditorContent(`
<p></p>
<h1>foo</h1>
`);
});
// behaviour change: plate now splits into default block
it('splits block into two same-type blocks when collapsed in middle of selection at block start', () => {
cy.clickHeadingOneButton()
.type('foo')
.setCursorBefore('oo')
.enter()
.confirmMarkdownEditorContent(`
<h1>f</h1>
<p>oo</p>
`);
});
it('deletes selected content and splits to same-type block when selection is expanded', () => {
cy.clickHeadingOneButton()
.type('foo bar')
.setSelection('o b')
.enter()
.confirmMarkdownEditorContent(`
<h1>fo</h1>
<h1>ar</h1>
`);
});
});

// skipped: cypress type event does not trigger actual keyboard event for the Plate to detect
describe.skip('pressing shift+enter', () => {
it('creates line break', () => {
cy.focused()
.enter({ shift: true })
.confirmMarkdownEditorContent(`
<p>
a<br>
</p>
`);
});
it('creates consecutive line break', () => {
cy.focused()
.enter({ shift: true, times: 4 })
.confirmMarkdownEditorContent(`
<p>
<br>
<br>
<br>
<br>
</p>
`);
});
});
});
Loading