Skip to content

Commit

Permalink
feat: allow content editable, copy button and details view to be disa…
Browse files Browse the repository at this point in the history
…bled/hidden (#107)

* fix: all content editable to be disabled

* fix: lint

* feat: support disableCopy and hideDetails

* fix: update readme
  • Loading branch information
dylandepass authored Feb 20, 2024
1 parent d7b6940 commit 368537a
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 12 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ The blocks plugins supports a special type of block called `library metadata` wh
| searchtags | A comma seperated list of search terms | Allows you to define other terms that could be used when searching for this block in the blocks plugin | false |
| tableHeaderBackgroundColor | A hex color (ex #ff3300) | Overrides the table header background color for any blocks in the section or page. | false |
| tableHeaderForegroundColor | A hex color (ex #ffffff) | Overrides the table header foreground color for any blocks in the section or page. | false |
| contentEditable | A boolean value (default: true) | Set to false to disable content editing in the preview window. | false |
| disableCopy | A boolean value (default: false) | Set to true to disable the copy button in the preview window. | false |
| hideDetailsView | A boolean value (default: false) | Hide the block details panel inside the preview window. | false |

### Default Library metadata vs Library metadata

Expand Down Expand Up @@ -170,6 +173,7 @@ The blocks plugin supports the following configuration properties that can be se
|----------------|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|----------|
| encodeImages | A boolean value that indicates if images should be encoded during copy operations | If your site has a Zero trust network access (ZTNA) service enabled such as Cloudflare Access then images should be encoded for copy/paste operations to work correctly with images. | false |
| viewPorts | Full or simplified configuration object, see examples below. | Configuration to overwrite the default viewport sizes. The default is 480px fo mobile, 768px for tablet and 100% of the current window for desktop. | false |
| contentEditable | A boolean value to disable content editing in all block previews. | Set to false to disable content editing. | false |

#### Examples

Expand Down
6 changes: 6 additions & 0 deletions src/components/block-list/block-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,19 @@ export class BlockList extends LitElement {
itemName = 'Unnamed Item';
}

// Check if the copy button should be disabled
const disableCopy = sectionLibraryMetadata.disablecopy
?? defaultLibraryMetadata.disablecopy
?? false;

// Create the sidenav item for the variant
const blockVariantItem = createSideNavItem(
itemName,
'sp-icon-file-code',
false,
true,
'sp-icon-copy',
disableCopy,
);

// Add search tags to the sidenav item
Expand Down
24 changes: 20 additions & 4 deletions src/components/block-renderer/block-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,16 @@ export class BlockRenderer extends LitElement {
* @param {HTMLElement} blockWrapper The wrapped block, includes section metadata
* @param {HTMLElement} hostContainer The host container to render the iframe into
*/
// eslint-disable-next-line no-unused-vars
async loadBlock(blockName, blockData, blockWrapper, defaultLibraryMetadata, hostContainer) {

async loadBlock(
blockName,
blockData,
blockWrapper,
defaultLibraryMetadata,
sectionLibraryMetadata,
// eslint-disable-next-line no-unused-vars
hostContainer,
) {
const { context } = AppModel.appStore;
const { url: blockURL } = blockData;
const origin = blockData.extended
Expand Down Expand Up @@ -291,8 +299,16 @@ export class BlockRenderer extends LitElement {
const sidekickLibraryClass = 'sidekick-library';
content?.classList.add(sidekickLibraryClass);

// Decorate the block with ids
this.decorateEditableElements(content);
// Should the content be editable?
const editable = sectionLibraryMetadata?.contenteditable
?? defaultLibraryMetadata?.contenteditable
?? context?.contentEditable
?? true;

// Editable can be a boolean or a string
if (editable.toString() === 'true') {
this.decorateEditableElements(content);
}

// Clone the block and decorate it
const blockClone = blockWrapper.cloneNode(true);
Expand Down
21 changes: 21 additions & 0 deletions src/plugins/blocks/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ function renderFrame(contextViewPorts, container) {
<sp-split-view
vertical
resizable
collapsible
primary-size="2600"
secondary-min="200"
splitter-pos="250"
Expand Down Expand Up @@ -287,6 +288,24 @@ function loadBlock(context, event, container) {
// Set block title & description in UI
updateDetailsContainer(content, authoredBlockName, blockDescription);

const disableCopyButton = sectionLibraryMetadata.disablecopy
?? defaultLibraryMetadata.disablecopy
?? false;

const copyButton = container.querySelector('.content .copy-button');
copyButton.removeAttribute('disabled');
if (disableCopyButton) {
copyButton.setAttribute('disabled', 'true');
}

const hideDetailsView = sectionLibraryMetadata.hidedetailsview
?? defaultLibraryMetadata.hidedetailsview
?? context.hidedetailsview
?? false;

const splitView = container.querySelector('.content sp-split-view');
splitView.primarySize = hideDetailsView ? '100%' : '75%';

const blockRenderer = content.querySelector('block-renderer');

// If the block element exists, load the block
Expand All @@ -295,6 +314,7 @@ function loadBlock(context, event, container) {
blockData,
blockWrapper,
defaultLibraryMetadata,
sectionLibraryMetadata,
container,
);

Expand Down Expand Up @@ -346,6 +366,7 @@ function loadTemplate(context, event, container) {
blockData,
blockWrapper,
defaultLibraryMetadata,
sectionLibraryMetadata,
container,
);

Expand Down
3 changes: 2 additions & 1 deletion src/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export function createSideNavItem(
disclosureArrow,
hasAction = false,
actionIcon = '',
disableCopy = false,
) {
const childElements = [];
if (icon) {
Expand All @@ -199,7 +200,7 @@ export function createSideNavItem(
}

const blockVariant = createTag('sp-sidenav-item', { label }, childElements);
if (hasAction) {
if (hasAction && !disableCopy) {
blockVariant.setAttribute('action', true);
}

Expand Down
29 changes: 29 additions & 0 deletions test/components/block-renderer/block-renderer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,35 @@ describe('BlockRenderer', () => {
});
});

describe('disable contentEditable', async () => {
it('should be disabled', async () => {
await renderContent('cards', cardsPageUrl, CARDS_DEFAULT_STUB, { contenteditable: false });

const iframe = blockRenderer.shadowRoot.querySelector('iframe');
await waitUntil(
() => recursiveQuery(iframe.contentDocument, '.cards'),
'Element did not render children',
);

const { contentDocument } = iframe;
const cardsBlock = contentDocument.querySelector('.cards');
cardsBlock.querySelectorAll('li, a, h1, h2, h3, h4, h5, h6').forEach((el) => {
expect(el.getAttribute('contenteditable')).to.be.null;
expect(el.getAttribute('data-library-id')).to.be.null;
});

cardsBlock.querySelectorAll('p').forEach((el) => {
expect(el.getAttribute('contenteditable')).to.be.null;
expect(el.getAttribute('data-library-id')).to.be.null;
});

cardsBlock.querySelectorAll('strong').forEach((el) => {
expect(el.getAttribute('contenteditable')).to.be.null;
expect(el.getAttribute('data-library-id')).to.be.null;
});
});
});

describe('loadBlock', () => {
it('should load a block page', async () => {
await renderContent('cards', cardsPageUrl, CARDS_DEFAULT_STUB);
Expand Down
38 changes: 31 additions & 7 deletions test/plugins/blocks/blocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ describe('Blocks Plugin', () => {
const mockData = [CARDS_BLOCK_LIBRARY_ITEM, COLUMNS_BLOCK_LIBRARY_ITEM];
mockFetchCardsPlainHTMLSuccess();
mockFetchColumnsPlainHTMLSuccess();
await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);

const blockLibrary = container.querySelector('.block-library');
const blocks = blockLibrary.querySelector('sp-split-view .menu .list-container block-list').shadowRoot.querySelectorAll('sp-sidenav-item');
Expand All @@ -331,7 +331,7 @@ describe('Blocks Plugin', () => {
CARDS_BLOCK_LIBRARY_ITEM,
COLUMNS_BLOCK_LIBRARY_ITEM,
];
await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);

const blockLibrary = container.querySelector('.block-library');
const blocks = blockLibrary.querySelector('sp-split-view .menu .list-container block-list').shadowRoot.querySelectorAll('sp-sidenav-item');
Expand All @@ -345,7 +345,7 @@ describe('Blocks Plugin', () => {
const mockData = [NON_EXISTENT_BLOCK_LIBRARY_ITEM];

container.addEventListener(PLUGIN_EVENTS.TOAST, eventSpy);
await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);

const blockLibrary = container.querySelector('.block-library');
const blocks = blockLibrary.querySelector('sp-split-view .menu .list-container block-list').shadowRoot.querySelectorAll('sp-sidenav-item');
Expand All @@ -359,7 +359,7 @@ describe('Blocks Plugin', () => {
mockFetchColumnsPlainHTMLSuccess();
const mockData = [CARDS_BLOCK_LIBRARY_ITEM, COLUMNS_BLOCK_LIBRARY_ITEM];

await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);
const blockLibrary = container.querySelector('.block-library');

const spSearch = blockLibrary.querySelector('sp-split-view .menu .search sp-search');
Expand All @@ -380,7 +380,7 @@ describe('Blocks Plugin', () => {
mockFetchColumnsPlainHTMLSuccess();
const mockData = [CARDS_BLOCK_LIBRARY_ITEM, COLUMNS_BLOCK_LIBRARY_ITEM];

await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);

const blockLibrary = container.querySelector('.block-library');
const spSearch = blockLibrary.querySelector('sp-split-view .menu .search sp-search');
Expand All @@ -402,7 +402,7 @@ describe('Blocks Plugin', () => {
const copyBlockSpy = sinon.spy();
const mockData = [CARDS_BLOCK_LIBRARY_ITEM];

await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);
const blockLibrary = container.querySelector('.block-library');
const blockList = blockLibrary.querySelector('sp-split-view .menu .list-container block-list');
blockList.addEventListener('CopyBlock', copyBlockSpy);
Expand Down Expand Up @@ -430,7 +430,7 @@ describe('Blocks Plugin', () => {
const copyBlockSpy = sinon.spy();
const mockData = [DEFAULT_CONTENT_LIBRARY_ITEM];

await decorate(container, mockData);
await decorate(container, mockData, undefined, AppModel.appStore.context);
const blockLibrary = container.querySelector('.block-library');
const blockList = blockLibrary.querySelector('sp-split-view .menu .list-container block-list');
blockList.addEventListener('CopyBlock', copyBlockSpy);
Expand Down Expand Up @@ -922,5 +922,29 @@ describe('Blocks Plugin', () => {

expect(frameView.style.width).to.eq('100%');
});

it('disable copy button', async () => {
mockFetchCardsPlainHTMLSuccess({ disablecopy: 'true' });
mockFetchColumnsPlainHTMLSuccess();
const mockData = [CARDS_BLOCK_LIBRARY_ITEM, COLUMNS_BLOCK_LIBRARY_ITEM];

await decorate(container, mockData, undefined, AppModel.appStore.context);
const blockLibrary = container.querySelector('.block-library');

const copyButton = blockLibrary.querySelector('sp-split-view .content .details-container .copy-button');
expect(copyButton.getAttribute('disabled')).to.eq('true');
});

it('hide details view', async () => {
mockFetchCardsPlainHTMLSuccess({ hideDetailsView: 'true' });
mockFetchColumnsPlainHTMLSuccess();
const mockData = [CARDS_BLOCK_LIBRARY_ITEM, COLUMNS_BLOCK_LIBRARY_ITEM];

await decorate(container, mockData, undefined, AppModel.appStore.context);
const blockLibrary = container.querySelector('.block-library');

const splitView = blockLibrary.querySelector('sp-split-view');
expect(splitView.getAttribute('splitter-pos')).to.eq('-2');
});
});
});

0 comments on commit 368537a

Please sign in to comment.