-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Tiptap RTE: Adds support for Reusable Content of Blocks #23195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
2766d52
Tiptap RTE Blocks: support Element Library (reusable content) references
leekelleher 9b4c617
Tiptap RTE Blocks: wire up is-reference attribute for Element Library…
leekelleher ab054cb
Tiptap RTE Blocks: fix unpublished state for Element Library references
leekelleher 1e69009
Tiptap RTE Blocks: address PR review feedback
leekelleher 007d6cd
Tiptap RTE Blocks: restore margin-top on RTE block entries
leekelleher 475386d
Merge branch 'v19/dev' into v19/feature/rcob-rte-blocks
leekelleher 3f388fc
RTE: address SonarCloud code smells on PR #23195
leekelleher d4e59ff
Merge branch 'v19/dev' into v19/feature/rcob-rte-blocks
leekelleher 37498c0
test(tiptap): add layout-key coverage for block doc-walk
leekelleher 2125bdc
Merge branch 'v19/dev' into v19/feature/rcob-rte-blocks
leekelleher fa83fe3
refactor(block): derive isExposed on shared block entry context
leekelleher cb9669c
test(tiptap): update div-container block expectations for layout keys
leekelleher 68b82bb
fix(core): allow BlockRegex to match RTE blocks carrying data-key
leekelleher 6f6ba70
Merge branch 'v19/dev' into v19/feature/rcob-rte-blocks
leekelleher 2d3bfb3
Changed `combineLatest` to `observeMultiple`
leekelleher 435a81f
Merge remote-tracking branch 'origin/v19/dev' into v19/feature/rcob-r…
nielslyngsoe 0298f12
Border radius on focused block
leekelleher f3b212f
Fix orphaned RTE block re-insertion when deleting mixed local/externa…
leekelleher 0a1e0ec
Move RTE block-key markup scan into the base class, deprecate _filter…
leekelleher 41de41e
Adds state to `_name`
leekelleher 79eb0b4
Split this PR: move server-side, shared block-entry and RTE entry UI …
leekelleher 830ad4e
test(rte): assert against manager state, add layout-before-content or…
leekelleher c5ce29f
perf(rte): hoist layout/content key regex out of markup scan loop
leekelleher File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| import { UmbPropertyEditorUiRteElementBase } from './rte-base.element.js'; | ||
| import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../constants.js'; | ||
| import { expect, fixture, html } from '@open-wc/testing'; | ||
| import { customElement } from '@umbraco-cms/backoffice/external/lit'; | ||
| import { UMB_BLOCK_RTE_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/block-rte'; | ||
| import type { UmbPropertyEditorRteValueType } from '../types.js'; | ||
| import type { UmbBlockRteLayoutModel, UmbBlockRteManagerContext } from '@umbraco-cms/backoffice/block-rte'; | ||
| import type { UmbBlockDataModel } from '@umbraco-cms/backoffice/block'; | ||
|
|
||
| @customElement('umb-test-rte-base') | ||
| class UmbTestRteBaseElement extends UmbPropertyEditorUiRteElementBase { | ||
| public callFilterUnusedBlocksFromMarkup(markup: string) { | ||
| this._filterUnusedBlocksFromMarkup(markup); | ||
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-deprecated | ||
| public callFilterUnusedBlocks(usedLayoutKeys: Array<string | null>) { | ||
| // eslint-disable-next-line @typescript-eslint/no-deprecated | ||
| this._filterUnusedBlocks(usedLayoutKeys); | ||
| } | ||
| } | ||
|
|
||
| declare global { | ||
| interface HTMLElementTagNameMap { | ||
| 'umb-test-rte-base': UmbTestRteBaseElement; | ||
| } | ||
| } | ||
|
|
||
| function makeValue(layouts: Array<UmbBlockRteLayoutModel>, contents: Array<UmbBlockDataModel>): UmbPropertyEditorRteValueType { | ||
| return { | ||
| markup: '', | ||
| blocks: { | ||
| layout: { [UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts }, | ||
| contentData: contents, | ||
| settingsData: [], | ||
| expose: [], | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| const LAYOUT_A: UmbBlockRteLayoutModel = { key: 'layout-a', contentKey: 'content-a' }; | ||
| const CONTENT_A: UmbBlockDataModel = { key: 'content-a', contentTypeKey: 'content-type', values: [] }; | ||
| const LAYOUT_B: UmbBlockRteLayoutModel = { key: 'layout-b', contentKey: 'content-b' }; | ||
| const CONTENT_B: UmbBlockDataModel = { key: 'content-b', contentTypeKey: 'content-type', values: [] }; | ||
|
|
||
| describe('UmbPropertyEditorUiRteElementBase', () => { | ||
| let element: UmbTestRteBaseElement; | ||
| // Asserted against directly: _filterUnusedBlocksByLayoutKeys' contract is mutating manager state. | ||
| // element.value only re-syncs from that state via a property-context-dependent observer, which this | ||
| // fixture (no real UMB_PROPERTY_CONTEXT provider) never wires up. | ||
| let managerContext: UmbBlockRteManagerContext; | ||
|
|
||
| beforeEach(async () => { | ||
| element = await fixture(html`<umb-test-rte-base></umb-test-rte-base>`); | ||
| managerContext = (await element.getContext(UMB_BLOCK_RTE_MANAGER_CONTEXT))!; | ||
| }); | ||
|
|
||
| describe('_filterUnusedBlocksFromMarkup', () => { | ||
| it('keeps only blocks referenced by data-key in the given markup', () => { | ||
| element.value = makeValue([LAYOUT_A, LAYOUT_B], [CONTENT_A, CONTENT_B]); | ||
|
|
||
| element.callFilterUnusedBlocksFromMarkup('<umb-rte-block data-key="layout-a" data-content-key="content-a"></umb-rte-block>'); | ||
|
|
||
| expect(managerContext.getLayouts()).to.deep.equal([LAYOUT_A]); | ||
| expect(managerContext.getContents()).to.deep.equal([CONTENT_A]); | ||
| }); | ||
|
|
||
| it('falls back to data-content-key for legacy layouts with no separate key', () => { | ||
| const legacyLayout: UmbBlockRteLayoutModel = { key: 'content-c', contentKey: 'content-c' }; | ||
| const legacyContent: UmbBlockDataModel = { key: 'content-c', contentTypeKey: 'content-type', values: [] }; | ||
| element.value = makeValue([legacyLayout], [legacyContent]); | ||
|
|
||
| element.callFilterUnusedBlocksFromMarkup('<umb-rte-block data-content-key="content-c"></umb-rte-block>'); | ||
|
|
||
| expect(managerContext.getLayouts()).to.deep.equal([legacyLayout]); | ||
| }); | ||
|
|
||
| it('removes a block entirely absent from the markup', () => { | ||
| element.value = makeValue([LAYOUT_A], [CONTENT_A]); | ||
|
|
||
| element.callFilterUnusedBlocksFromMarkup('<p>No blocks here.</p>'); | ||
|
|
||
| expect(managerContext.getLayouts()).to.deep.equal([]); | ||
| expect(managerContext.getContents()).to.deep.equal([]); | ||
| }); | ||
|
|
||
| // Removing content/settings emits synchronously; if a still-present layout's content stays | ||
| // resolvable while that happens, #updateBlocks (block.tiptap-api.ts) re-inserts the node before | ||
| // the layout removal catches up, orphaning it. Layouts must therefore be removed first. | ||
| it('removes the layout before removing its content', () => { | ||
| element.value = makeValue([LAYOUT_A], [CONTENT_A]); | ||
|
|
||
| const removalOrder: Array<'layouts' | 'contents'> = []; | ||
| managerContext.layouts.subscribe((layouts) => { | ||
| if (!layouts.some((x) => x.key === LAYOUT_A.key)) removalOrder.push('layouts'); | ||
| }); | ||
| managerContext.contents.subscribe((contents) => { | ||
| if (!contents.some((x) => x.key === CONTENT_A.key)) removalOrder.push('contents'); | ||
| }); | ||
|
|
||
| element.callFilterUnusedBlocksFromMarkup('<p>No blocks here.</p>'); | ||
|
|
||
| expect(removalOrder).to.deep.equal(['layouts', 'contents']); | ||
| }); | ||
| }); | ||
|
|
||
| // The deprecated overload must keep working for existing RTE implementations built against it. | ||
| describe('_filterUnusedBlocks (deprecated)', () => { | ||
| it('keeps only blocks referenced by the given layout keys', () => { | ||
| element.value = makeValue([LAYOUT_A, LAYOUT_B], [CONTENT_A, CONTENT_B]); | ||
|
|
||
| element.callFilterUnusedBlocks(['layout-a']); | ||
|
|
||
| expect(managerContext.getLayouts()).to.deep.equal([LAYOUT_A]); | ||
| expect(managerContext.getContents()).to.deep.equal([CONTENT_A]); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.