811 lines
29 KiB
JavaScript
811 lines
29 KiB
JavaScript
/**
|
||
* AKSRT Gutenberg Blocks
|
||
*
|
||
* Custom Gutenberg block extensions for the AKSRT theme.
|
||
* Provides 9 generic content blocks adapted from zibll.
|
||
*
|
||
* Blocks: tabs, tab, enlighter, quote, alert, buttons, collapse, feature, iframe
|
||
*
|
||
* @package AKSRT
|
||
*/
|
||
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.wp || !window.wp.blocks) return;
|
||
|
||
var el = wp.element.createElement;
|
||
var Fragment = wp.element.Fragment;
|
||
var RawHTML = wp.element.RawHTML;
|
||
var registerBlockTypeRaw = wp.blocks.registerBlockType;
|
||
var blockEditor = wp.blockEditor || wp.editor || {};
|
||
var InspectorControls = blockEditor.InspectorControls || null;
|
||
var InnerBlocks = blockEditor.InnerBlocks || null;
|
||
var RichText = blockEditor.RichText || null;
|
||
var MediaUpload = blockEditor.MediaUpload || null;
|
||
var ColorPicker = wp.components.ColorPicker;
|
||
var PanelBody = wp.components.PanelBody;
|
||
var SelectControl = wp.components.SelectControl;
|
||
var TextControl = wp.components.TextControl;
|
||
var ToggleControl = wp.components.ToggleControl;
|
||
var RangeControl = wp.components.RangeControl;
|
||
var Notice = wp.components.Notice;
|
||
var useSelect = window.wp.data && window.wp.data.useSelect;
|
||
|
||
if (!registerBlockTypeRaw || !el) return;
|
||
|
||
// ── Helper: create element shorthand ──
|
||
function h(tag, props, children) {
|
||
var args = [tag, props];
|
||
if (children !== undefined) {
|
||
if (Array.isArray(children)) {
|
||
args = args.concat(children);
|
||
} else {
|
||
args.push(children);
|
||
}
|
||
}
|
||
return el.apply(null, args);
|
||
}
|
||
|
||
// ── Helper: InspectorControls wrapper ──
|
||
function withInspector(title, children) {
|
||
if (!InspectorControls) return null;
|
||
return h(InspectorControls, { key: 'inspector' },
|
||
h(PanelBody, { title: title }, children)
|
||
);
|
||
}
|
||
|
||
var CATEGORY = 'aksrt-blocks';
|
||
var BLOCK_ICON = h('svg', { viewBox: '0 0 24 24', width: 20, height: 20 },
|
||
h('path', { d: 'M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5', fill: 'none', stroke: 'currentColor', strokeWidth: 2 })
|
||
);
|
||
|
||
// Register category BEFORE any registerBlockType (filter-after-register is too late).
|
||
if (window.wp.hooks && window.wp.hooks.addFilter) {
|
||
wp.hooks.addFilter('blocks.getBlockCategories', 'aksrt/block-categories', function (categories) {
|
||
var list = Array.isArray(categories) ? categories.slice() : [];
|
||
var hasAksrt = list.some(function (c) { return c && c.slug === CATEGORY; });
|
||
if (hasAksrt) return list;
|
||
list.push({
|
||
slug: CATEGORY,
|
||
title: 'AKSRT',
|
||
icon: null,
|
||
});
|
||
return list;
|
||
});
|
||
}
|
||
|
||
// WP 6.9+ expects apiVersion 3 for iframe editor compatibility.
|
||
function registerBlockType(name, settings) {
|
||
settings = settings || {};
|
||
if (settings.apiVersion == null) {
|
||
settings.apiVersion = 3;
|
||
}
|
||
if (!settings.category) {
|
||
settings.category = CATEGORY;
|
||
}
|
||
return registerBlockTypeRaw(name, settings);
|
||
}
|
||
|
||
// ================================================================
|
||
// 1. Tabs Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/tabs', {
|
||
title: '标签页',
|
||
icon: BLOCK_ICON,
|
||
category: CATEGORY,
|
||
description: '可切换的标签页内容容器',
|
||
attributes: {
|
||
style: { type: 'string', default: 'default' },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('标签页设置',
|
||
h(SelectControl, {
|
||
label: '样式',
|
||
value: props.attributes.style,
|
||
options: [
|
||
{ label: '默认', value: 'default' },
|
||
{ label: '线条', value: 'line' },
|
||
{ label: '卡片', value: 'card' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ style: v }); }
|
||
})
|
||
),
|
||
h('div', { className: 'aksrt-block-tabs aksrt-block-tabs--' + props.attributes.style },
|
||
h(InnerBlocks, {
|
||
allowedBlocks: ['aksrt/tab'],
|
||
template: [['aksrt/tab', { title: '标签 1' }], ['aksrt/tab', { title: '标签 2' }]],
|
||
})
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('div', { className: 'aksrt-block-tabs aksrt-block-tabs--' + props.attributes.style },
|
||
h(InnerBlocks.Content, null)
|
||
);
|
||
},
|
||
});
|
||
|
||
registerBlockType('aksrt/tab', {
|
||
title: '标签页项',
|
||
icon: BLOCK_ICON,
|
||
category: CATEGORY,
|
||
parent: ['aksrt/tabs'],
|
||
attributes: {
|
||
title: { type: 'string', default: '标签' },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
h(TextControl, {
|
||
label: '标签标题',
|
||
value: props.attributes.title,
|
||
onChange: function (v) { props.setAttributes({ title: v }); }
|
||
}),
|
||
h('div', { className: 'aksrt-block-tab__content' },
|
||
h(InnerBlocks, null)
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('div', { className: 'aksrt-block-tab', 'data-title': props.attributes.title },
|
||
h(InnerBlocks.Content, null)
|
||
);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 2. EnlighterJS Code Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/enlighter', {
|
||
title: '代码高亮',
|
||
icon: 'editor-code',
|
||
category: CATEGORY,
|
||
description: '带语法高亮的代码块(EnlighterJS)',
|
||
attributes: {
|
||
code: { type: 'string', default: '' },
|
||
language: { type: 'string', default: 'javascript' },
|
||
theme: { type: 'string', default: '' },
|
||
linenumbers: { type: 'boolean', default: true },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('代码块设置',
|
||
h(SelectControl, {
|
||
label: '语言',
|
||
value: props.attributes.language,
|
||
options: [
|
||
{ label: 'JavaScript', value: 'javascript' },
|
||
{ label: 'TypeScript', value: 'typescript' },
|
||
{ label: 'PHP', value: 'php' },
|
||
{ label: 'Python', value: 'python' },
|
||
{ label: 'Java', value: 'java' },
|
||
{ label: 'C/C++', value: 'cpp' },
|
||
{ label: 'C#', value: 'csharp' },
|
||
{ label: 'Go', value: 'go' },
|
||
{ label: 'Rust', value: 'rust' },
|
||
{ label: 'Ruby', value: 'ruby' },
|
||
{ label: 'Swift', value: 'swift' },
|
||
{ label: 'Kotlin', value: 'kotlin' },
|
||
{ label: 'HTML', value: 'html' },
|
||
{ label: 'CSS/SCSS', value: 'css' },
|
||
{ label: 'SQL', value: 'sql' },
|
||
{ label: 'Shell/Bash', value: 'shell' },
|
||
{ label: 'JSON', value: 'json' },
|
||
{ label: 'YAML', value: 'yaml' },
|
||
{ label: 'Markdown', value: 'markdown' },
|
||
{ label: 'Docker', value: 'dockerfile' },
|
||
{ label: 'Diff', value: 'diff' },
|
||
{ label: 'XML', value: 'xml' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ language: v }); }
|
||
}),
|
||
h(ToggleControl, {
|
||
label: '显示行号',
|
||
checked: props.attributes.linenumbers,
|
||
onChange: function (v) { props.setAttributes({ linenumbers: v }); }
|
||
})
|
||
),
|
||
h('div', { className: 'aksrt-block-enlighter' },
|
||
h('div', { className: 'aksrt-block-enlighter__header' },
|
||
h('span', { className: 'aksrt-block-enlighter__lang' }, props.attributes.language)
|
||
),
|
||
h('textarea', {
|
||
className: 'aksrt-block-enlighter__code',
|
||
value: props.attributes.code,
|
||
onChange: function (e) { props.setAttributes({ code: e.target.value }); },
|
||
placeholder: '在此输入代码...',
|
||
rows: 10,
|
||
spellCheck: false,
|
||
})
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
var attrs = {
|
||
className: 'gl',
|
||
'data-enlighter-language': props.attributes.language,
|
||
};
|
||
if (props.attributes.theme) attrs['data-enlighter-theme'] = props.attributes.theme;
|
||
if (!props.attributes.linenumbers) attrs['data-enlighter-linenumbers'] = 'false';
|
||
|
||
return h('pre', { className: 'enlighter-pre' },
|
||
h('code', attrs, props.attributes.code)
|
||
);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 3. Quote Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/quote-styled', {
|
||
title: '样式引用',
|
||
icon: 'editor-quote',
|
||
category: CATEGORY,
|
||
description: '带颜色样式的引用块',
|
||
attributes: {
|
||
content: { type: 'string', default: '' },
|
||
style: { type: 'string', default: 'gray' },
|
||
cite: { type: 'string', default: '' },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('引用设置',
|
||
h(SelectControl, {
|
||
label: '样式',
|
||
value: props.attributes.style,
|
||
options: [
|
||
{ label: '灰色', value: 'gray' },
|
||
{ label: '蓝色', value: 'blue' },
|
||
{ label: '绿色', value: 'green' },
|
||
{ label: '红色', value: 'red' },
|
||
{ label: '黄色', value: 'yellow' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ style: v }); }
|
||
}),
|
||
h(TextControl, {
|
||
label: '来源',
|
||
value: props.attributes.cite,
|
||
onChange: function (v) { props.setAttributes({ cite: v }); }
|
||
})
|
||
),
|
||
h('blockquote', { className: 'aksrt-block-quote aksrt-block-quote--' + props.attributes.style },
|
||
h(RichText, {
|
||
tagName: 'p',
|
||
value: props.attributes.content,
|
||
onChange: function (v) { props.setAttributes({ content: v }); },
|
||
placeholder: '输入引用内容...',
|
||
}),
|
||
props.attributes.cite ? h('cite', null, props.attributes.cite) : null
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('blockquote', { className: 'aksrt-block-quote aksrt-block-quote--' + props.attributes.style },
|
||
h(RichText.Content, { tagName: 'p', value: props.attributes.content }),
|
||
props.attributes.cite ? h('cite', null, props.attributes.cite) : null
|
||
);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 4. Alert Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/alert', {
|
||
title: '提示框',
|
||
icon: 'warning',
|
||
category: CATEGORY,
|
||
description: '通知/警告/成功/信息提示框',
|
||
attributes: {
|
||
content: { type: 'string', default: '' },
|
||
type: { type: 'string', default: 'info' },
|
||
dismissible: { type: 'boolean', default: false },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('提示框设置',
|
||
h(SelectControl, {
|
||
label: '类型',
|
||
value: props.attributes.type,
|
||
options: [
|
||
{ label: '信息', value: 'info' },
|
||
{ label: '成功', value: 'success' },
|
||
{ label: '警告', value: 'warning' },
|
||
{ label: '错误', value: 'error' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ type: v }); }
|
||
}),
|
||
h(ToggleControl, {
|
||
label: '可关闭',
|
||
checked: props.attributes.dismissible,
|
||
onChange: function (v) { props.setAttributes({ dismissible: v }); }
|
||
})
|
||
),
|
||
h('div', {
|
||
className: 'aksrt-block-alert aksrt-block-alert--' + props.attributes.type +
|
||
(props.attributes.dismissible ? ' aksrt-block-alert--dismissible' : '')
|
||
},
|
||
h(RichText, {
|
||
tagName: 'p',
|
||
value: props.attributes.content,
|
||
onChange: function (v) { props.setAttributes({ content: v }); },
|
||
placeholder: '输入提示内容...',
|
||
})
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
var cls = 'aksrt-block-alert aksrt-block-alert--' + props.attributes.type;
|
||
if (props.attributes.dismissible) cls += ' aksrt-block-alert--dismissible';
|
||
return h('div', { className: cls, role: 'alert' },
|
||
h(RichText.Content, { tagName: 'p', value: props.attributes.content }),
|
||
props.attributes.dismissible ? h('button', { type: 'button', className: 'aksrt-block-alert__close', 'aria-label': '关闭' }, '×') : null
|
||
);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 5. Buttons Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/buttons', {
|
||
title: '按钮组',
|
||
icon: 'button',
|
||
category: CATEGORY,
|
||
description: '多个按钮组合',
|
||
attributes: {
|
||
align: { type: 'string', default: 'left' },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('按钮组设置',
|
||
h(SelectControl, {
|
||
label: '对齐',
|
||
value: props.attributes.align,
|
||
options: [
|
||
{ label: '左对齐', value: 'left' },
|
||
{ label: '居中', value: 'center' },
|
||
{ label: '右对齐', value: 'right' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ align: v }); }
|
||
})
|
||
),
|
||
h('div', { className: 'aksrt-block-buttons aksrt-block-buttons--' + props.attributes.align },
|
||
h(InnerBlocks, {
|
||
allowedBlocks: ['aksrt/button'],
|
||
template: [['aksrt/button', { text: '按钮', style: 'primary' }]],
|
||
})
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('div', { className: 'aksrt-block-buttons aksrt-block-buttons--' + props.attributes.align },
|
||
h(InnerBlocks.Content, null)
|
||
);
|
||
},
|
||
});
|
||
|
||
registerBlockType('aksrt/button', {
|
||
title: '按钮',
|
||
icon: 'button',
|
||
category: CATEGORY,
|
||
parent: ['aksrt/buttons'],
|
||
attributes: {
|
||
text: { type: 'string', default: '按钮' },
|
||
url: { type: 'string', default: '' },
|
||
style: { type: 'string', default: 'primary' },
|
||
size: { type: 'string', default: 'md' },
|
||
newTab: { type: 'boolean', default: false },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('按钮设置',
|
||
h(TextControl, {
|
||
label: '链接',
|
||
value: props.attributes.url,
|
||
onChange: function (v) { props.setAttributes({ url: v }); }
|
||
}),
|
||
h(SelectControl, {
|
||
label: '样式',
|
||
value: props.attributes.style,
|
||
options: [
|
||
{ label: '主要', value: 'primary' },
|
||
{ label: '次要', value: 'secondary' },
|
||
{ label: '轮廓', value: 'outline' },
|
||
{ label: '幽灵', value: 'ghost' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ style: v }); }
|
||
}),
|
||
h(SelectControl, {
|
||
label: '大小',
|
||
value: props.attributes.size,
|
||
options: [
|
||
{ label: '小', value: 'sm' },
|
||
{ label: '中', value: 'md' },
|
||
{ label: '大', value: 'lg' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ size: v }); }
|
||
}),
|
||
h(ToggleControl, {
|
||
label: '新窗口打开',
|
||
checked: props.attributes.newTab,
|
||
onChange: function (v) { props.setAttributes({ newTab: v }); }
|
||
})
|
||
),
|
||
h(RichText, {
|
||
tagName: 'span',
|
||
className: 'aksrt-block-btn aksrt-block-btn--' + props.attributes.style + ' aksrt-block-btn--' + props.attributes.size,
|
||
value: props.attributes.text,
|
||
onChange: function (v) { props.setAttributes({ text: v }); },
|
||
placeholder: '按钮文字...',
|
||
withoutInteractiveFormatting: true,
|
||
})
|
||
);
|
||
},
|
||
save: function (props) {
|
||
var linkProps = {
|
||
className: 'aksrt-block-btn aksrt-block-btn--' + props.attributes.style + ' aksrt-block-btn--' + props.attributes.size,
|
||
};
|
||
if (props.attributes.url) {
|
||
linkProps.href = props.attributes.url;
|
||
if (props.attributes.newTab) {
|
||
linkProps.target = '_blank';
|
||
linkProps.rel = 'noopener noreferrer';
|
||
}
|
||
return h('a', linkProps, props.attributes.text);
|
||
}
|
||
return h('span', linkProps, props.attributes.text);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 6. Collapse Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/collapse', {
|
||
title: '折叠面板',
|
||
icon: 'arrow-down',
|
||
category: CATEGORY,
|
||
description: '可展开/收起的内容面板',
|
||
attributes: {
|
||
title: { type: 'string', default: '点击展开' },
|
||
open: { type: 'boolean', default: false },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('折叠面板设置',
|
||
h(ToggleControl, {
|
||
label: '默认展开',
|
||
checked: props.attributes.open,
|
||
onChange: function (v) { props.setAttributes({ open: v }); }
|
||
})
|
||
),
|
||
h('div', { className: 'aksrt-block-collapse' },
|
||
h('div', { className: 'aksrt-block-collapse__header' },
|
||
h(RichText, {
|
||
tagName: 'span',
|
||
className: 'aksrt-block-collapse__title',
|
||
value: props.attributes.title,
|
||
onChange: function (v) { props.setAttributes({ title: v }); },
|
||
placeholder: '面板标题...',
|
||
}),
|
||
h('span', { className: 'aksrt-block-collapse__arrow' }, '▼')
|
||
),
|
||
h('div', { className: 'aksrt-block-collapse__body' },
|
||
h(InnerBlocks, null)
|
||
)
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('details', {
|
||
className: 'aksrt-block-collapse',
|
||
open: props.attributes.open ? true : undefined,
|
||
},
|
||
h('summary', { className: 'aksrt-block-collapse__header' },
|
||
h('span', { className: 'aksrt-block-collapse__title' }, props.attributes.title)
|
||
),
|
||
h('div', { className: 'aksrt-block-collapse__body' },
|
||
h(InnerBlocks.Content, null)
|
||
)
|
||
);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 7. Feature Grid Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/feature', {
|
||
title: '特性网格',
|
||
icon: 'grid-view',
|
||
category: CATEGORY,
|
||
description: '图标+标题+描述的特性展示网格',
|
||
attributes: {
|
||
columns: { type: 'number', default: 3 },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('特性网格设置',
|
||
h(RangeControl, {
|
||
label: '列数',
|
||
value: props.attributes.columns,
|
||
min: 2,
|
||
max: 4,
|
||
onChange: function (v) { props.setAttributes({ columns: v }); }
|
||
})
|
||
),
|
||
h('div', { className: 'aksrt-block-feature aksrt-block-feature--cols-' + props.attributes.columns },
|
||
h(InnerBlocks, {
|
||
allowedBlocks: ['aksrt/feature-item'],
|
||
template: [
|
||
['aksrt/feature-item', { icon: 'fa fas fa-star', title: '特性一', desc: '特性描述' }],
|
||
['aksrt/feature-item', { icon: 'fa fas fa-heart', title: '特性二', desc: '特性描述' }],
|
||
['aksrt/feature-item', { icon: 'fa fas fa-bolt', title: '特性三', desc: '特性描述' }],
|
||
],
|
||
})
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('div', { className: 'aksrt-block-feature aksrt-block-feature--cols-' + props.attributes.columns },
|
||
h(InnerBlocks.Content, null)
|
||
);
|
||
},
|
||
});
|
||
|
||
registerBlockType('aksrt/feature-item', {
|
||
title: '特性项',
|
||
icon: 'star-filled',
|
||
category: CATEGORY,
|
||
parent: ['aksrt/feature'],
|
||
attributes: {
|
||
icon: { type: 'string', default: 'fa fas fa-star' },
|
||
title: { type: 'string', default: '' },
|
||
desc: { type: 'string', default: '' },
|
||
},
|
||
edit: function (props) {
|
||
return h('div', { className: 'aksrt-block-feature__item' },
|
||
h(TextControl, {
|
||
label: '图标类名',
|
||
value: props.attributes.icon,
|
||
onChange: function (v) { props.setAttributes({ icon: v }); }
|
||
}),
|
||
h('i', { className: props.attributes.icon + ' aksrt-block-feature__icon' }),
|
||
h(RichText, {
|
||
tagName: 'h3',
|
||
className: 'aksrt-block-feature__title',
|
||
value: props.attributes.title,
|
||
onChange: function (v) { props.setAttributes({ title: v }); },
|
||
placeholder: '标题...',
|
||
}),
|
||
h(RichText, {
|
||
tagName: 'p',
|
||
className: 'aksrt-block-feature__desc',
|
||
value: props.attributes.desc,
|
||
onChange: function (v) { props.setAttributes({ desc: v }); },
|
||
placeholder: '描述...',
|
||
})
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('div', { className: 'aksrt-block-feature__item' },
|
||
h('i', { className: props.attributes.icon + ' aksrt-block-feature__icon' }),
|
||
h('h3', { className: 'aksrt-block-feature__title' }, props.attributes.title),
|
||
h('p', { className: 'aksrt-block-feature__desc' }, props.attributes.desc)
|
||
);
|
||
},
|
||
});
|
||
|
||
// ================================================================
|
||
// 8. Responsive Iframe Block
|
||
// ================================================================
|
||
|
||
registerBlockType('aksrt/iframe', {
|
||
title: '响应式嵌入',
|
||
icon: 'video-alt3',
|
||
category: CATEGORY,
|
||
description: '响应式 iframe 嵌入(视频、地图等)',
|
||
attributes: {
|
||
src: { type: 'string', default: '' },
|
||
ratio: { type: 'string', default: '16:9' },
|
||
title: { type: 'string', default: '' },
|
||
},
|
||
edit: function (props) {
|
||
return h(Fragment, null,
|
||
withInspector('嵌入设置',
|
||
h(TextControl, {
|
||
label: 'URL',
|
||
value: props.attributes.src,
|
||
onChange: function (v) { props.setAttributes({ src: v }); }
|
||
}),
|
||
h(SelectControl, {
|
||
label: '宽高比',
|
||
value: props.attributes.ratio,
|
||
options: [
|
||
{ label: '16:9', value: '16:9' },
|
||
{ label: '4:3', value: '4:3' },
|
||
{ label: '1:1', value: '1:1' },
|
||
{ label: '自适应', value: 'auto' },
|
||
],
|
||
onChange: function (v) { props.setAttributes({ ratio: v }); }
|
||
}),
|
||
h(TextControl, {
|
||
label: '标题',
|
||
value: props.attributes.title,
|
||
onChange: function (v) { props.setAttributes({ title: v }); }
|
||
})
|
||
),
|
||
h('div', { className: 'aksrt-block-iframe aksrt-block-iframe--' + props.attributes.ratio.replace(':', '-') },
|
||
props.attributes.src
|
||
? h('iframe', { src: props.attributes.src, title: props.attributes.title || '嵌入内容', allowfullscreen: true })
|
||
: h('div', { className: 'aksrt-block-iframe__placeholder' }, '请输入嵌入 URL')
|
||
)
|
||
);
|
||
},
|
||
save: function (props) {
|
||
return h('div', { className: 'aksrt-block-iframe aksrt-block-iframe--' + props.attributes.ratio.replace(':', '-') },
|
||
h('iframe', {
|
||
src: props.attributes.src,
|
||
title: props.attributes.title || '嵌入内容',
|
||
allowfullscreen: true,
|
||
loading: 'lazy',
|
||
})
|
||
);
|
||
},
|
||
});
|
||
|
||
// ── Carousel Block (Gallery → Swiper) ──
|
||
// MediaUpload lives on wp.blockEditor; skip registration if unavailable.
|
||
if (MediaUpload) {
|
||
registerBlockType('aksrt/carousel', {
|
||
title: '轮播图',
|
||
icon: 'images-alt2',
|
||
category: CATEGORY,
|
||
description: '图片轮播/幻灯片(基于 Swiper)',
|
||
attributes: {
|
||
images: { type: 'array', default: [] },
|
||
autoplay: { type: 'boolean', default: true },
|
||
delay: { type: 'number', default: 3000 },
|
||
effect: { type: 'string', default: 'slide' },
|
||
},
|
||
edit: function (props) {
|
||
var attrs = props.attributes;
|
||
var setAttributes = props.setAttributes;
|
||
|
||
var onSelectImages = function (media) {
|
||
setAttributes({
|
||
images: media.map(function (m) {
|
||
return { url: m.url, alt: m.alt || '', id: m.id };
|
||
}),
|
||
});
|
||
};
|
||
|
||
var inspector = h(InspectorControls, { key: 'insp' },
|
||
h(PanelBody, { title: '轮播设置' },
|
||
h(ToggleControl, {
|
||
label: '自动播放',
|
||
checked: attrs.autoplay,
|
||
onChange: function (v) { setAttributes({ autoplay: v }); },
|
||
}),
|
||
h(RangeControl, {
|
||
label: '切换间隔(毫秒)',
|
||
value: attrs.delay,
|
||
min: 1000,
|
||
max: 10000,
|
||
step: 500,
|
||
onChange: function (v) { setAttributes({ delay: v }); },
|
||
}),
|
||
h(SelectControl, {
|
||
label: '切换效果',
|
||
value: attrs.effect,
|
||
options: [
|
||
{ label: '滑动', value: 'slide' },
|
||
{ label: '淡入淡出', value: 'fade' },
|
||
{ label: '立方体', value: 'cube' },
|
||
],
|
||
onChange: function (v) { setAttributes({ effect: v }); },
|
||
})
|
||
)
|
||
);
|
||
|
||
var preview = attrs.images.length > 0
|
||
? h('div', { className: 'aksrt-carousel-preview', style: { display: 'flex', gap: '8px', flexWrap: 'wrap' } },
|
||
attrs.images.map(function (img, i) {
|
||
return h('img', {
|
||
key: i,
|
||
src: img.url,
|
||
alt: img.alt,
|
||
style: { width: '120px', height: '80px', objectFit: 'cover', borderRadius: '6px' },
|
||
});
|
||
})
|
||
)
|
||
: h('p', { style: { color: '#999', textAlign: 'center', padding: '20px' } }, '请选择图片');
|
||
|
||
return h(Fragment, null,
|
||
inspector,
|
||
h('div', { className: 'aksrt-carousel-block' },
|
||
preview,
|
||
h(MediaUpload, {
|
||
onSelect: onSelectImages,
|
||
allowedTypes: ['image'],
|
||
multiple: true,
|
||
value: attrs.images.map(function (img) { return img.id; }),
|
||
render: function (obj) {
|
||
return h(wp.components.Button, {
|
||
onClick: obj.open,
|
||
variant: 'secondary',
|
||
style: { marginTop: '8px' },
|
||
}, attrs.images.length ? '重新选择图片' : '选择图片');
|
||
},
|
||
})
|
||
)
|
||
);
|
||
},
|
||
save: function () {
|
||
// Dynamic block — rendered via PHP
|
||
return null;
|
||
},
|
||
});
|
||
}
|
||
|
||
// ================================================================
|
||
// 9. Paid Fragment Block
|
||
// ================================================================
|
||
|
||
function usePaidReadingConfigured() {
|
||
if (!useSelect) return true;
|
||
return useSelect(function (select) {
|
||
var editor = select('core/editor');
|
||
if (!editor || !editor.getEditedPostAttribute) return true;
|
||
|
||
var meta = editor.getEditedPostAttribute('meta') || {};
|
||
var pay = meta._aksrt_pay || {};
|
||
if (pay.pay_type !== 'reading') return false;
|
||
return pay.pay_modo === 'points'
|
||
? Number(pay.points_price || 0) > 0
|
||
: Number(pay.price || 0) > 0;
|
||
}, []);
|
||
}
|
||
|
||
registerBlockType('aksrt/paid-fragment', {
|
||
title: '付费可见',
|
||
icon: 'lock',
|
||
category: CATEGORY,
|
||
description: '仅购买文章付费阅读后可见的片段。',
|
||
attributes: {
|
||
content: { type: 'string', default: '' },
|
||
},
|
||
edit: function (props) {
|
||
var configured = usePaidReadingConfigured();
|
||
return h('div', { className: 'aksrt-paid-fragment-block' },
|
||
!configured && Notice ? h(Notice, { status: 'warning', isDismissible: false },
|
||
'请在“付费内容设置”中选择“付费阅读”并设置有效价格;未配置时此片段会公开显示。'
|
||
) : null,
|
||
h('div', { className: 'aksrt-paid-fragment-block__label' },
|
||
h('span', { className: 'dashicons dashicons-lock', 'aria-hidden': true }),
|
||
'付费可见片段'
|
||
),
|
||
h(RichText, {
|
||
tagName: 'div',
|
||
className: 'aksrt-paid-fragment-block__content',
|
||
value: props.attributes.content,
|
||
onChange: function (value) { props.setAttributes({ content: value }); },
|
||
placeholder: '输入购买后可见的内容…',
|
||
})
|
||
);
|
||
},
|
||
save: function (props) {
|
||
// Save a single canonical shortcode so hand-written and block-authored
|
||
// content use the identical server-side rendering path.
|
||
var shortcode = '[hidecontent type="payshow"]' + props.attributes.content + '[/hidecontent]';
|
||
return RawHTML ? h(RawHTML, null, shortcode) : shortcode;
|
||
},
|
||
});
|
||
|
||
})();
|