Skip to content

Commit

Permalink
Always lazy load new blocks
Browse files Browse the repository at this point in the history
Backported from 5.x, hopefully will help with #823
  • Loading branch information
ttempleton committed May 29, 2024
1 parent aac358a commit 195a81b
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 323 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

### Changed
- New Neo input blocks are now always loaded from the server, regardless of the `enableLazyLoadingNewBlocks` plugin setting

### Deprecated
- Deprecated `benf\neo\models\Settings::$enableLazyLoadingNewBlocks`
- Deprecated `benf\neo\services\Blocks::renderTabs()`

### Fixed
- Fixed a bug where swapping the positions of new Neo blocks could cause a mixup of field layout elements and content loss
- Fixed an error that could occur when updating visible field layout elements after the first change to an entry
Expand Down
2 changes: 0 additions & 2 deletions src/assets/InputAsset.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ private static function _getBlockTypesJsSettings(Field $field, array $blockTypes
{
$user = Craft::$app->getUser()->getIdentity();
$pluginSettings = Neo::$plugin->getSettings();
$loadTabs = !$pluginSettings->enableLazyLoadingNewBlocks;
$disablePermissions = !$pluginSettings->enableBlockTypeUserPermissions;
$jsBlockTypes = [];

Expand Down Expand Up @@ -214,7 +213,6 @@ private static function _getBlockTypesJsSettings(Field $field, array $blockTypes
fn($tab) => Craft::t('site', $tab->name),
$blockType->getFieldLayout()->getTabs()
),
'tabs' => $loadTabs ? Neo::$plugin->blocks->renderTabs($block) : null,
'fieldLayoutId' => $blockType->fieldLayoutId,
'groupId' => $blockType->groupId,
'hasChildBlocksUiElement' => $blockType->hasChildBlocksUiElement(),
Expand Down
2 changes: 1 addition & 1 deletion src/assets/dist/neo-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/assets/dist/neo-main.js.map

Large diffs are not rendered by default.

237 changes: 19 additions & 218 deletions src/assets/src/input/Block.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ const _defaults = {
showBlockTypeHandle: false
}

const _resources = {}

const _escapeMap = {
'&': '&',
'<': '&lt;',
Expand All @@ -34,23 +32,6 @@ const _escapeMap = {
'/': '&#x2F;'
}

function _resourceFilter () {
let url = this.href || this.src

if (url) {
const paramIndex = url.indexOf('?')

url = (paramIndex < 0 ? url : url.substr(0, paramIndex))

const isNew = !Object.prototype.hasOwnProperty.call(_resources, url)
_resources[url] = 1

return isNew
}

return true
}

function _escapeHTML (str) {
return str ? str.replace(/[&<>"'/]/g, s => _escapeMap[s]) : ''
}
Expand Down Expand Up @@ -93,17 +74,19 @@ export default Garnish.Base.extend({
} else {
this._tabs = null
}
this._html = settings.tabs?.html ?? null
this._js = settings.tabs?.js ?? null
this._blockHtml = settings.blockHtml
this._bodyHtml = settings.bodyHtml
this._headHtml = settings.headHtml
this._id = settings.id
this._enabled = settings.enabled && this._blockType.getEnabled()
this._initialEnabled = settings.enabled
this._modified = settings.modified
this._showButtons = settings.showButtons
this._renderOldChildBlocksContainer = !settings.blockType.hasChildBlocksUiElement()
this.$container = generateElement
? this._generateElement(settings.showBlockTypeHandle)
this.$container = this._blockHtml
? $(this._blockHtml)
: this._field.$container.find(`[data-neo-b-id=${this._id}]`)
this._uuid = settings.uuid ?? this.$container.data('neo-b-uuid')

const $neo = this.$container.find('[data-neo-b]')
this.$bodyContainer = $neo.filter(`[data-neo-b="${this._id}.container.body"]`)
Expand Down Expand Up @@ -145,202 +128,15 @@ export default Garnish.Base.extend({
this.$container.data('block', this)
},

_generateElement (showHandle = false) {
NS.enter(this._templateNs)
const baseInputName = NS.toFieldName()
const baseInputId = NS.toString('-')
NS.leave()

const type = this._blockType
const tabs = this._tabs ?? type.getTabs()
const hasTabs = tabs.length > 0
const isParent = type.isParent()
const actionBtnLabel = `${type.getName()} ${Craft.t('neo', 'Actions')}`
const actionMenuId = `neoblock-action-menu-${this._id}`
const tabsBtnLabel = `${type.getName()} ${Craft.t('neo', 'Tabs')}`
const tabsMenuId = `neoblock-tabs-menu-${this._id}`
const sortOrderName = `${this._templateNs[0]}[${this._templateNs.slice(1, this._templateNs.length - 2).join('][')}][sortOrder]`
const elementHtml = []
elementHtml.push(`
<div class="ni_block ni_block--${type.getHandle()} is-${this._collapsed ? 'collapsed' : 'expanded'} ${!hasTabs && !isParent ? 'is-empty' : ''} ${isParent ? 'is-parent' : ''}" data-neo-b-id="${this._id}" data-neo-b-name="${type.getName()}">
<input type="hidden" name="${baseInputName}[type]" value="${type.getHandle()}">
<input type="hidden" name="${baseInputName}[enabled]" value="${this._enabled ? '1' : ''}" data-neo-b="${this._id}.input.enabled">
<input type="hidden" name="${baseInputName}[level]" value="${this._level}" data-neo-b="${this._id}.input.level">
<input type="hidden" name="${sortOrderName}[]" value="${this._id}" data-neo-b="${this._id}.input.sortOrder">`)

if (isNaN(parseInt(this._id))) {
elementHtml.push(`
<input type="hidden" name="${baseInputName}[collapsed]" value="${!this._expanded ? '1' : ''}" data-neo-b="${this._id}.input.collapsed">`)
}

elementHtml.push(`
<div class="ni_block_topbar" data-neo-b="${this._id}.container.topbar">
<div class="ni_block_topbar_left" data-neo-b="${this._id}.container.topbarLeft">
<div class="ni_block_topbar_item" data-neo-b="${this._id}.select">
<div class="checkbox block-checkbox" title="${Craft.t('neo', 'Select')}" aria-label="${Craft.t('neo', 'Select')}"></div>
</div>
<div class="ni_block_topbar_item title">
<span class="blocktype" data-neo-b="${this._id}.select">${type.getName()}</span>
</div>
<div class="ni_block_topbar_item preview-container clip-text">
<span class="preview" data-neo-b="${this._id}.container.preview">&nbsp;</span>
</div>
</div>
<div class="ni_block_topbar_right" data-neo-b="${this._id}.container.topbarRight">
<div class="ni_block_topbar_item size-full tabs">`)

if (hasTabs || isParent) {
elementHtml.push(`
<div class="tabs_trigger" data-neo-b="${this._id}.button.toggler"></div>`)
}

if (tabs.length > 1) {
elementHtml.push(`
<div class="tabs_inner" data-neo-b="${this._id}.container.tabs">`)

for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i]
const tabName = tab.getName()
const tabUid = tab.getUid()
elementHtml.push(`
<a class="tab ${!i ? 'is-selected' : ''}" data-neo-b="${this._id}.button.tab" data-neo-b-info="${tabName}" data-neo-b-tabuid="${tabUid}">${tabName}</a>`)
}

elementHtml.push(`
</div>
<div>
<button type="button" role="button" title=${Craft.t('neo', 'Tabs')} aria-controls="${tabsMenuId}" aria-label="${tabsBtnLabel}" data-disclosure-trigger data-neo-b="${this._id}.button.tabs" class="tabs_btn menubtn">
${tabs[0].getName()}
</button>
<div id="${tabsMenuId}" class="neo_block_tabs-menu menu menu--disclosure">
<ul>`)

for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i]
const tabName = tab.getName()
const tabUid = tab.getUid()
elementHtml.push(`
<li>
<a${!i ? ' class="is-selected"' : ''} href="#" type="button" role="button" aria-label="${tabName}" data-neo-b="${this._id}.button.tab" data-neo-b-info="${tabName}" data-neo-b-tabuid="${tabUid}">${tabName}</a>
</li>`)
}

elementHtml.push(`
</ul>
</div>
</div>`)
}

elementHtml.push(`
</div>
<div class="ni_block_topbar_item hidden" data-neo-b="${this._id}.status">
<div class="status off" title="${Craft.t('neo', 'Disabled')}"></div>
</div>
<div class="ni_block_topbar_item block-settings">
<div>
<button class="btn settings icon menubtn" type="button" role="button" title="${Craft.t('neo', 'Actions')}" aria-controls="${actionMenuId}" aria-label="${actionBtnLabel}" data-disclosure-trigger data-neo-b="${this._id}.button.actions"></button>
<div id="${actionMenuId}" class="menu menu--disclosure" data-neo-b="${this._id}.container.menu">
<ul class="padded">`)

if (hasTabs || isParent) {
elementHtml.push(`
<li><a data-icon="collapse" data-action="collapse" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Collapse')}">${Craft.t('neo', 'Collapse')}</a></li>
<li class="hidden"><a data-icon="expand" data-action="expand" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Expand')}">${Craft.t('neo', 'Expand')}</a></li>`)
}

elementHtml.push(`
<li><a data-icon="disabled" data-action="disable" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Disable')}">${Craft.t('neo', 'Disable')}</a></li>
<li class="hidden"><a data-icon="enabled" data-action="enable" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Enable')}">${Craft.t('neo', 'Enable')}</a></li>
<li class="hidden"><a data-icon="uarr" data-action="moveUp" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Move up')}">${Craft.t('neo', 'Move up')}</a></li>
<li class="hidden"><a data-icon="darr" data-action="moveDown" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Move down')}">${Craft.t('neo', 'Move down')}</a></li>
</ul>
<hr>
<ul class="padded">
<li><a data-icon="plus" data-action="add" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Add block above')}">${Craft.t('neo', 'Add block above')}</a></li>
<li><a data-icon="field" data-action="copy" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Copy')}">${Craft.t('neo', 'Copy')}</a></li>
<li><a data-icon="brush" data-action="paste" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Paste')}">${Craft.t('neo', 'Paste')}</a></li>
<li><a data-icon="share" data-action="duplicate" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Clone')}">${Craft.t('neo', 'Clone')}</a></li>
</ul>`)

if (type.isDeletableByUser()) {
elementHtml.push(`
<hr>
<ul class="padded">
<li><a class="error" data-icon="remove" data-action="delete" href="#" type="button" role="button" aria-label="${Craft.t('neo', 'Delete')}">${Craft.t('neo', 'Delete')}</a></li>
</ul>`)
}

elementHtml.push(`
</div>
</div>
</div>
<div class="ni_block_topbar_item block-reorder">
<a class="move icon" title="${Craft.t('neo', 'Reorder')}" aria-label="${Craft.t('neo', 'Reorder')}" role="button" data-neo-b="${this._id}.button.move"></a>
</div>
</div>
</div>`)

if (hasTabs || isParent) {
elementHtml.push(`
<div class="ni_block_body" data-neo-b="${this._id}.container.body">`)

if (hasTabs) {
elementHtml.push(`
<div class="ni_block_content" data-neo-b="${this._id}.container.content">
${this.getHtml()}
</div>`)
}

if (isParent && this._renderOldChildBlocksContainer) {
elementHtml.push(`
<div class="ni_block_children" data-neo-b="${this._id}.container.children">
<div class="ni_blocks" data-neo-b="${this._id}.container.blocks">
</div>
<div data-neo-b="${this._id}.container.buttons" class="hidden"></div>
<div data-neo-b="${this._id}.container.childrenWarnings" class="hidden">
<p class="first warning with-icon">${Craft.t('neo', "This Neo field's maximum number of levels has been reached, so no child blocks can be added here.")}</p>
</div>
</div>`)
}

elementHtml.push(`
</div>`)
}

if (isParent) {
elementHtml.push(`
<div class="ni_block_collapsed-children" data-neo-b="${this._id}.container.collapsedChildren"></div>`)
}

elementHtml.push(`
<div data-neo="container.buttons"></div>`)

const $elementHtml = $(elementHtml.join(''))

if (showHandle) {
$('<div/>')
.addClass('ni_block_topbar_item handle')
.prop('data-neo-b', `${this._id}.container.handle`)
.append(Craft.ui.createCopyTextBtn({
id: `${baseInputId}-${type.getHandle()}-attribute`,
class: ['code', 'small', 'light'],
value: type.getHandle()
}))
.insertAfter($elementHtml.find('.ni_block_topbar_item.title'))
}

return $elementHtml
},

initUi (callInitUiElements = true) {
if (this._initialised) {
// Nothing to do here
return
}

if (callInitUiElements) {
this.$foot = $(this.getJs()).filter(_resourceFilter)
Garnish.$bod.append(this.$foot)
Craft.appendBodyHtml(this._bodyHtml)
Craft.appendHeadHtml(this._headHtml)
Craft.initUiElements(this.$contentContainer)
}

Expand Down Expand Up @@ -458,18 +254,14 @@ export default Garnish.Base.extend({
* @since 3.9.0
*/
getHtml () {
return this._html !== null
? this._html.replace(/__NEOBLOCK__/g, this._id)
: this._blockType.getHtml(this._id)
return this._blockHtml.replace(/__NEOBLOCK__/g, this._id)
},

/**
* @since 3.9.0
*/
getJs () {
return this._js !== null
? this._js.replace(/__NEOBLOCK__/g, this._id)
: this._blockType.getJs(this._id)
return this._bodyHtml.replace(/__NEOBLOCK__/g, this._id)
},

destroy () {
Expand All @@ -494,6 +286,15 @@ export default Garnish.Base.extend({
return this._id
},

/**
* @public
* @returns the block UUID
* @since 4.2.0
*/
getUuid () {
return this._uuid
},

/**
* @public
* @returns the ID of the duplicate block, or the ID of this block if it hasn't been duplicated
Expand Down
30 changes: 30 additions & 0 deletions src/assets/src/input/BlockType.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ export default Garnish.Base.extend({
getTopLevel () { return this._topLevel },
getTabNames () { return this._tabNames },

/**
* @deprecated in 4.2.0
*/
getTabs () { return this._tabs !== null ? Array.from(this._tabs) : null },

/**
* @deprecated in 4.2.0
*/
async loadTabs () {
if (this._tabs !== null) {
return
Expand Down Expand Up @@ -129,6 +136,29 @@ export default Garnish.Base.extend({
}
},

/**
* @since 4.2.0
*/
async newBlock () {
NS.enter(this._field.getNamespace())
const data = {
namespace: NS.toFieldName(),
fieldId: this._field?.getId(),
siteId: this._field?.getSiteId(),
blocks: [{
collapsed: false,
enabled: true,
level: 1,
ownerId: this._field?.getOwnerId(),
type: this._id
}]
}
NS.leave()
const response = await Craft.sendActionRequest('POST', 'neo/input/render-blocks', { data })

return response.data.blocks[0]
},

getHtml (blockId = null) {
return this._replaceBlockIdPlaceholder(this._html, blockId)
},
Expand Down
Loading

0 comments on commit 195a81b

Please sign in to comment.