PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / post-meta / index.js

index.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/post-meta/index.js

362 lines 24.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* ====================================================
2 Post Meta Bar Block — editor (index.js)
3 Block: blockenberg/post-meta
4 ==================================================== */
5 ( function () {
6 var el = wp.element.createElement;
7 var Fragment = wp.element.Fragment;
8 var useState = wp.element.useState;
9 var registerBlockType = wp.blocks.registerBlockType;
10 var InspectorControls = wp.blockEditor.InspectorControls;
11 var useBlockProps = wp.blockEditor.useBlockProps;
12 var PanelBody = wp.components.PanelBody;
13 var PanelColorSettings = wp.blockEditor.PanelColorSettings;
14 var RangeControl = wp.components.RangeControl;
15 var SelectControl = wp.components.SelectControl;
16 var ToggleControl = wp.components.ToggleControl;
17 var TextControl = wp.components.TextControl;
18 var Button = wp.components.Button;
19 var __ = wp.i18n.__;
20
21 var _tc; function getTypoControl() { return _tc || (_tc = window.bkbgTypographyControl); }
22 var _tv; function getTypoCssVars() { return _tv || (_tv = window.bkbgTypoCssVars); }
23
24 var IP = function () { return window.bkbgIconPicker; };
25
26 function renderIconPart(type, charVal, dashicon, imageUrl, dashiconColor, forSave) {
27 if (!type || type === 'custom-char') return charVal;
28 var fn = forSave ? IP().buildSaveIcon : IP().buildEditorIcon;
29 return fn(type, charVal, dashicon, imageUrl, dashiconColor);
30 }
31
32 function buildWrapStyle(a) {
33 var _tvFn = getTypoCssVars();
34 var s = {
35 '--bkbg-pm-avatar-size': a.avatarSize + 'px',
36 '--bkbg-pm-gap': a.gap + 'px',
37 '--bkbg-pm-pad-v': a.paddingV + 'px',
38 '--bkbg-pm-pad-h': a.paddingH + 'px',
39 '--bkbg-pm-radius': a.borderRadius + 'px',
40 '--bkbg-pm-tag-pv': a.tagPaddingV + 'px',
41 '--bkbg-pm-tag-ph': a.tagPaddingH + 'px',
42 '--bkbg-pm-tag-r': a.tagRadius + 'px',
43 '--bkbg-pm-text': a.textColor,
44 '--bkbg-pm-link': a.linkColor,
45 '--bkbg-pm-link-hover': a.linkColorHover,
46 '--bkbg-pm-sep-color': a.separatorColor,
47 '--bkbg-pm-bg': a.bgColor,
48 '--bkbg-pm-avatar-border': a.avatarBorderColor,
49 '--bkbg-pm-tag-bg': a.tagBg,
50 '--bkbg-pm-tag-color': a.tagColor,
51 };
52 Object.assign(s, _tvFn(a.textTypo, '--bkbg-pm-tx-'));
53 return s;
54 }
55
56 var SEP_OPTIONS = [
57 { label: '• Bullet', value: '' },
58 { label: '| Pipe', value: '|' },
59 { label: '— Em dash', value: '' },
60 { label: '/ Slash', value: '/' },
61 { label: 'Custom', value: '__custom__' },
62 ];
63
64 /* ── Render a single meta item ── */
65 function renderItem(key, a, editorMode) {
66 var sep = a.separator === '__custom__' ? (a.customSeparator || '') : a.separator;
67 var sepEl = el('span', { className: 'bkbg-pm-sep', 'aria-hidden': 'true' }, sep);
68
69 switch (key) {
70 case 'avatar':
71 if (!a.showAvatar || !a.showAuthor) return null;
72 return el('span', { key: 'avatar', className: 'bkbg-pm-item bkbg-pm-avatar' },
73 a.authorAvatarUrl
74 ? el('img', { src: a.authorAvatarUrl, alt: a.authorName, className: 'bkbg-pm-avatar-img' })
75 : el('span', { className: 'bkbg-pm-avatar-placeholder' }, (a.authorName || 'A').charAt(0).toUpperCase())
76 );
77 case 'author':
78 if (!a.showAuthor) return null;
79 return el('span', { key: 'author', className: 'bkbg-pm-item bkbg-pm-author' },
80 a.authorLabel ? el('span', { className: 'bkbg-pm-label' }, a.authorLabel + ' ') : null,
81 el('a', { className: 'bkbg-pm-author-link', href: a.authorUrl, onClick: editorMode ? function(e){ e.preventDefault(); } : undefined }, a.authorName)
82 );
83 case 'date':
84 if (!a.showDate) return null;
85 var dateIcon = renderIconPart(a.dateLabelType, a.dateLabel, a.dateLabelDashicon, a.dateLabelImageUrl, a.dateLabelDashiconColor, !editorMode);
86 return el('span', { key: 'date', className: 'bkbg-pm-item bkbg-pm-date' },
87 dateIcon ? el('span', { className: 'bkbg-pm-icon' }, dateIcon, ' ') : null,
88 el('time', { className: 'bkbg-pm-date-value' }, a.dateValue)
89 );
90 case 'readingTime':
91 if (!a.showReadingTime) return null;
92 var rtIcon = renderIconPart(a.readingTimeLabelType, a.readingTimeLabel, a.readingTimeLabelDashicon, a.readingTimeLabelImageUrl, a.readingTimeLabelDashiconColor, !editorMode);
93 return el('span', { key: 'readingTime', className: 'bkbg-pm-item bkbg-pm-reading-time' },
94 rtIcon ? el('span', { className: 'bkbg-pm-icon' }, rtIcon, ' ') : null,
95 el('span', null, a.readingTimeValue)
96 );
97 case 'categories':
98 if (!a.showCategories || !a.categories.length) return null;
99 var catIcon = renderIconPart(a.categoriesLabelType, a.categoriesLabel, a.categoriesLabelDashicon, a.categoriesLabelImageUrl, a.categoriesLabelDashiconColor, !editorMode);
100 return el('span', { key: 'categories', className: 'bkbg-pm-item bkbg-pm-categories' },
101 catIcon ? el('span', { className: 'bkbg-pm-icon' }, catIcon, ' ') : null,
102 a.categories.map(function(cat, i) {
103 return el(Fragment, { key: i },
104 el('a', { className: 'bkbg-pm-tag', href: cat.url, onClick: editorMode ? function(e){ e.preventDefault(); } : undefined }, cat.label),
105 i < a.categories.length - 1 ? el('span', { className: 'bkbg-pm-tag-sep' }, ', ') : null
106 );
107 })
108 );
109 case 'tags':
110 if (!a.showTags || !a.tags.length) return null;
111 return el('span', { key: 'tags', className: 'bkbg-pm-item bkbg-pm-tags' },
112 a.tags.map(function(tag, i) {
113 return el('a', { key: i, className: 'bkbg-pm-tag bkbg-pm-tag--pill', href: tag.url, onClick: editorMode ? function(e){ e.preventDefault(); } : undefined }, '#' + tag.label);
114 })
115 );
116 case 'comments':
117 if (!a.showComments) return null;
118 var cmtIcon = renderIconPart(a.commentsLabelType, a.commentsLabel, a.commentsLabelDashicon, a.commentsLabelImageUrl, a.commentsLabelDashiconColor, !editorMode);
119 return el('span', { key: 'comments', className: 'bkbg-pm-item bkbg-pm-comments' },
120 cmtIcon ? el('span', { className: 'bkbg-pm-icon' }, cmtIcon, ' ') : null,
121 el('a', { className: 'bkbg-pm-comments-link', href: a.commentsUrl, onClick: editorMode ? function(e){ e.preventDefault(); } : undefined }, a.commentsCount)
122 );
123 default:
124 return null;
125 }
126 }
127
128 /* ── Build list of visible items with separators ── */
129 function buildItems(a, editorMode) {
130 var order = a.itemOrder || ['avatar', 'author', 'date', 'readingTime', 'categories', 'tags', 'comments'];
131 var sep = a.separator === '__custom__' ? (a.customSeparator || '') : a.separator;
132 var result = [];
133 var visibleCount = 0;
134
135 order.forEach(function(key) {
136 var rendered = renderItem(key, a, editorMode);
137 if (!rendered) return;
138 if (visibleCount > 0 && key !== 'avatar') {
139 result.push(el('span', { key: 'sep-' + key, className: 'bkbg-pm-sep', 'aria-hidden': 'true' }, sep));
140 }
141 result.push(rendered);
142 if (key !== 'avatar') visibleCount++;
143 });
144 return result;
145 }
146
147 /* ── Categories editor ── */
148 function CatsEditor(props) {
149 var cats = props.cats;
150 var onChange = props.onChange;
151 var addLabel = props.addLabel;
152 return el(Fragment, null,
153 cats.map(function(cat, i) {
154 return el('div', { key: i, style: { display: 'flex', gap: '6px', marginBottom: '4px', alignItems: 'flex-end' } },
155 el(TextControl, { label: i === 0 ? __('Label', 'blockenberg') : undefined, value: cat.label, onChange: function(v){ var n = cats.slice(); n[i] = Object.assign({}, cat, {label:v}); onChange(n); }, style: { flex: 2 } }),
156 el(TextControl, { label: i === 0 ? __('URL', 'blockenberg') : undefined, value: cat.url, onChange: function(v){ var n = cats.slice(); n[i] = Object.assign({}, cat, {url:v}); onChange(n); }, style: { flex: 2 } }),
157 el(Button, { icon: 'no-alt', isSmall: true, isDestructive: true, label: __('Remove', 'blockenberg'), onClick: function(){ onChange(cats.filter(function(_,j){ return j!==i; })); } })
158 );
159 }),
160 el(Button, { variant: 'secondary', isSmall: true, onClick: function(){ onChange(cats.concat([{label: 'Category', url: '#'}])); } }, addLabel)
161 );
162 }
163
164 /* ── REGISTER ── */
165 registerBlockType('blockenberg/post-meta', {
166 deprecated: [{
167 attributes: {
168 layout: { type: 'string', default: 'horizontal' },
169 separator: { type: 'string', default: '' },
170 customSeparator: { type: 'string', default: '' },
171 showAvatar: { type: 'boolean', default: true },
172 showAuthor: { type: 'boolean', default: true },
173 showDate: { type: 'boolean', default: true },
174 showReadingTime: { type: 'boolean', default: true },
175 showCategories: { type: 'boolean', default: true },
176 showTags: { type: 'boolean', default: false },
177 showComments: { type: 'boolean', default: true },
178 itemOrder: { type: 'array', default: ['avatar','author','date','readingTime','categories','tags','comments'] },
179 authorLabel: { type: 'string', default: 'By' },
180 authorName: { type: 'string', default: 'Jane Doe' },
181 authorUrl: { type: 'string', default: '#' },
182 authorAvatarUrl: { type: 'string', default: '' },
183 dateLabel: { type: 'string', default: '�
184 ' },
185 dateValue: { type: 'string', default: 'June 12, 2025' },
186 readingTimeLabel:{ type: 'string', default: '' },
187 readingTimeValue:{ type: 'string', default: '5 min read' },
188 categoriesLabel: { type: 'string', default: '🏷' },
189 categories: { type: 'array', default: [{label:'Design',url:'#'},{label:'Typography',url:'#'}] },
190 tags: { type: 'array', default: [{label:'ux',url:'#'},{label:'branding',url:'#'}] },
191 commentsLabel: { type: 'string', default: '💬' },
192 commentsCount: { type: 'string', default: '14 comments' },
193 commentsUrl: { type: 'string', default: '#' },
194 fontSize: { type: 'number', default: 14 },
195 fontWeight: { type: 'string', default: '400' },
196 avatarSize: { type: 'number', default: 32 },
197 gap: { type: 'number', default: 14 },
198 paddingV: { type: 'number', default: 0 },
199 paddingH: { type: 'number', default: 0 },
200 borderRadius: { type: 'number', default: 0 },
201 tagPaddingV: { type: 'number', default: 2 },
202 tagPaddingH: { type: 'number', default: 8 },
203 tagRadius: { type: 'number', default: 50 },
204 textColor: { type: 'string', default: '#64748b' },
205 linkColor: { type: 'string', default: '#6c3fb5' },
206 linkColorHover: { type: 'string', default: '#4f2d99' },
207 separatorColor: { type: 'string', default: '#cbd5e1' },
208 bgColor: { type: 'string', default: 'transparent' },
209 avatarBorderColor:{ type: 'string', default: '#e2e8f0' },
210 tagBg: { type: 'string', default: '#f1f5f9' },
211 tagColor: { type: 'string', default: '#475569' }
212 },
213 save: function (props) {
214 var a = props.attributes;
215 var oldWrapStyle = {
216 '--bkbg-pm-font': a.fontSize + 'px',
217 '--bkbg-pm-weight': a.fontWeight,
218 '--bkbg-pm-avatar-size': a.avatarSize + 'px',
219 '--bkbg-pm-gap': a.gap + 'px',
220 '--bkbg-pm-pad-v': a.paddingV + 'px',
221 '--bkbg-pm-pad-h': a.paddingH + 'px',
222 '--bkbg-pm-radius': a.borderRadius + 'px',
223 '--bkbg-pm-tag-pv': a.tagPaddingV + 'px',
224 '--bkbg-pm-tag-ph': a.tagPaddingH + 'px',
225 '--bkbg-pm-tag-r': a.tagRadius + 'px',
226 '--bkbg-pm-text': a.textColor,
227 '--bkbg-pm-link': a.linkColor,
228 '--bkbg-pm-link-hover': a.linkColorHover,
229 '--bkbg-pm-sep-color': a.separatorColor,
230 '--bkbg-pm-bg': a.bgColor,
231 '--bkbg-pm-avatar-border': a.avatarBorderColor,
232 '--bkbg-pm-tag-bg': a.tagBg,
233 '--bkbg-pm-tag-color': a.tagColor,
234 };
235 var blockProps = wp.blockEditor.useBlockProps.save({
236 className: 'bkbg-pm-wrap bkbg-pm-layout--' + a.layout,
237 style: oldWrapStyle
238 });
239 return el('div', blockProps,
240 el('div', { className: 'bkbg-pm-inner' },
241 buildItems(a, false)
242 )
243 );
244 }
245 }],
246 edit: function (props) {
247 var a = props.attributes;
248 var setAttr = props.setAttributes;
249 var style = buildWrapStyle(a);
250 var blockProps = useBlockProps({
251 className: 'bkbg-pm-wrap bkbg-pm-layout--' + a.layout,
252 style: style
253 });
254
255 var inspector = el(InspectorControls, null,
256 /* Visibility */
257 el(PanelBody, { title: __('Visible Items', 'blockenberg'), initialOpen: true },
258 el('p', { style: { color: '#64748b', fontSize: '12px' } }, __('Toggle which meta items appear in the bar.', 'blockenberg')),
259 el(ToggleControl, { label: __('Author avatar', 'blockenberg'), checked: a.showAvatar, onChange: function(v){ setAttr({ showAvatar: v }); } }),
260 el(ToggleControl, { label: __('Author name', 'blockenberg'), checked: a.showAuthor, onChange: function(v){ setAttr({ showAuthor: v }); } }),
261 el(ToggleControl, { label: __('Date', 'blockenberg'), checked: a.showDate, onChange: function(v){ setAttr({ showDate: v }); } }),
262 el(ToggleControl, { label: __('Reading time', 'blockenberg'), checked: a.showReadingTime, onChange: function(v){ setAttr({ showReadingTime: v }); } }),
263 el(ToggleControl, { label: __('Categories', 'blockenberg'), checked: a.showCategories, onChange: function(v){ setAttr({ showCategories: v }); } }),
264 el(ToggleControl, { label: __('Tags', 'blockenberg'), checked: a.showTags, onChange: function(v){ setAttr({ showTags: v }); } }),
265 el(ToggleControl, { label: __('Comment count', 'blockenberg'), checked: a.showComments, onChange: function(v){ setAttr({ showComments: v }); } })
266 ),
267
268 /* Demo values */
269 el(PanelBody, { title: __('Demo Values', 'blockenberg'), initialOpen: false },
270 el('p', { style: { color: '#64748b', fontSize: '12px' } }, __('These values appear in the editor preview. In production, replace with dynamic data from your theme.', 'blockenberg')),
271 el(TextControl, { label: __('Author label', 'blockenberg'), value: a.authorLabel, onChange: function(v){ setAttr({ authorLabel: v }); } }),
272 el(TextControl, { label: __('Author name', 'blockenberg'), value: a.authorName, onChange: function(v){ setAttr({ authorName: v }); } }),
273 el(TextControl, { label: __('Author URL', 'blockenberg'), value: a.authorUrl, onChange: function(v){ setAttr({ authorUrl: v }); } }),
274 el(TextControl, { label: __('Avatar URL (leave blank for initial)', 'blockenberg'), value: a.authorAvatarUrl, onChange: function(v){ setAttr({ authorAvatarUrl: v }); } }),
275 el('hr', null),
276 el(IP().IconPickerControl, IP().iconPickerProps(a, setAttr, { charAttr: 'dateLabel', typeAttr: 'dateLabelType', dashiconAttr: 'dateLabelDashicon', imageUrlAttr: 'dateLabelImageUrl', colorAttr: 'dateLabelDashiconColor' })),
277 el(TextControl, { label: __('Date value', 'blockenberg'), value: a.dateValue, onChange: function(v){ setAttr({ dateValue: v }); } }),
278 el('hr', null),
279 el(IP().IconPickerControl, IP().iconPickerProps(a, setAttr, { charAttr: 'readingTimeLabel', typeAttr: 'readingTimeLabelType', dashiconAttr: 'readingTimeLabelDashicon', imageUrlAttr: 'readingTimeLabelImageUrl', colorAttr: 'readingTimeLabelDashiconColor' })),
280 el(TextControl, { label: __('Reading time value', 'blockenberg'), value: a.readingTimeValue, onChange: function(v){ setAttr({ readingTimeValue: v }); } }),
281 el('hr', null),
282 el(IP().IconPickerControl, IP().iconPickerProps(a, setAttr, { charAttr: 'categoriesLabel', typeAttr: 'categoriesLabelType', dashiconAttr: 'categoriesLabelDashicon', imageUrlAttr: 'categoriesLabelImageUrl', colorAttr: 'categoriesLabelDashiconColor' })),
283 el(CatsEditor, { cats: a.categories, onChange: function(v){ setAttr({ categories: v }); }, addLabel: __('+ Add Category', 'blockenberg') }),
284 el('hr', null),
285 el(CatsEditor, { cats: a.tags, onChange: function(v){ setAttr({ tags: v }); }, addLabel: __('+ Add Tag', 'blockenberg') }),
286 el('hr', null),
287 el(IP().IconPickerControl, IP().iconPickerProps(a, setAttr, { charAttr: 'commentsLabel', typeAttr: 'commentsLabelType', dashiconAttr: 'commentsLabelDashicon', imageUrlAttr: 'commentsLabelImageUrl', colorAttr: 'commentsLabelDashiconColor' })),
288 el(TextControl, { label: __('Comments value', 'blockenberg'), value: a.commentsCount, onChange: function(v){ setAttr({ commentsCount: v }); } }),
289 el(TextControl, { label: __('Comments URL', 'blockenberg'), value: a.commentsUrl, onChange: function(v){ setAttr({ commentsUrl: v }); } })
290 ),
291
292 /* Layout */
293 el(PanelBody, { title: __('Layout & Separator', 'blockenberg'), initialOpen: false },
294 el(SelectControl, { label: __('Layout', 'blockenberg'), value: a.layout, options: [
295 { label: __('Horizontal', 'blockenberg'), value: 'horizontal' },
296 { label: __('Vertical', 'blockenberg'), value: 'vertical' },
297 { label: __('Minimal', 'blockenberg'), value: 'minimal' },
298 ], onChange: function(v){ setAttr({ layout: v }); } }),
299 el(SelectControl, { label: __('Separator', 'blockenberg'), value: a.separator, options: SEP_OPTIONS, onChange: function(v){ setAttr({ separator: v }); } }),
300 a.separator === '__custom__' ? el(TextControl, { label: __('Custom separator', 'blockenberg'), value: a.customSeparator, onChange: function(v){ setAttr({ customSeparator: v }); } }) : null
301 ),
302
303 /* Spacing */
304 el(PanelBody, { title: __('Spacing', 'blockenberg'), initialOpen: false },
305 el(RangeControl, { label: __('Gap between items (px)', 'blockenberg'), value: a.gap, min: 0, max: 60, onChange: function(v){ setAttr({ gap: v }); } }),
306 el(RangeControl, { label: __('Padding vertical (px)', 'blockenberg'), value: a.paddingV, min: 0, max: 40, onChange: function(v){ setAttr({ paddingV: v }); } }),
307 el(RangeControl, { label: __('Padding horizontal (px)','blockenberg'), value: a.paddingH, min: 0, max: 60, onChange: function(v){ setAttr({ paddingH: v }); } }),
308 el(RangeControl, { label: __('Border radius (px)', 'blockenberg'), value: a.borderRadius, min: 0, max: 50, onChange: function(v){ setAttr({ borderRadius: v }); } })
309 ),
310
311 /* Typography */
312 el(PanelBody, { title: __('Typography', 'blockenberg'), initialOpen: false },
313 (function () {
314 var TC = getTypoControl();
315 if (!TC) return el('p', null, 'Loading…');
316 return el(TC, { label: 'Text Typography', value: a.textTypo, onChange: function (v) { setAttr({ textTypo: v }); } });
317 })(),
318 el(RangeControl, { label: __('Avatar size (px)', 'blockenberg'), value: a.avatarSize, min: 20, max: 64, onChange: function(v){ setAttr({ avatarSize: v }); } }),
319 el(RangeControl, { label: __('Tag padding V (px)', 'blockenberg'), value: a.tagPaddingV, min: 0, max: 12, onChange: function(v){ setAttr({ tagPaddingV: v }); } }),
320 el(RangeControl, { label: __('Tag padding H (px)', 'blockenberg'), value: a.tagPaddingH, min: 0, max: 20, onChange: function(v){ setAttr({ tagPaddingH: v }); } }),
321 el(RangeControl, { label: __('Tag border radius (px)', 'blockenberg'), value: a.tagRadius, min: 0, max: 50, onChange: function(v){ setAttr({ tagRadius: v }); } })
322 ),
323
324 /* Colors */
325 el(PanelColorSettings, { title: __('Colors', 'blockenberg'), initialOpen: false, colorSettings: [
326 { label: __('Text color', 'blockenberg'), value: a.textColor, onChange: function(v){ setAttr({ textColor: v||'' }); } },
327 { label: __('Link color', 'blockenberg'), value: a.linkColor, onChange: function(v){ setAttr({ linkColor: v||'' }); } },
328 { label: __('Link hover color', 'blockenberg'), value: a.linkColorHover, onChange: function(v){ setAttr({ linkColorHover: v||'' }); } },
329 { label: __('Separator color', 'blockenberg'), value: a.separatorColor, onChange: function(v){ setAttr({ separatorColor: v||'' }); } },
330 { label: __('Background', 'blockenberg'), value: a.bgColor, onChange: function(v){ setAttr({ bgColor: v||'' }); } },
331 { label: __('Avatar border', 'blockenberg'), value: a.avatarBorderColor,onChange: function(v){ setAttr({ avatarBorderColor: v||'' }); } },
332 { label: __('Tag background', 'blockenberg'), value: a.tagBg, onChange: function(v){ setAttr({ tagBg: v||'' }); } },
333 { label: __('Tag text color', 'blockenberg'), value: a.tagColor, onChange: function(v){ setAttr({ tagColor: v||'' }); } },
334 ] })
335 );
336
337 return el(Fragment, null,
338 inspector,
339 el('div', blockProps,
340 el('div', { className: 'bkbg-pm-inner' },
341 buildItems(a, true)
342 )
343 )
344 );
345 },
346
347 save: function (props) {
348 var a = props.attributes;
349 var blockProps = wp.blockEditor.useBlockProps.save({
350 className: 'bkbg-pm-wrap bkbg-pm-layout--' + a.layout,
351 style: buildWrapStyle(a)
352 });
353
354 return el('div', blockProps,
355 el('div', { className: 'bkbg-pm-inner' },
356 buildItems(a, false)
357 )
358 );
359 }
360 });
361 }() );
362