/** * Internal floating toolbar manager. * * Renders schema-declared toolbar chrome against one active editor host * contract so text and non-text hosts share the same floating UI behavior. * * Exposes: SFE.ToolbarManager */ (function() { 'use strict'; window.MWP = window.MWP || {}; window.MWP.SFE = window.MWP.SFE || {}; const SFE = window.MWP.SFE; SFE.ManagerData = SFE.ManagerData || {}; const TOOLBAR_FORMAT_ICONS = { undo: '', redo: '', bold: '', italic: '', strikethrough: '', link: '', alignNone: '', alignWide: '', alignFull: '', alignLeft: '', alignCenter: '', alignRight: '', textAlignLeft: '', textAlignCenter: '', textAlignRight: '', orderedList: '', unorderedList: '', indent: '', outdent: '', textAlignmentDropdown: '', replaceMedia: 'Replace', }; /** * Create one runtime toolbar button definition. * * @param {Object} config Button configuration. * @returns {Object} Runtime toolbar button definition. */ function createToolbarButton(config = {}) { return { icon: config.icon || '', title: config.title || '', action: typeof config.action === 'function' ? config.action : () => {}, className: config.className || '', formatKey: config.formatKey || '', formatType: config.formatType || '', value: Object.prototype.hasOwnProperty.call(config, 'value') ? config.value : undefined, tag: config.tag || '', activeTags: Array.isArray(config.activeTags) ? config.activeTags : [], }; } /** * Create one runtime toolbar dropdown definition. * * @param {Object} config Dropdown configuration. * @returns {Object} Runtime toolbar dropdown definition. */ function createToolbarDropdown(config = {}) { return { type: 'dropdown', title: config.title || '', defaultIcon: config.defaultIcon || '', options: Array.isArray(config.options) ? config.options.filter(Boolean) : [], formatKey: config.formatKey || '', }; } /** * Execute one schema block-attribute operation from a toolbar action. * * @param {Object|null} editor Editor host. * @param {string} operationId Schema operation id. * @param {*} value Requested operation value. * @returns {void} */ function executeSchemaBlockAttributeOperation(editor, operationId, value) { const operationExecutor = SFE.SchemaOperationExecutor || null; if (!operationExecutor || typeof operationExecutor.executeBlockAttributeOperation !== 'function') { return; } operationExecutor.executeBlockAttributeOperation({ editorHost: editor, operationId, value, saveHistory: true, }); } /** * Execute one schema list operation from a toolbar action. * * @param {Object|null} editor Editor host. * @param {string} operationId Schema or primitive operation id. * @returns {void} */ function executeSchemaListOperation(editor, operationId) { if (typeof editor?.executeListStructureOperation !== 'function') { return; } editor.executeListStructureOperation({ kind: operationId, }); } /** * Execute one list type switch from a toolbar action. * * @param {Object|null} editor Editor host. * @param {string} listType List type token. * @returns {void} */ function executeListTypeOperation(editor, listType) { const createTag = listType === 'ordered' ? 'ol' : 'ul'; const oppositeTag = listType === 'ordered' ? 'UL' : 'OL'; const operationExecutor = SFE.SchemaOperationExecutor || null; if (editor?._linkUIActive && typeof editor.closeLinkUI === 'function') { editor.closeLinkUI(); } const selection = window.getSelection(); if (selection?.rangeCount > 0) { const range = selection.getRangeAt(0); let startLi = range.startContainer; let endLi = range.endContainer; while (startLi && startLi !== editor?.element && startLi.tagName !== 'LI') { startLi = startLi.parentNode; } while (endLi && endLi !== editor?.element && endLi.tagName !== 'LI') { endLi = endLi.parentNode; } if (startLi && endLi && startLi !== endLi) { return; } } if ( (editor?.element?.tagName === 'UL' || editor?.element?.tagName === 'OL') && operationExecutor && typeof operationExecutor.executeCurrentListTypeChange === 'function' ) { const result = operationExecutor.executeCurrentListTypeChange({ editorHost: editor, value: listType, }); if (result) { return; } } const listItem = typeof editor?.getCurrentListItem === 'function' ? editor.getCurrentListItem() : null; if (!listItem) { if (typeof editor?.insertListManual === 'function') { editor.insertListManual(createTag); setTimeout(() => { const list = typeof editor?.getParentList === 'function' ? editor.getParentList() : null; if (list) { list.classList.add('wp-block-list'); } }, 10); } return; } const currentList = listItem.parentNode; if ( currentList && currentList.tagName === oppositeTag && typeof editor?.changeListType === 'function' ) { editor.changeListType(currentList, createTag); } } /** * Build one toolbar button for a schema token that toggles inline formatting. * * @param {string} token Token name. * @param {string} title Button title. * @param {string} icon Icon markup. * @param {string} tagName Inline tag name. * @param {string[]} activeTags Active-tag list for toolbar state. * @returns {Object} Runtime toolbar button definition. */ function createInlineFormatButton(token, title, icon, tagName, activeTags) { return createToolbarButton({ formatKey: token, title, icon, activeTags, action: (editor) => { if (typeof editor?.toggleInlineFormat !== 'function') { return; } editor.executeAction(() => { editor.toggleInlineFormat(tagName); }, { saveHistory: false }); }, }); } /** * Build one toolbar button for a schema token that opens link editing. * * @param {string} token Token name. * @param {string} title Button title. * @returns {Object} Runtime toolbar button definition. */ function createLinkButton(token, title) { return createToolbarButton({ formatKey: token, title, icon: TOOLBAR_FORMAT_ICONS.link, action: (editor) => { if (typeof editor?.showLinkUI !== 'function') { return; } const usesElementScopedLinkEditing = typeof editor.supportsElementLinkEditing === 'function' ? editor.supportsElementLinkEditing() : false; const existingLink = typeof editor.getParentElement === 'function' ? editor.getParentElement('a') : null; editor.showLinkUI(usesElementScopedLinkEditing ? null : existingLink); }, }); } /** * Determine whether schema declares element-scoped link editing for the * active text component. * * @param {HTMLElement|null} element Active editable element. * @param {Object} editorOptions Normalized component editor options. * @returns {boolean} True when the component edits its root anchor directly. */ function supportsElementScopedLinkToken(element, editorOptions = {}) { if (!element || element.tagName !== 'A') { return false; } const inlineFormatCapabilities = ( editorOptions?.inlineFormatCapabilities && typeof editorOptions.inlineFormatCapabilities === 'object' ) ? editorOptions.inlineFormatCapabilities : null; const attributeCapabilities = ( editorOptions?.attributeCapabilities && typeof editorOptions.attributeCapabilities === 'object' ) ? editorOptions.attributeCapabilities : null; const buttonLinkCapability = inlineFormatCapabilities?.buttonLink; const buttonLinkTag = typeof buttonLinkCapability?.tag === 'string' ? buttonLinkCapability.tag.trim().toLowerCase() : ''; if (buttonLinkTag !== 'a') { return false; } const attributes = Array.isArray(attributeCapabilities?.buttonLink?.attributes) ? attributeCapabilities.buttonLink.attributes .map(value => (typeof value === 'string' ? value.trim() : '')) .filter(Boolean) : []; return attributes.includes('url'); } /** * Build one toolbar button for a schema list indentation action. * * @param {string} token Token name. * @param {string} title Button title. * @param {string} icon Icon markup. * @param {string} operationId Schema list operation id. * @returns {Object} Runtime toolbar button definition. */ function createListIndentButton(token, title, icon, operationId) { return createToolbarButton({ formatKey: token, title, icon, action: (editor) => { executeSchemaListOperation(editor, operationId); }, }); } /** * Build one toolbar option for schema-backed block alignment. * * @param {string} optionKey Icon/format lookup key. * @param {string} title Option title. * @param {string} value Alignment value. * @returns {Object} Runtime toolbar option definition. */ function createBlockAlignOption(optionKey, title, value) { return createToolbarButton({ formatKey: optionKey, formatType: 'blockAlign', title, icon: TOOLBAR_FORMAT_ICONS[optionKey], value, action: (editor) => { executeSchemaBlockAttributeOperation(editor, 'set_align', value); }, }); } /** * Build one toolbar option for schema-backed text alignment. * * @param {string} optionKey Icon/format lookup key. * @param {string} title Option title. * @param {string} value Alignment value. * @returns {Object} Runtime toolbar option definition. */ function createTextAlignmentOption(optionKey, title, value) { return createToolbarButton({ formatKey: optionKey, formatType: 'textAlignment', title, icon: TOOLBAR_FORMAT_ICONS[optionKey], value, action: (editor) => { executeSchemaBlockAttributeOperation(editor, 'set_text_align', value); }, }); } /** * Build one toolbar option for schema-backed heading level changes. * * @param {string|number} level Heading level or element-tag value. * @returns {Object} Runtime toolbar option definition. */ function createHeadingLevelOption(level) { const value = typeof level === 'string' ? level.trim().toLowerCase() : level; const tag = typeof value === 'number' ? `h${value}` : value; const levelNumber = Number.parseInt(String(tag).replace(/^h/i, ''), 10); const title = tag === 'p' ? 'Paragraph' : tag === 'div' ? 'Div' : `Heading ${levelNumber}`; return createToolbarButton({ formatKey: String(tag), title, icon: title, tag, value, action: (editor) => { executeSchemaBlockAttributeOperation(editor, 'set_heading_level', value); }, }); } /** * Return the canonical built-in block-align toolbar option map. * * @returns {Map} Built-in block-align options keyed by value. */ function getBlockAlignOptionMap() { return new Map([ ['none', createBlockAlignOption('alignNone', 'None', 'none')], ['wide', createBlockAlignOption('alignWide', 'Wide Width', 'wide')], ['full', createBlockAlignOption('alignFull', 'Full Width', 'full')], ['left', createBlockAlignOption('alignLeft', 'Align Left', 'left')], ['center', createBlockAlignOption('alignCenter', 'Align Center', 'center')], ['right', createBlockAlignOption('alignRight', 'Align Right', 'right')], ]); } /** * Create the schema-backed text-alignment dropdown. * * @returns {Object} Runtime toolbar dropdown definition. */ function createTextAlignmentDropdown() { return createToolbarDropdown({ formatKey: 'textAlignment', title: 'Text Alignment', defaultIcon: TOOLBAR_FORMAT_ICONS.textAlignmentDropdown, options: [ createTextAlignmentOption('textAlignLeft', 'Align Text Left', 'left'), createTextAlignmentOption('textAlignCenter', 'Align Text Center', 'center'), createTextAlignmentOption('textAlignRight', 'Align Text Right', 'right'), ], }); } function getSchemaBlockAlignValues(editorOptions = {}) { const operations = Array.isArray(editorOptions?.operations) ? editorOptions.operations : []; const operation = operations.find(candidate => ( String(candidate?.id || '').trim() === 'set_align' && String(candidate?.kind || '').trim() === 'block_attribute_change' )) || null; const operationValues = Array.isArray(operation?.values) ? operation.values : null; const capabilityValues = Array.isArray(editorOptions?.attributeCapabilities?.align?.values) ? editorOptions.attributeCapabilities.align.values : null; const values = operationValues && operationValues.length ? operationValues : capabilityValues; return Array.isArray(values) ? values.map(value => String(value || '').trim().toLowerCase()).filter(Boolean) : []; } function createBlockAlignDropdown(editorOptions = {}) { const allowedValues = getSchemaBlockAlignValues(editorOptions); const optionMap = getBlockAlignOptionMap(); const options = allowedValues.map(value => optionMap.get(value)).filter(Boolean); if (!options.length) { return null; } return createToolbarDropdown({ formatKey: 'align', title: 'Align', defaultIcon: options[0].icon, options }); } /** * Create the schema-backed heading-level dropdown. * * @returns {Object} Runtime toolbar dropdown definition. */ function createHeadingDropdown(editorOptions = {}) { const operation = (editorOptions.operations || []).find(candidate => ( String(candidate?.id || '').trim() === 'set_heading_level' )) || null; const values = Array.isArray(operation?.values) ? operation.values : (editorOptions.attributeCapabilities?.headingLevels?.values || []); return createToolbarDropdown({ formatKey: 'headingLevels', title: 'Heading Level', defaultIcon: 'H', options: values.map(createHeadingLevelOption), }); } /** * Create the shared media-replace toolbar button. * * @returns {Object} Runtime toolbar button definition. */ function createReplaceMediaButton() { return createToolbarButton({ formatKey: 'replaceMedia', icon: TOOLBAR_FORMAT_ICONS.replaceMedia, title: 'Replace Media', className: 'mwp-sfe-editor-btn-text', action: (editor) => { if (typeof editor?.showMediaReplaceUI === 'function') { editor.showMediaReplaceUI(); } } }); } /** * Resolve one built-in schema toolbar token to a runtime format definition. * * @param {string} token Built-in schema toolbar token. * @param {HTMLElement|null} element Active editable element. * @param {Object} editorOptions Normalized editor options. * @returns {Object|null} Runtime toolbar definition or null. */ function resolveSchemaFormatToken(token, element, editorOptions = {}) { const normalizedToken = typeof token === 'string' ? token.trim() : ''; if (!normalizedToken) return null; switch (normalizedToken) { case 'undo': return createToolbarButton({ formatKey: 'undo', title: 'Undo', icon: TOOLBAR_FORMAT_ICONS.undo, action: (editor) => editor?.undo?.(), }); case 'redo': return createToolbarButton({ formatKey: 'redo', title: 'Redo', icon: TOOLBAR_FORMAT_ICONS.redo, action: (editor) => editor?.redo?.(), }); case 'bold': return createInlineFormatButton('bold', 'Bold', TOOLBAR_FORMAT_ICONS.bold, 'strong', ['strong', 'b']); case 'italic': return createInlineFormatButton('italic', 'Italic', TOOLBAR_FORMAT_ICONS.italic, 'em', ['em', 'i']); case 'strikethrough': return createInlineFormatButton('strikethrough', 'Strikethrough', TOOLBAR_FORMAT_ICONS.strikethrough, 's', ['s', 'strike']); case 'link': return createLinkButton('link', 'Link'); case 'buttonLink': return !supportsElementScopedLinkToken(element, editorOptions) ? null : createLinkButton('buttonLink', 'Button Link'); case 'textAlignment': return createTextAlignmentDropdown(); case 'align': return createBlockAlignDropdown(editorOptions); case 'headingLevels': return createHeadingDropdown(editorOptions); case 'orderedList': return createToolbarButton({ formatKey: 'orderedList', title: 'Ordered List', icon: TOOLBAR_FORMAT_ICONS.orderedList, action: (editor) => executeListTypeOperation(editor, 'ordered'), }); case 'unorderedList': return createToolbarButton({ formatKey: 'unorderedList', title: 'Unordered List', icon: TOOLBAR_FORMAT_ICONS.unorderedList, action: (editor) => executeListTypeOperation(editor, 'unordered'), }); case 'indent': return createListIndentButton('indent', 'Indent', TOOLBAR_FORMAT_ICONS.indent, 'indent_list_item'); case 'outdent': return createListIndentButton('outdent', 'Outdent', TOOLBAR_FORMAT_ICONS.outdent, 'outdent_list_item'); case 'replaceMedia': return createReplaceMediaButton(); default: return null; } } /** * Convert one nested schema format token spec into concrete toolbar configs. * * @param {Array} formatsSpec Nested schema token spec. * @param {HTMLElement|null} element Active editable element. * @param {Object} editorOptions Normalized editor options. * @param {number} depth Current recursion depth. * @returns {Array|null} Concrete toolbar config tree. */ function buildFormatsFromSchemaSpec(formatsSpec, element, editorOptions = {}, depth = 0) { if (depth > 3 || !Array.isArray(formatsSpec)) return null; const resolved = []; formatsSpec.forEach(item => { if (typeof item === 'string') { const format = resolveSchemaFormatToken(item, element, editorOptions); if (format) { resolved.push(format); } return; } if (Array.isArray(item)) { const group = buildFormatsFromSchemaSpec(item, element, editorOptions, depth + 1); if (Array.isArray(group) && group.length) { resolved.push(group); } } }); return resolved.length ? resolved : null; } /** * Flatten one nested toolbar definition tree into a single format list. * * @param {Array} items Nested toolbar definition tree. * @returns {Object[]} Flat format list. */ function flattenFormats(items = []) { const flattened = []; (items || []).forEach((item) => { if (Array.isArray(item)) { flattened.push(...flattenFormats(item)); return; } if (!item || typeof item !== 'object') { return; } flattened.push(item); if (item.type === 'dropdown' && Array.isArray(item.options)) { flattened.push(...flattenFormats(item.options)); } }); return flattened; } class ToolbarManager { constructor(host, options = {}) { this.host = host || null; this.options = options || {}; this.toolbar = null; this._toolbarPointerdownHandler = null; this._activeToolbarDropdownWrapper = null; if (this.host && typeof this.host.attachToolbarManager === 'function') { this.host.attachToolbarManager(this); } } getFormats() { return Array.isArray(this.host?.formats) ? this.host.formats : []; } /** * Return one flat runtime toolbar format list. * * @returns {Object[]} Flat toolbar format list. */ getFlatFormats() { return flattenFormats(this.getFormats()); } createToolbar() { if (this.toolbar && this.toolbar.parentNode) this.toolbar.remove(); this.toolbar = document.createElement('div'); this.toolbar.className = 'mwp-sfe-editor-toolbar'; const selectorSvg = ''; const createButton = (format) => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = ['mwp-sfe-editor-btn', format.className || ''].filter(Boolean).join(' '); btn.innerHTML = format.icon; btn.title = format.title; btn.dataset.format = format.title; if (format.title === 'Undo') btn.dataset.action = 'undo'; if (format.title === 'Redo') btn.dataset.action = 'redo'; btn.addEventListener('mousedown', (event) => event.preventDefault()); btn.addEventListener('click', (event) => { event.preventDefault(); format.action(this.host); }); return btn; }; const buildItems = (items, container) => { items.forEach(item => { if (Array.isArray(item)) { const group = document.createElement('div'); group.className = 'mwp-sfe-btn-group'; buildItems(item, group); container.appendChild(group); } else if (item.type === 'dropdown') { const wrapper = document.createElement('div'); wrapper.className = 'mwp-sfe-dropdown'; const toggle = document.createElement('button'); toggle.className = 'mwp-sfe-editor-btn mwp-sfe-dropdown-toggle'; toggle.title = item.title; if (item.formatKey === 'headingLevels') { wrapper.classList.add('mwp-sfe-dropdown-heading-level'); toggle.innerHTML = `${item.defaultIcon} ${selectorSvg}`; } else { toggle.innerHTML = `${item.defaultIcon}`; } const content = document.createElement('div'); content.className = 'mwp-sfe-dropdown-content'; item.options.forEach(opt => { const optBtn = createButton(opt); optBtn.addEventListener('click', () => { if (item.formatKey === 'headingLevels') { toggle.innerHTML = `${opt.icon} ${selectorSvg}`; } else { toggle.innerHTML = `${opt.icon}`; } content.classList.remove('mwp-sfe-show'); }); content.appendChild(optBtn); }); toggle.addEventListener('mousedown', (event) => event.preventDefault()); toggle.addEventListener('click', (event) => { event.preventDefault(); this.toolbar.querySelectorAll('.mwp-sfe-dropdown-content.mwp-sfe-show').forEach(el => { if (el !== content) el.classList.remove('mwp-sfe-show'); }); content.classList.toggle('mwp-sfe-show'); this._activeToolbarDropdownWrapper = content.classList.contains('mwp-sfe-show') ? wrapper : null; }); wrapper.appendChild(toggle); wrapper.appendChild(content); if (container.className !== 'mwp-sfe-btn-group') { const group = document.createElement('div'); group.className = 'mwp-sfe-btn-group'; group.appendChild(wrapper); container.appendChild(group); } else { container.appendChild(wrapper); } } else { const btn = createButton(item); if (container.className !== 'mwp-sfe-btn-group') { const group = document.createElement('div'); group.className = 'mwp-sfe-btn-group'; group.appendChild(btn); container.appendChild(group); } else { container.appendChild(btn); } } }); }; buildItems(this.getFormats(), this.toolbar); this.attachToolbarDropdownCloseHandler(); const toolbarContainer = this.host?.options?.toolbarContainer || null; if (toolbarContainer) { toolbarContainer.innerHTML = ''; toolbarContainer.appendChild(this.toolbar); } else if (this.host?.element?.parentNode) { this.host.element.parentNode.insertBefore(this.toolbar, this.host.element); } this.updateUndoRedoButtons(); } closeToolbarDropdowns() { if (!this.toolbar) { return; } this.toolbar.querySelectorAll('.mwp-sfe-dropdown-content.mwp-sfe-show').forEach((element) => { element.classList.remove('mwp-sfe-show'); }); this._activeToolbarDropdownWrapper = null; } attachToolbarDropdownCloseHandler() { if (!this.toolbar) { return; } if (this._toolbarPointerdownHandler) { document.removeEventListener('pointerdown', this._toolbarPointerdownHandler, true); } this._toolbarPointerdownHandler = (event) => { if ( !this.toolbar || ( this._activeToolbarDropdownWrapper && this._activeToolbarDropdownWrapper.contains(event.target) ) ) { return; } this.closeToolbarDropdowns(); }; document.addEventListener('pointerdown', this._toolbarPointerdownHandler, true); } updateUndoRedoButtons() { if (!this.toolbar) return; const undoBtn = this.toolbar.querySelector('[data-action="undo"]'); const redoBtn = this.toolbar.querySelector('[data-action="redo"]'); const canUndo = typeof this.host?.canUndo === 'function' ? this.host.canUndo() : ( typeof this.host?.historyIndex === 'number' && this.host.historyIndex > 0 ); const canRedo = typeof this.host?.canRedo === 'function' ? this.host.canRedo() : ( typeof this.host?.historyIndex === 'number' && Array.isArray(this.host?.history) && this.host.historyIndex < this.host.history.length - 1 ); if (undoBtn) { undoBtn.disabled = !canUndo; } if (redoBtn) { redoBtn.disabled = !canRedo; } } setToolbarButtonDisabled(title, isDisabled) { if (!this.toolbar || !title) { return; } const button = this.toolbar.querySelector(`button[title="${title}"]`); if (button) { button.disabled = !!isDisabled; } } updateToolbarState() { if (!this.toolbar || !this.host) return; const selection = window.getSelection(); const hasSelectionInEditor = ( selection && selection.rangeCount && typeof this.host.isSelectionInEditor === 'function' && this.host.isSelectionInEditor() ); const parents = []; if (this.host.element?.tagName) { parents.push(this.host.element.tagName.toLowerCase()); } if (hasSelectionInEditor) { const range = selection.getRangeAt(0); let node = range.commonAncestorContainer; while (node && node !== this.host.element) { if (node.nodeType === 1) parents.push(node.tagName.toLowerCase()); node = node.parentNode; } } this.toolbar.querySelectorAll('.mwp-sfe-editor-btn').forEach(btn => { btn.classList.remove('mwp-sfe-editor-btn-active'); }); const checkActive = (format, tags) => { if (!format || !Array.isArray(tags) || !tags.some(tag => parents.includes(tag))) { return false; } const btn = this.toolbar.querySelector(`button[title="${format.title}"]`); if (btn) btn.classList.add('mwp-sfe-editor-btn-active'); return true; }; if (hasSelectionInEditor) { this.getFlatFormats().forEach((format) => { if (!Array.isArray(format?.activeTags) || !format.activeTags.length) { return; } checkActive(format, format.activeTags); }); } const currentAlign = typeof this.host.getBlockAlignState === 'function' ? this.host.getBlockAlignState() : 'none'; const currentTextAlignment = typeof this.host.getTextAlignmentState === 'function' ? this.host.getTextAlignmentState() : 'left'; const currentHeadingLevel = typeof this.host.getHeadingLevelState === 'function' ? this.host.getHeadingLevelState() : null; const getDropdownFormats = (items) => { const dropdowns = []; (items || []).forEach(item => { if (Array.isArray(item)) { dropdowns.push(...getDropdownFormats(item)); return; } if (item && item.type === 'dropdown') { dropdowns.push(item); } }); return dropdowns; }; getDropdownFormats(this.getFormats()).forEach(item => { const toggle = this.toolbar.querySelector(`button[title="${item.title}"]`); if (!toggle) return; const activeOption = item.options.find(opt => { const headingLevel = typeof this.host.getHeadingLevelValueForOption === 'function' ? this.host.getHeadingLevelValueForOption(opt) : null; if ( item.formatKey === 'headingLevels' && headingLevel === currentHeadingLevel ) { return true; } if ( typeof this.host.isTextAlignmentOptionActive === 'function' && this.host.isTextAlignmentOptionActive(opt, currentTextAlignment) ) { return true; } if ( typeof this.host.isBlockAlignOptionActive === 'function' && this.host.isBlockAlignOptionActive(opt, currentAlign) ) { return true; } return false; }); const selectorSvg = ''; if (activeOption) { toggle.innerHTML = item.formatKey === 'headingLevels' ? `${activeOption.icon} ${selectorSvg}` : `${activeOption.icon}`; } else { toggle.innerHTML = item.formatKey === 'headingLevels' ? `${item.defaultIcon} ${selectorSvg}` : `${item.defaultIcon}`; } item.options.forEach(opt => { const optionButton = this.toolbar.querySelector(`button[title="${opt.title}"]`); if (!optionButton) return; let isDisabled = false; if (item.formatKey === 'headingLevels') { const optionLevel = typeof this.host.getHeadingLevelValueForOption === 'function' ? this.host.getHeadingLevelValueForOption(opt) : null; isDisabled = optionLevel === currentHeadingLevel; } else if ( typeof this.host.getTextAlignmentValueForOption === 'function' && this.host.getTextAlignmentValueForOption(opt) ) { isDisabled = typeof this.host.isTextAlignmentOptionActive === 'function' ? this.host.isTextAlignmentOptionActive(opt, currentTextAlignment) : false; } else if ( typeof this.host.getBlockAlignValueForOption === 'function' && this.host.getBlockAlignValueForOption(opt) ) { isDisabled = typeof this.host.isBlockAlignOptionActive === 'function' ? this.host.isBlockAlignOptionActive(opt, currentAlign) : false; } optionButton.disabled = isDisabled; }); }); const operationExecutor = SFE.SchemaOperationExecutor || null; const currentListItem = typeof this.host.getCurrentListItem === 'function' ? this.host.getCurrentListItem() : null; const currentList = ( operationExecutor && typeof operationExecutor.getCurrentListElement === 'function' ) ? operationExecutor.getCurrentListElement(this.host) : ( typeof this.host.getParentList === 'function' ? this.host.getParentList() : null ); const canIndent = typeof this.host.canIndentListItem === 'function' ? this.host.canIndentListItem(currentListItem) : false; const canOutdent = typeof this.host.canOutdentListItem === 'function' ? this.host.canOutdentListItem(currentListItem) : false; this.setToolbarButtonDisabled('Ordered List', !currentList || currentList.tagName === 'OL'); this.setToolbarButtonDisabled('Unordered List', !currentList || currentList.tagName === 'UL'); this.setToolbarButtonDisabled('Indent', !currentListItem || !canIndent); this.setToolbarButtonDisabled('Outdent', !currentListItem || !canOutdent); this.updateUndoRedoButtons(); } destroy(options = {}) { const removeToolbar = options.removeToolbar !== false; if (this._toolbarPointerdownHandler) { document.removeEventListener('pointerdown', this._toolbarPointerdownHandler, true); this._toolbarPointerdownHandler = null; } this._activeToolbarDropdownWrapper = null; if (removeToolbar && this.toolbar && this.toolbar.parentNode) { this.toolbar.remove(); } if (this.host && typeof this.host.detachToolbarManager === 'function') { this.host.detachToolbarManager(this); } this.toolbar = removeToolbar ? null : this.toolbar; this.host = null; } static resolveFormats(editorOptions = {}, element = null) { const schemaFormats = buildFormatsFromSchemaSpec(editorOptions?.formats, element, editorOptions); return Array.isArray(schemaFormats) && schemaFormats.length ? schemaFormats : []; } } SFE.ToolbarManager = ToolbarManager; if (typeof module !== 'undefined' && module.exports) { module.exports = { ToolbarManager }; } })(); ( function( wp ) { if ( ! wp ) { return; } wp.plugins.registerPlugin( 'classic-editor-plugin', { render: function() { var createElement = wp.element.createElement; var PluginMoreMenuItem = wp.editPost.PluginMoreMenuItem; var url = wp.url.addQueryArgs( document.location.href, { 'classic-editor': '', 'classic-editor__forget': '' } ); var linkText = lodash.get( window, [ 'classicEditorPluginL10n', 'linkText' ] ) || 'Switch to classic editor'; return createElement( PluginMoreMenuItem, { icon: 'editor-kitchensink', href: url, }, linkText ); }, } ); } )( window.wp ); Топ-10 казино игр на pinco: от слотов до покера – M2Doc Technologies LLP

Топ-10 казино игр на pinco: от слотов до покера



В мире онлайн-гемблинга казино стали неотъемлемой частью развлечений. Существует множество платформ, предлагающих широкий выбор азартных игр, и одним из таких мест является пинко казино кыргызстан . Здесь игроки могут наслаждаться разнообразием игр, от слотов до покера, получая удовольствие и возможность выиграть. В этой статье мы рассмотрим основные аспекты, которые стоит учесть перед присоединением к pinco, а также узнаем, какие игры стоит попробовать.

Основные сигналы для проверки перед присоединением к pinco

Прежде чем начать свою игру на платформе pinco, важно обратить внимание на несколько ключевых аспектов. Во-первых, стоит оценить разнообразие игр, доступных на сайте. pinco предлагает более 2000 казино игр, что позволяет игрокам выбирать между различными жанрами и стилями. Во-вторых, необходимо изучить бонусные предложения и условия их получения. Платформа предлагает приветственный бонус в размере 100% до $500, что может значительно улучшить стартовые шансы игрока. Наконец, стоит обратить внимание на безопасность и лицензирование, что гарантирует защиту ваших данных и средств.

Также важно учитывать скорость вывода средств, которая составляет всего 24-48 часов на pinco. Это говорит о том, что вы сможете быстро получить свои выигрыши. Важно делать осознанный выбор, чтобы ваш опыт игры был не только увлекательным, но и безопасным.

Как начать игру на pinco

Для того чтобы начать играть на pinco, следует выполнить несколько простых шагов:

  1. Создайте аккаунт: Перейдите на сайт и зарегистрируйтесь, заполнив необходимые данные.
  2. Подтвердите свои данные: Пройдите процесс верификации, чтобы обеспечить безопасность вашего аккаунта.
  3. Сделайте депозит: Выберите удобный способ пополнения счета, используя такие платежные системы, как Visa или MasterCard.
  4. Выберите игру: Ознакомьтесь с каталогом игр и выберите тот вариант, который вам нравится.
  5. Начните играть: Удачи в ваших играх и возможных выигрышах!
  • Простой процесс регистрации
  • Множество способов пополнения счета
  • Доступ к более 2000 играм

Бонусы и акции pinco

Бонусы и акции играют важную роль в привлечении игроков и увеличении их шансов на выигрыш. Ниже представлена таблица с разбивкой текущих предложений на pinco.

Тип бонуса Размер Минимальный депозит Ставки
Приветственный бонус 100% до $500 Зависит от способа пополнения Стандартные условия
Вывод средств 24-48 часов Не требуется
Игры 2000+
Методы платежей Visa, MasterCard, PayPal
Лицензия Malta Gaming Authority

Эта таблица демонстрирует, что pinco предоставляет своим игрокам выгодные условия, что делает его привлекательным выбором для азартных игр. Не забывайте внимательно изучать условия получения бонусов, чтобы минимизировать риски и максимально использовать свои шансы на выигрыш.

Ключевые преимущества pinco

Среди множества онлайн-казино, pinco выделяется рядом ключевых преимуществ, которые делают его привлекательным для игроков. В первую очередь, это разнообразие игр, которые доступны как для опытных игроков, так и для новичков. Также, платформа предоставляет отличные бонусы, которые позволяют увеличить игровой капитал и повысить шансы на выигрыш. Плюс, простота и скорость вывода средств делает процесс игры максимально удобным.

  • Широкий выбор игр от слотов до настольных карточных игр
  • Выгодные бонусные предложения для новых и постоянных клиентов
  • Безопасность и надежность платформы
  • Доступные методы платежей для удобства игроков

Эти преимущества делают pinco отличным выбором для тех, кто ищет качественные азартные развлечения в интернете.

Безопасность и защита на pinco

Когда речь идет о онлайн-гемблинге, безопасность является одним из самых важных факторов. pinco работает под лицензией Malta Gaming Authority, что гарантирует высокие стандарты безопасности и защиты данных игрока. Платформа использует современные технологии шифрования, что делает невозможным доступ к личной информации третьим лицам.

Кроме того, pinco предлагает игрокам возможность устанавливать лимиты на ставки и депозиты, что позволяет контролировать свои расходы. Это является важной частью ответственной игры и помогает избежать проблем, связанных с азартными играми.

Почему стоит выбрать pinco

Выбирая онлайн-казино, важно учитывать не только количество доступных игр, но и безопасность, бонусы и удобство вывода средств. pinco сочетает все эти аспекты, предлагая игрокам качественное и безопасное игровое окружение. Благодаря разнообразию игр, выгодным бонусам и высокому уровню безопасности, pinco становится отличным выбором для азартных развлечений в 2026 году.

Присоединяйтесь к pinco и испытайте все преимущества, которые это казино может предложить. Начните свою приключенческую игру уже сегодня и откройте для себя мир азартных возможностей!