feat(demo): add story 1 — Sorano: Rock and Time
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
export default function collapse(input) {
|
||||
let output = input;
|
||||
|
||||
output = output.replace(/<figure class="image">((((?!(<\/figure>)).)|\n)*)<\/figure>/gm, '$1');
|
||||
|
||||
const domOutput = new DOMParser().parseFromString(output, 'text/html');
|
||||
|
||||
[...domOutput.querySelectorAll('shortcode-block, shortcode-inline')].forEach((domShortcode) => {
|
||||
domShortcode.setAttribute('sc-rendered', false);
|
||||
});
|
||||
|
||||
let domShortcode = domOutput.querySelector('shortcode-block[sc-rendered], shortcode-inline[sc-rendered]');
|
||||
|
||||
while (domShortcode) {
|
||||
const name = domShortcode.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
|
||||
domShortcode.removeAttribute('class');
|
||||
domShortcode.removeAttribute('sc-rendered');
|
||||
|
||||
const domInnerContent = domShortcode.querySelector(`shortcode-${shortcode.type}-editable, shortcode-${shortcode.type}-readonly`);
|
||||
domShortcode.innerHTML = (domInnerContent && domInnerContent.innerHTML) || '';
|
||||
|
||||
domShortcode = domOutput.querySelector('shortcode-block[sc-rendered], shortcode-inline[sc-rendered]');
|
||||
}
|
||||
|
||||
output = domOutput.body.innerHTML;
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import collapse from './collapse';
|
||||
import uncollapse from './uncollapse';
|
||||
|
||||
const Command = window.nextgenEditor.classes.core.command.class;
|
||||
|
||||
window.nextgenEditor.addPlugin('GravShortcodeCoreCommand', {
|
||||
init() {
|
||||
Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => {
|
||||
const commandName = `shortcode_${shortcode.name}`;
|
||||
|
||||
class GravShortcodeCoreCommand extends Command {
|
||||
execute(args) {
|
||||
this.editor.model.change((modelWriter) => {
|
||||
let dataShortcode = '';
|
||||
const argsForUncollapse = {};
|
||||
|
||||
const wrapOnInsert = !shortcode.child && !shortcode.parent
|
||||
? shortcode.wrapOnInsert !== undefined
|
||||
? shortcode.wrapOnInsert
|
||||
: true
|
||||
: false;
|
||||
|
||||
const selectedBlocks = [...this.editor.model.document.selection.getSelectedBlocks()];
|
||||
const selectedItems = [...this.editor.model.document.selection.getFirstRange().getItems({ shallow: true })];
|
||||
|
||||
const firstSelectedBlock = selectedBlocks[0];
|
||||
const firstBlockSelectedItems = selectedItems.filter((item) => item.parent === firstSelectedBlock);
|
||||
|
||||
const attributes = Object.keys(shortcode.attributes).reduce((acc, attrName) => {
|
||||
acc[attrName] = shortcode.attributes[attrName].default.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
dataShortcode += `<shortcode-${shortcode.type} name="${shortcode.name}" attributes="${encodeURIComponent(JSON.stringify(attributes))}">`;
|
||||
|
||||
if (wrapOnInsert) {
|
||||
if (shortcode.type === 'block') {
|
||||
const modelSelectedBlocks = modelWriter.createDocumentFragment();
|
||||
selectedBlocks.forEach((block) => modelWriter.append(modelWriter.cloneElement(block), modelSelectedBlocks));
|
||||
|
||||
const viewSelectedBlocks = this.editor.data.toView(modelSelectedBlocks);
|
||||
const dataSelectedBlocks = this.editor.data.processor.toData(viewSelectedBlocks);
|
||||
|
||||
dataShortcode += collapse(dataSelectedBlocks);
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
const modelSelectedBlocks = modelWriter.createDocumentFragment();
|
||||
|
||||
firstBlockSelectedItems.forEach((item) => {
|
||||
const block = item.textNode
|
||||
? modelWriter.createText(item.data)
|
||||
: modelWriter.cloneElement(item);
|
||||
|
||||
modelWriter.append(block, modelSelectedBlocks);
|
||||
});
|
||||
|
||||
const viewSelectedBlocks = this.editor.data.toView(modelSelectedBlocks);
|
||||
const dataSelectedBlocks = this.editor.data.processor.toData(viewSelectedBlocks);
|
||||
|
||||
dataShortcode += collapse(dataSelectedBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcode.parent) {
|
||||
dataShortcode += '<p> </p>';
|
||||
}
|
||||
|
||||
dataShortcode += `</shortcode-${shortcode.type}>`;
|
||||
|
||||
if (shortcode.parent) {
|
||||
if (args && args.modelParentShortcode) {
|
||||
argsForUncollapse.parentAttributes = JSON.parse(decodeURIComponent(args.modelParentShortcode.getAttribute('attributes')));
|
||||
}
|
||||
}
|
||||
|
||||
dataShortcode = uncollapse(dataShortcode, argsForUncollapse);
|
||||
|
||||
const convertContext = shortcode.type === 'inline'
|
||||
? '$block'
|
||||
: '$root';
|
||||
|
||||
const viewShortcode = this.editor.data.processor.toView(dataShortcode).getChild(0);
|
||||
const modelShortcode = this.editor.data.toModel(viewShortcode, convertContext).getChild(0);
|
||||
|
||||
let insertPosition = modelWriter.createPositionAt(this.editor.model.document.getRoot(), 0);
|
||||
|
||||
if (!args || !args.insertPosition) {
|
||||
if (shortcode.type === 'block') {
|
||||
const firstBlock = selectedBlocks[0];
|
||||
const lastBlock = selectedBlocks[selectedBlocks.length - 1];
|
||||
|
||||
if (wrapOnInsert) {
|
||||
insertPosition = modelWriter.createPositionBefore(firstBlock);
|
||||
|
||||
modelWriter.remove(
|
||||
modelWriter.createRange(
|
||||
modelWriter.createPositionBefore(firstBlock),
|
||||
modelWriter.createPositionAfter(lastBlock),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
insertPosition = modelWriter.createPositionAfter(lastBlock);
|
||||
|
||||
if (lastBlock && lastBlock.name === 'paragraph' && lastBlock.childCount === 0) {
|
||||
insertPosition = modelWriter.createPositionBefore(lastBlock);
|
||||
modelWriter.remove(lastBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
const firstItem = firstBlockSelectedItems.length
|
||||
? firstBlockSelectedItems[0]
|
||||
: null;
|
||||
|
||||
const lastItem = firstBlockSelectedItems.length
|
||||
? firstBlockSelectedItems[firstBlockSelectedItems.length - 1]
|
||||
: null;
|
||||
|
||||
if (wrapOnInsert) {
|
||||
insertPosition = firstItem
|
||||
? modelWriter.createPositionBefore(firstItem)
|
||||
: this.editor.model.document.selection.getFirstPosition();
|
||||
|
||||
if (firstItem) {
|
||||
modelWriter.remove(
|
||||
modelWriter.createRange(
|
||||
modelWriter.createPositionBefore(firstItem),
|
||||
modelWriter.createPositionAfter(lastItem),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
insertPosition = lastItem
|
||||
? modelWriter.createPositionAfter(lastItem)
|
||||
: this.editor.model.document.selection.getFirstPosition();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
insertPosition = args.insertPosition;
|
||||
}
|
||||
|
||||
modelWriter.insert(modelShortcode, insertPosition);
|
||||
modelWriter.setSelection(modelShortcode, 'on');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.editor.commands.add(commandName, new GravShortcodeCoreCommand(this.editor));
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
const Widget = window.nextgenEditor.classes.widget.class;
|
||||
const { toWidget, toWidgetEditable } = window.nextgenEditor.classes.widget.utils;
|
||||
|
||||
window.nextgenEditor.addPlugin('GravShortcodeCoreConvertersBlock', {
|
||||
requires: [Widget],
|
||||
init() {
|
||||
this.editor.model.schema.register('shortcode-block', {
|
||||
isBlock: true,
|
||||
isObject: true,
|
||||
allowWhere: '$block',
|
||||
allowContentOf: '$root',
|
||||
allowAttributes: [
|
||||
'name',
|
||||
'attributes',
|
||||
'class',
|
||||
],
|
||||
});
|
||||
|
||||
this.editor.conversion.for('upcast').elementToElement({
|
||||
view: 'shortcode-block',
|
||||
model(viewElement, { writer }) {
|
||||
return writer.createElement('shortcode-block', viewElement.getAttributes());
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.conversion.for('dataDowncast').elementToElement({
|
||||
model: 'shortcode-block',
|
||||
view(modelElement, { writer }) {
|
||||
return writer.createContainerElement('shortcode-block', modelElement.getAttributes());
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.conversion.for('editingDowncast').elementToElement({
|
||||
model: 'shortcode-block',
|
||||
view(modelElement, { writer }) {
|
||||
const container = writer.createContainerElement('shortcode-block', modelElement.getAttributes());
|
||||
return toWidget(container, writer);
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.model.schema.register('shortcode-block-editable', {
|
||||
isLimit: true,
|
||||
allowWhere: '$block',
|
||||
allowContentOf: '$root',
|
||||
});
|
||||
|
||||
this.editor.conversion.for('upcast').elementToElement({
|
||||
view: 'shortcode-block-editable',
|
||||
model: 'shortcode-block-editable',
|
||||
});
|
||||
|
||||
this.editor.conversion.for('dataDowncast').elementToElement({
|
||||
model: 'shortcode-block-editable',
|
||||
view: 'shortcode-block-editable',
|
||||
});
|
||||
|
||||
this.editor.conversion.for('editingDowncast').elementToElement({
|
||||
model: 'shortcode-block-editable',
|
||||
view(modelElement, { writer }) {
|
||||
const container = writer.createEditableElement('shortcode-block-editable', modelElement.getAttributes());
|
||||
return toWidgetEditable(container, writer);
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.model.schema.register('shortcode-block-readonly', {
|
||||
isLimit: true,
|
||||
allowWhere: '$block',
|
||||
allowContentOf: '$root',
|
||||
});
|
||||
|
||||
this.editor.conversion.elementToElement({
|
||||
view: 'shortcode-block-readonly',
|
||||
model: 'shortcode-block-readonly',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
window.nextgenEditor.addPlugin('GravShortcodeCoreConvertersInline', {
|
||||
requires: [Widget],
|
||||
init() {
|
||||
this.editor.model.schema.register('shortcode-inline', {
|
||||
isObject: true,
|
||||
isInline: true,
|
||||
allowWhere: '$text',
|
||||
allowContentOf: '$block',
|
||||
allowAttributes: [
|
||||
'name',
|
||||
'attributes',
|
||||
'class',
|
||||
],
|
||||
});
|
||||
|
||||
this.editor.conversion.for('upcast').elementToElement({
|
||||
view: 'shortcode-inline',
|
||||
model(viewElement, { writer }) {
|
||||
return writer.createElement('shortcode-inline', viewElement.getAttributes());
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.conversion.for('dataDowncast').elementToElement({
|
||||
model: 'shortcode-inline',
|
||||
view(modelElement, { writer }) {
|
||||
return writer.createContainerElement('shortcode-inline', modelElement.getAttributes());
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.conversion.for('editingDowncast').elementToElement({
|
||||
model: 'shortcode-inline',
|
||||
view(modelElement, { writer }) {
|
||||
const container = writer.createContainerElement('shortcode-inline', modelElement.getAttributes());
|
||||
return toWidget(container, writer);
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.model.schema.register('shortcode-inline-editable', {
|
||||
isLimit: true,
|
||||
allowWhere: '$text',
|
||||
allowContentOf: '$block',
|
||||
});
|
||||
|
||||
this.editor.conversion.for('upcast').elementToElement({
|
||||
view: 'shortcode-inline-editable',
|
||||
model: 'shortcode-inline-editable',
|
||||
});
|
||||
|
||||
this.editor.conversion.for('dataDowncast').elementToElement({
|
||||
model: 'shortcode-inline-editable',
|
||||
view: 'shortcode-inline-editable',
|
||||
});
|
||||
|
||||
this.editor.conversion.for('editingDowncast').elementToElement({
|
||||
model: 'shortcode-inline-editable',
|
||||
view(modelElement, { writer }) {
|
||||
const container = writer.createEditableElement('shortcode-inline-editable', modelElement.getAttributes());
|
||||
return toWidgetEditable(container, writer);
|
||||
},
|
||||
});
|
||||
|
||||
this.editor.model.schema.register('shortcode-inline-readonly', {
|
||||
isLimit: true,
|
||||
allowWhere: '$text',
|
||||
allowContentOf: '$block',
|
||||
});
|
||||
|
||||
this.editor.conversion.elementToElement({
|
||||
view: 'shortcode-inline-readonly',
|
||||
model: 'shortcode-inline-readonly',
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import displaySettings from './settings';
|
||||
|
||||
window.scDisplaySettings = function scDisplaySettings() {
|
||||
const domShortcode = this.closest('shortcode-block, shortcode-inline');
|
||||
|
||||
if (domShortcode) {
|
||||
displaySettings(domShortcode);
|
||||
}
|
||||
};
|
||||
|
||||
window.scBlockAddChildFromParent = function scBlockAddChildFromParent() {
|
||||
const { editors } = window.nextgenEditor;
|
||||
|
||||
const domShortcode = this.parentNode;
|
||||
const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift();
|
||||
|
||||
const name = domShortcode.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
|
||||
if (editor) {
|
||||
const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode);
|
||||
const modelShortcode = editor.editing.mapper.toModelElement(viewShortcode);
|
||||
|
||||
const domShortcodeBlockReadOnly = domShortcode.querySelector('shortcode-block-readonly');
|
||||
const viewShortcodeBlockReadOnly = editor.editing.view.domConverter.mapDomToView(domShortcodeBlockReadOnly);
|
||||
const modelShortcodeBlockReadOnly = editor.editing.mapper.toModelElement(viewShortcodeBlockReadOnly);
|
||||
|
||||
editor.model.change((modelWriter) => {
|
||||
const insertPosition = modelWriter.createPositionAt(modelShortcodeBlockReadOnly, 0);
|
||||
editor.execute(`shortcode_${shortcode.child.name}`, { insertPosition, modelParentShortcode: modelShortcode });
|
||||
|
||||
domShortcode.querySelector('.sc-add-child').classList.remove('sc-visible');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.scBlockAddChild = function scBlockAddChild(event, where) {
|
||||
const { editors } = window.nextgenEditor;
|
||||
|
||||
const domShortcode = this.parentNode;
|
||||
const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift();
|
||||
|
||||
const name = domShortcode.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
|
||||
if (editor) {
|
||||
const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode);
|
||||
const modelShortcode = editor.editing.mapper.toModelElement(viewShortcode);
|
||||
|
||||
editor.model.change((modelWriter) => {
|
||||
let modelParentShortcode = modelShortcode.parent;
|
||||
const insertPosition = modelWriter.createPositionAt(modelShortcode, where);
|
||||
|
||||
while (modelParentShortcode && modelParentShortcode.name !== 'shortcode-block') {
|
||||
modelParentShortcode = modelParentShortcode.parent;
|
||||
}
|
||||
|
||||
if (modelParentShortcode) {
|
||||
editor.execute(`shortcode_${shortcode.name}`, { insertPosition, modelParentShortcode });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.scBlockMoveChild = function scBlockMove(event, where) {
|
||||
const { editors } = window.nextgenEditor;
|
||||
|
||||
const domShortcode = this.parentNode;
|
||||
const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift();
|
||||
|
||||
if (editor) {
|
||||
const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode);
|
||||
const modelShortcode = editor.editing.mapper.toModelElement(viewShortcode);
|
||||
|
||||
const domSiblingShortcode = where === 'up'
|
||||
? domShortcode.previousSibling
|
||||
: domShortcode.nextSibling;
|
||||
|
||||
const viewSiblingShortcode = editor.editing.view.domConverter.mapDomToView(domSiblingShortcode);
|
||||
const modelSiblingShortcode = editor.editing.mapper.toModelElement(viewSiblingShortcode);
|
||||
|
||||
editor.model.change((modelWriter) => {
|
||||
modelWriter.move(modelWriter.createRangeOn(modelShortcode), modelSiblingShortcode, where === 'up' ? 'before' : 'after');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
window.nextgenEditor.addHook('hookInit', () => {
|
||||
Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => {
|
||||
shortcode.attributes = shortcode.attributes || {};
|
||||
|
||||
if (!shortcode.button) {
|
||||
shortcode.button = { label: shortcode.title };
|
||||
}
|
||||
|
||||
Object.values(shortcode.attributes).forEach((attribute) => {
|
||||
if (attribute.default === undefined) {
|
||||
attribute.default = '';
|
||||
}
|
||||
if (typeof attribute.default !== 'object') {
|
||||
attribute.default = { value: attribute.default };
|
||||
}
|
||||
if (attribute.shorthand === undefined) {
|
||||
attribute.shorthand = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (shortcode.type === 'block' && !shortcode.titlebar) {
|
||||
shortcode.titlebar = () => '';
|
||||
}
|
||||
if (!shortcode.content) {
|
||||
shortcode.content = () => '';
|
||||
}
|
||||
|
||||
if (shortcode.preserve) {
|
||||
if (shortcode.preserve.block) {
|
||||
window.nextgenEditor.addVariable('preserveBlockTags', shortcode.preserve.block);
|
||||
}
|
||||
|
||||
if (shortcode.preserve.inline) {
|
||||
window.nextgenEditor.addVariable('preserveInlineTags', shortcode.preserve.inline);
|
||||
}
|
||||
}
|
||||
|
||||
if (!shortcode.parent) {
|
||||
window.nextgenEditor.addButton(`shortcode_${shortcode.name}`, {
|
||||
command: `shortcode_${shortcode.name}`,
|
||||
...shortcode.button,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,278 @@
|
||||
shortcode-block {
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
#admin-main .ck.ck-editor__editable_inline > :first-child {
|
||||
margin-top: 16px;
|
||||
}
|
||||
#admin-main .ck.ck-editor__editable_inline > :last-child {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
#admin-main .ck-editor shortcode-block.ck-shortcode-child:hover {
|
||||
outline-color: #1f89e5;
|
||||
}
|
||||
|
||||
#admin-main .ck-editor shortcode-block > .sc-header > .sc-title > p,
|
||||
#admin-main .ck-editor shortcode-block > .sc-header > .sc-titlebar > p,
|
||||
#admin-main .ck-editor shortcode-block > .sc-header > .sc-settings > p,
|
||||
#admin-main .ck-editor shortcode-block > .sc-add > p,
|
||||
#admin-main .ck-editor shortcode-block > .sc-move > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-header {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ccc;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 8px 8px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-header > .sc-title > p > .sc-value {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-header > .sc-titlebar {
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-header > .sc-settings {
|
||||
color: #ffc83d;
|
||||
cursor: pointer;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
margin-left: auto;
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease, color .2s ease;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-header > .sc-settings:hover,
|
||||
shortcode-block.ck-shortcode-child > .sc-header > .sc-settings,
|
||||
shortcode-block.ck-widget_selected > .sc-header > .sc-settings {
|
||||
color: #1f89e5;
|
||||
}
|
||||
|
||||
shortcode-block.ck-widget_selected > .sc-header > .sc-settings:hover,
|
||||
shortcode-block.ck-shortcode-child > .sc-header > .sc-settings:hover {
|
||||
opacity: .5
|
||||
}
|
||||
|
||||
shortcode-block:hover > .sc-header > .sc-settings,
|
||||
shortcode-block.ck-widget_selected > .sc-header > .sc-settings {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-header > .sc-settings > p > svg {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
shortcode-block-editable,
|
||||
shortcode-block-readonly {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
shortcode-block-readonly > [data-cke-filler] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#admin-main .ck-editor shortcode-block-editable > :first-child {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
#admin-main .ck-editor shortcode-block.ck-shortcode-child .ck-widget__type-around__button_after,
|
||||
#admin-main .ck-editor shortcode-block.ck-shortcode-child .ck-widget__type-around__button_before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add-child {
|
||||
align-items: center;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
margin: 16px 0;
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add-child.sc-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
shortcode-block:hover > .sc-add-child,
|
||||
shortcode-block.ck-widget_selected > .sc-add-child {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add-child > p {
|
||||
align-items: center;
|
||||
background: #ffc83d;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
justify-content: center;
|
||||
margin: 0!important;
|
||||
transition: opacity .2s ease, background .2s ease;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add-child > p:hover,
|
||||
shortcode-block.ck-widget_selected > .sc-add-child > p {
|
||||
background: #1f89e5;
|
||||
}
|
||||
|
||||
shortcode-block.ck-widget_selected > .sc-add-child > p:hover {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add-child > p > svg {
|
||||
color: #fff;
|
||||
height: 20px;
|
||||
transition: opacity .2s ease;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add {
|
||||
left: 30px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
transition: opacity .2s ease;
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add-after {
|
||||
bottom: -11px;
|
||||
left: auto;
|
||||
right: 30px;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
shortcode-block:hover > .sc-add {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add > p {
|
||||
align-items: center;
|
||||
background: #1f89e5;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add > p > svg {
|
||||
color: #fff;
|
||||
height: 16px;
|
||||
transition: opacity .2s ease;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-add:hover > p > svg {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-move {
|
||||
background: #1f89e5;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: 20px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: -12px;
|
||||
transition: opacity .2s ease;
|
||||
width: 20px;
|
||||
top: 46px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-move-up {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
shortcode-block:first-child > .sc-move-up,
|
||||
shortcode-block:last-child > .sc-move-down {
|
||||
display: none;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-move-down {
|
||||
bottom: 4px;
|
||||
left: auto;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
shortcode-block:hover > .sc-move {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-move > p > svg {
|
||||
color: #fff;
|
||||
height: 20px;
|
||||
left: 1.5px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
transition: opacity .2s ease;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
shortcode-block > .sc-move:hover > p > svg {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
shortcode-inline {
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
display: inline-flex;
|
||||
margin-left: 2px;
|
||||
margin-right: 1px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
shortcode-inline-editable,
|
||||
shortcode-inline-readonly {
|
||||
display: inline;
|
||||
padding: 1px 4px 2px;
|
||||
}
|
||||
|
||||
shortcode-inline > .sc-content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
shortcode-inline > .sc-settings {
|
||||
align-items: center;
|
||||
border-left: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
padding: 0 2px;
|
||||
transition: opacity .2s ease, color .2s ease;
|
||||
}
|
||||
|
||||
shortcode-inline:hover > .sc-settings {
|
||||
color: #ffc83d;
|
||||
}
|
||||
|
||||
shortcode-inline > .sc-settings:hover,
|
||||
shortcode-inline.ck-widget_selected > .sc-settings {
|
||||
color: #1f89e5;
|
||||
}
|
||||
|
||||
shortcode-inline.ck-widget_selected > .sc-settings:hover {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
shortcode-inline > .sc-settings > svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import './command';
|
||||
import './converters';
|
||||
import './events';
|
||||
import './init';
|
||||
import './prerender';
|
||||
import './postsave';
|
||||
import './remove';
|
||||
import './render';
|
||||
import './save';
|
||||
import './main.css';
|
||||
@@ -0,0 +1,95 @@
|
||||
window.nextgenEditor.addHook('hookHTMLtoMarkdown', {
|
||||
weight: 50,
|
||||
handler(options, editor, input) {
|
||||
let output = input;
|
||||
|
||||
const realNames = Object.values(window.nextgenEditor.shortcodes).map((shortcode) => shortcode.realName)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
const openingRegexp = realNames
|
||||
.map((name) => `(\\[${name}[^\\]]*\\])`).join('|');
|
||||
|
||||
const hashMap = {};
|
||||
let shortcodeCounter = 1;
|
||||
|
||||
while (shortcodeCounter > 0) {
|
||||
shortcodeCounter = 0;
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => {
|
||||
const regexp = `(?<opening>\\[${shortcode.realName}[^\\]]*\\])(?<content>(((?!(${openingRegexp}|(\\[\\/${shortcode.realName}\\]))).)|\\n)*)(?<closing>\\[\\/${shortcode.realName}\\])`;
|
||||
|
||||
output = output.replace(new RegExp(regexp, 'g'), (...matches) => {
|
||||
shortcodeCounter += 1;
|
||||
|
||||
const hash = Math.random().toString(36).slice(2);
|
||||
hashMap[hash] = { shortcode, matches };
|
||||
|
||||
if (shortcode.child) {
|
||||
const childName = shortcode.child.realName;
|
||||
|
||||
Object.keys(hashMap).forEach((childHash) => {
|
||||
const childShortcode = hashMap[childHash].shortcode;
|
||||
|
||||
if (childShortcode === shortcode.child && childShortcode.name !== `${shortcode.realName}_${childName}` && matches[0].includes(childHash)) {
|
||||
hashMap[childHash].shortcode = window.nextgenEditor.shortcodes[`${shortcode.realName}_${childName}`];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hash;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
shortcodeCounter = 1;
|
||||
|
||||
while (shortcodeCounter > 0) {
|
||||
shortcodeCounter = 0;
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
Object.keys(hashMap).forEach((hash) => {
|
||||
if (!output.includes(hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
shortcodeCounter += 1;
|
||||
|
||||
const { shortcode, matches } = hashMap[hash];
|
||||
const groups = matches.pop();
|
||||
|
||||
if (shortcode.type === 'block') {
|
||||
let content = groups.content.replace(/^\n/, '').replace(/\n$/, '');
|
||||
|
||||
if (shortcode.child) {
|
||||
content = content.trim().split('\n').filter((line) => !!line).join('\n');
|
||||
content = `\n${content}\n`;
|
||||
}
|
||||
|
||||
if (shortcode.parent) {
|
||||
content = `\n${content}\n`;
|
||||
}
|
||||
|
||||
output = output.replace(hash, `${groups.opening}${content}${groups.closing}`);
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
output = output.replace(hash, matches[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => {
|
||||
const regexp = `(?<opening>\\[${shortcode.realName}[^\\]]*\\])\n(?<content>(((?!(${openingRegexp}|(\\[\\/${shortcode.realName}\\]))).))*)\n(?<closing>\\[\\/${shortcode.realName}\\])`;
|
||||
|
||||
output = output.replace(new RegExp(regexp, 'g'), (...matches) => {
|
||||
const groups = matches.pop();
|
||||
return `${groups.opening}${groups.content}${groups.closing}`;
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
return output;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
window.nextgenEditor.addHook('hookMarkdowntoHTML', {
|
||||
weight: -50,
|
||||
handler(options, input) {
|
||||
let output = input;
|
||||
|
||||
const realNames = Object.values(window.nextgenEditor.shortcodes).map((shortcode) => shortcode.realName)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
const openingRegexp = realNames
|
||||
.map((name) => `(\\[${name}[^\\]]*\\])`).join('|');
|
||||
|
||||
realNames.forEach((name) => {
|
||||
const regexp = `\\[${name}(?<attributes>(=| +).+?(?=/]))?\\/\\]`;
|
||||
|
||||
output = output.replace(new RegExp(regexp, 'g'), (...matches) => {
|
||||
const groups = matches.pop();
|
||||
|
||||
const attributes = groups.attributes.trim()
|
||||
? `${groups.attributes}`
|
||||
: '';
|
||||
|
||||
return `[${name}${attributes}][/${name}]`;
|
||||
});
|
||||
});
|
||||
|
||||
const hashMap = {};
|
||||
let shortcodeCounter = 1;
|
||||
|
||||
while (shortcodeCounter > 0) {
|
||||
shortcodeCounter = 0;
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => {
|
||||
const regexp = `(?<spaces_before> *)\\[${shortcode.realName}(?<attributes>(=| +)[^\\]]*)?\\](?<content>(((?!(${openingRegexp}|(\\[\\/${shortcode.realName}\\]))).)|\\n)*)\\[\\/${shortcode.realName}\\](?<spaces_after> *)`;
|
||||
|
||||
output = output.replace(new RegExp(regexp, 'g'), (...matches) => {
|
||||
shortcodeCounter += 1;
|
||||
|
||||
const hash = Math.random().toString(36).slice(2);
|
||||
hashMap[hash] = { shortcode, matches };
|
||||
|
||||
if (shortcode.child) {
|
||||
const childName = shortcode.child.realName;
|
||||
|
||||
Object.keys(hashMap).forEach((childHash) => {
|
||||
const childShortcode = hashMap[childHash].shortcode;
|
||||
|
||||
if (childShortcode === shortcode.child && childShortcode.name !== `${shortcode.realName}_${childName}` && matches[0].includes(childHash)) {
|
||||
hashMap[childHash].shortcode = window.nextgenEditor.shortcodes[`${shortcode.realName}_${childName}`];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return hash;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
shortcodeCounter = 1;
|
||||
|
||||
while (shortcodeCounter > 0) {
|
||||
shortcodeCounter = 0;
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
Object.keys(hashMap).forEach((hash) => {
|
||||
if (!output.includes(hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
shortcodeCounter += 1;
|
||||
|
||||
const { shortcode, matches } = hashMap[hash];
|
||||
const groups = matches.pop();
|
||||
|
||||
const spacesBefore = groups.spaces_before.replace(/ /g, ' ');
|
||||
const spacesAfter = groups.spaces_after.replace(/ /g, ' ');
|
||||
|
||||
if (shortcode.type === 'block') {
|
||||
let content = groups.content.trim();
|
||||
|
||||
if (groups.spaces_before.length) {
|
||||
content = content.replace(new RegExp(`^( ){${groups.spaces_before.length}}`, 'gm'), '');
|
||||
}
|
||||
|
||||
const replacement = `\n\n[${shortcode.name}${groups.attributes || ''}]\n\n${content}\n\n[/${shortcode.name}]\n\n`;
|
||||
|
||||
output = output.replace(new RegExp(`(\\n)?(\\n)?${hash}(\\n)?(\\n)?`), replacement);
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
output = output.replace(hash, `${spacesBefore}[${shortcode.name}${groups.attributes || ''}]${groups.content}[/${shortcode.name}]${spacesAfter}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
output = output.replace(/^\n\n/, '').replace(/\n\n$/, '');
|
||||
|
||||
return output;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
window.nextgenEditor.addPlugin('GravShortcodeCoreRemove', {
|
||||
init() {
|
||||
const deleteBackwardCommand = this.editor.commands.get('delete');
|
||||
const deleteForwardCommand = this.editor.commands.get('forwardDelete');
|
||||
|
||||
const preDelete = (event) => {
|
||||
const selectedElement = this.editor.model.document.selection.getSelectedElement();
|
||||
|
||||
if (selectedElement && selectedElement.name === 'shortcode-block') {
|
||||
const name = selectedElement.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
|
||||
if (shortcode.parent) {
|
||||
const viewShortcode = this.editor.editing.mapper.toViewElement(selectedElement);
|
||||
const domShortcode = this.editor.editing.view.domConverter.mapViewToDom(viewShortcode);
|
||||
const domParentShortcode = domShortcode.closest(`shortcode-block[name="${shortcode.parent.name}"]`);
|
||||
|
||||
event.childShortcodeDeleted = true;
|
||||
event.modelShortcodeBlockReadOnly = selectedElement.parent;
|
||||
event.domParentShortcode = domParentShortcode;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const postDelete = (event) => {
|
||||
if (event.childShortcodeDeleted) {
|
||||
const { domParentShortcode, modelShortcodeBlockReadOnly } = event;
|
||||
|
||||
const children = [...modelShortcodeBlockReadOnly.getChildren()];
|
||||
const scChildren = children.filter((child) => child.name === 'shortcode-block');
|
||||
const otherChildren = children.filter((child) => child.name !== 'shortcode-block');
|
||||
|
||||
setTimeout(() => {
|
||||
this.editor.model.change((modelWriter) => {
|
||||
otherChildren.forEach((modelChild) => {
|
||||
if (modelChild.name === 'paragraph' && modelChild.childCount === 0) {
|
||||
modelWriter.remove(modelChild);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (!scChildren.length) {
|
||||
domParentShortcode.querySelector('shortcode-block > .sc-add-child').classList.add('sc-visible');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
deleteBackwardCommand.on('execute', preDelete, { priority: 'highest' });
|
||||
deleteForwardCommand.on('execute', preDelete, { priority: 'highest' });
|
||||
|
||||
deleteBackwardCommand.on('execute', postDelete, { priority: 'lowest' });
|
||||
deleteForwardCommand.on('execute', postDelete, { priority: 'lowest' });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import uncollapse from './uncollapse';
|
||||
|
||||
window.nextgenEditor.addHook('hookMarkdowntoHTML', {
|
||||
weight: 50,
|
||||
handler(options, input) {
|
||||
let output = input;
|
||||
|
||||
let shortcodeCounter = 1;
|
||||
const openingRegexp = Object.keys(window.nextgenEditor.shortcodes).map((name) => `(\\[${name}[^\\]]*\\])`).join('|');
|
||||
|
||||
while (shortcodeCounter > 0) {
|
||||
shortcodeCounter = 0;
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => {
|
||||
const regexp = `(?<p1><p>)?\\[${shortcode.name}(?<attributes>(=| +)[^\\]]*)?\\](<\\/p>)?(?<content>(((?!(${openingRegexp}|(\\[\\/${shortcode.name}\\]))).)|\\n)*)\\[\\/${shortcode.name}\\](?<p2><\\/p>)?`;
|
||||
|
||||
output = output.replace(new RegExp(regexp, 'g'), (...matches) => {
|
||||
shortcodeCounter += 1;
|
||||
|
||||
const groups = matches.pop();
|
||||
|
||||
let content = shortcode.type === 'block'
|
||||
? groups.content.replace(/<p>$/, '')
|
||||
: groups.content;
|
||||
|
||||
const bbcode = Object.keys(shortcode.attributes).reduce((acc, attrName) => acc || (shortcode.attributes[attrName].bbcode && shortcode.attributes[attrName].shorthand && attrName), '');
|
||||
const innerHTMLAttribute = Object.keys(shortcode.attributes).reduce((acc, attrName) => acc || (shortcode.attributes[attrName].innerHTML && attrName), '');
|
||||
|
||||
let attrGroup = bbcode && groups.attributes && groups.attributes.startsWith('=')
|
||||
? `${bbcode}${groups.attributes}`
|
||||
: groups.attributes || '';
|
||||
|
||||
if (innerHTMLAttribute) {
|
||||
const innerHTML = shortcode.type === 'block'
|
||||
? content.replace(/^<p>/, '').replace(/<\/p>$/, '').replace(/^ $/, '')
|
||||
: content.replace(/^ $/, '');
|
||||
|
||||
attrGroup = attrGroup
|
||||
? `${attrGroup} ${innerHTMLAttribute}="${innerHTML}"`
|
||||
: `${innerHTMLAttribute}="${innerHTML}"`;
|
||||
|
||||
content = '';
|
||||
}
|
||||
|
||||
const domAttributes = new DOMParser().parseFromString(`<div ${attrGroup}></div>`, 'text/html').body.firstChild.attributes;
|
||||
|
||||
const attributes = Object.keys(shortcode.attributes).reduce((acc, attrName) => {
|
||||
const attribute = shortcode.attributes[attrName];
|
||||
|
||||
let attrValue = domAttributes.getNamedItem(attrName)
|
||||
? domAttributes.getNamedItem(attrName).value
|
||||
: attribute.default.value;
|
||||
|
||||
if (attribute.type === Boolean && domAttributes.getNamedItem(attrName)) {
|
||||
attrValue = domAttributes.getNamedItem(attrName) !== 'false';
|
||||
}
|
||||
|
||||
if (attribute.type === Number) {
|
||||
attrValue = +attrValue;
|
||||
}
|
||||
|
||||
acc[attrName] = attrValue;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
let replacement = '';
|
||||
|
||||
const attributesEncoded = encodeURIComponent(JSON.stringify(attributes));
|
||||
|
||||
if (shortcode.type === 'block') {
|
||||
replacement += `<shortcode-block name="${shortcode.name}" attributes="${attributesEncoded}">`;
|
||||
replacement += content;
|
||||
replacement += '</shortcode-block>';
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
replacement += groups.p1 || '';
|
||||
replacement += `<shortcode-inline name="${shortcode.name}" attributes="${attributesEncoded}">`;
|
||||
replacement += content;
|
||||
replacement += '</shortcode-inline>';
|
||||
replacement += groups.p2 || '';
|
||||
}
|
||||
|
||||
return replacement;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
output = uncollapse(output);
|
||||
|
||||
return output;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import collapse from './collapse';
|
||||
|
||||
window.nextgenEditor.addHook('hookHTMLtoMarkdown', {
|
||||
weight: -50,
|
||||
handler(options, editor, input) {
|
||||
let output = input;
|
||||
|
||||
output = collapse(output);
|
||||
|
||||
const domOutput = new DOMParser().parseFromString(output, 'text/html');
|
||||
|
||||
let domShortcode = domOutput.querySelector('shortcode-block, shortcode-inline');
|
||||
|
||||
while (domShortcode) {
|
||||
const name = domShortcode.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
const attributes = JSON.parse(decodeURIComponent(domShortcode.getAttribute('attributes')));
|
||||
|
||||
const innerHTMLAttribute = Object.keys(shortcode.attributes).reduce((acc, attrName) => acc || (shortcode.attributes[attrName].innerHTML && attrName), '');
|
||||
|
||||
const attrLine = Object.keys(shortcode.attributes).reduce((acc, attrName) => {
|
||||
const attribute = shortcode.attributes[attrName];
|
||||
|
||||
if (attribute.type === Boolean) {
|
||||
return attributes[attrName]
|
||||
? `${acc} ${attrName}`
|
||||
: acc;
|
||||
}
|
||||
|
||||
if (attributes[attrName] === attribute.default.value && !attribute.default.preserve) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (attribute.bbcode && attribute.shorthand) {
|
||||
return `="${attributes[attrName]}"${acc}`;
|
||||
}
|
||||
|
||||
if (attribute.innerHTML) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return `${acc} ${attrName}="${attributes[attrName]}"`;
|
||||
}, '');
|
||||
|
||||
if (shortcode.type === 'block') {
|
||||
if (domShortcode.innerHTML === '<p> </p>') {
|
||||
domShortcode.innerHTML = '';
|
||||
}
|
||||
|
||||
if (innerHTMLAttribute) {
|
||||
domShortcode.outerHTML = `<p>[${shortcode.realName}${attrLine}]${attributes[innerHTMLAttribute]}[/${shortcode.realName}]</p>`;
|
||||
} else if (domShortcode.innerHTML) {
|
||||
domShortcode.outerHTML = `<p>[${shortcode.realName}${attrLine}]</p>${domShortcode.innerHTML}<p>[/${shortcode.realName}]</p>`;
|
||||
} else {
|
||||
domShortcode.outerHTML = `<p>[${shortcode.realName}${attrLine} /]</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
if (domShortcode.innerHTML === ' ') {
|
||||
domShortcode.innerHTML = '';
|
||||
}
|
||||
|
||||
if (innerHTMLAttribute) {
|
||||
domShortcode.outerHTML = `[${shortcode.realName}${attrLine}]${attributes[innerHTMLAttribute]}[/${shortcode.realName}]`;
|
||||
} else if (domShortcode.innerHTML) {
|
||||
domShortcode.outerHTML = `[${shortcode.realName}${attrLine}]${domShortcode.innerHTML}[/${shortcode.realName}]`;
|
||||
} else {
|
||||
domShortcode.outerHTML = `[${shortcode.realName}${attrLine} /]`;
|
||||
}
|
||||
}
|
||||
|
||||
domShortcode = domOutput.querySelector('shortcode-block, shortcode-inline');
|
||||
}
|
||||
|
||||
output = domOutput.body.innerHTML;
|
||||
|
||||
return output;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
import collapse from './collapse';
|
||||
import uncollapse from './uncollapse';
|
||||
|
||||
const { showSettingsPopup } = window.nextgenEditor.exports;
|
||||
|
||||
export default function displaySettings(domShortcode) {
|
||||
const { editors } = window.nextgenEditor;
|
||||
const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift();
|
||||
|
||||
const name = domShortcode.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
const plugin = window.nextgenEditor.shortcodePlugins[shortcode.plugin];
|
||||
|
||||
if (editor) {
|
||||
const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode);
|
||||
let modelShortcode = editor.editing.mapper.toModelElement(viewShortcode);
|
||||
|
||||
const currentAttributes = JSON.parse(decodeURIComponent(domShortcode.getAttribute('attributes')));
|
||||
|
||||
const domDisplayPoint = shortcode.type === 'block'
|
||||
? domShortcode.querySelector('.sc-header > .sc-settings')
|
||||
: domShortcode;
|
||||
|
||||
const title = []
|
||||
.concat([
|
||||
(plugin && plugin.title) || '',
|
||||
(shortcode.parent && shortcode.parent.title) || '',
|
||||
shortcode.title || '',
|
||||
])
|
||||
.filter((item) => !!item)
|
||||
.join(' / ');
|
||||
|
||||
const argsForPopup = {
|
||||
title,
|
||||
domDisplayPoint,
|
||||
debounceDelay: 1000,
|
||||
attributes: shortcode.attributes,
|
||||
currentAttributes,
|
||||
parentAttributes: null,
|
||||
childAttributes: null,
|
||||
};
|
||||
|
||||
if (shortcode.parent) {
|
||||
const domParentShortcode = domShortcode.closest(`shortcode-block[name="${shortcode.parent.name}"]`);
|
||||
|
||||
argsForPopup.parentAttributes = domParentShortcode
|
||||
? JSON.parse(decodeURIComponent(domParentShortcode.getAttribute('attributes')))
|
||||
: {};
|
||||
}
|
||||
|
||||
if (shortcode.child) {
|
||||
argsForPopup.childAttributes = [];
|
||||
|
||||
const childNodes = [...domShortcode.querySelectorAll(`shortcode-block shortcode-block[name="${shortcode.child.name}"]`)];
|
||||
const deepChildNodes = [...domShortcode.querySelectorAll(`shortcode-block shortcode-block shortcode-block[name="${shortcode.child.name}"]`)];
|
||||
|
||||
childNodes
|
||||
.filter((domChildShortcode) => !deepChildNodes.includes(domChildShortcode))
|
||||
.forEach((domChildShortcode) => {
|
||||
const childAttributes = JSON.parse(decodeURIComponent(domChildShortcode.getAttribute('attributes')));
|
||||
argsForPopup.childAttributes.push(childAttributes);
|
||||
});
|
||||
}
|
||||
|
||||
argsForPopup.deleteItem = () => editor.execute('delete');
|
||||
|
||||
argsForPopup.changeAttributes = () => {
|
||||
editor.model.change((modelWriter) => {
|
||||
modelWriter.setAttribute('attributes', encodeURIComponent(JSON.stringify(currentAttributes)), modelShortcode);
|
||||
|
||||
const convertContext = shortcode.type === 'inline'
|
||||
? '$block'
|
||||
: '$root';
|
||||
|
||||
if (shortcode.parent) {
|
||||
const viewOldShortcode = editor.editing.mapper.toViewElement(modelShortcode);
|
||||
const domOldShortcode = editor.editing.view.domConverter.mapViewToDom(viewOldShortcode);
|
||||
|
||||
if (!domOldShortcode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domOldParentShortcode = domOldShortcode.parentNode.closest('shortcode-block');
|
||||
const viewOldParentShortcode = editor.editing.view.domConverter.mapDomToView(domOldParentShortcode);
|
||||
const modelOldParentShortcode = editor.editing.mapper.toModelElement(viewOldParentShortcode);
|
||||
|
||||
const childNodes = [...domOldParentShortcode.querySelectorAll('shortcode-block shortcode-block')];
|
||||
const deepChildNodes = [...domOldParentShortcode.querySelectorAll('shortcode-block shortcode-block shortcode-block')];
|
||||
|
||||
const childIndex = childNodes
|
||||
.filter((domChildShortcode) => !deepChildNodes.includes(domChildShortcode))
|
||||
.indexOf(domOldShortcode);
|
||||
|
||||
const insertPosition = modelWriter.createPositionBefore(modelOldParentShortcode);
|
||||
const modelOldParentClonedShortcode = modelWriter.cloneElement(modelOldParentShortcode);
|
||||
|
||||
const modelOldParentFragment = modelWriter.createDocumentFragment();
|
||||
modelWriter.append(modelOldParentClonedShortcode, modelOldParentFragment);
|
||||
|
||||
const viewOldParentClonedShortcode = editor.data.toView(modelOldParentFragment).getChild(0);
|
||||
const dataOldParentClonedShortcode = editor.data.processor.toData(viewOldParentClonedShortcode);
|
||||
|
||||
const dataNewParentShortcode = uncollapse(collapse(dataOldParentClonedShortcode));
|
||||
const viewNewParentShortcode = editor.data.processor.toView(dataNewParentShortcode).getChild(0);
|
||||
const modelNewParentShortcode = editor.data.toModel(viewNewParentShortcode, convertContext).getChild(0);
|
||||
|
||||
modelWriter.remove(modelOldParentShortcode);
|
||||
modelWriter.insert(modelNewParentShortcode, insertPosition);
|
||||
|
||||
setTimeout(() => {
|
||||
const viewParentShortcode = editor.editing.mapper.toViewElement(modelNewParentShortcode);
|
||||
const domParentShortcode = editor.editing.view.domConverter.mapViewToDom(viewParentShortcode);
|
||||
|
||||
const childNewNodes = [...domParentShortcode.querySelectorAll('shortcode-block shortcode-block')];
|
||||
const deepNewChildNodes = [...domParentShortcode.querySelectorAll('shortcode-block shortcode-block shortcode-block')];
|
||||
|
||||
const domNewShortcode = childNewNodes.filter((domChildShortcode) => !deepNewChildNodes.includes(domChildShortcode))[childIndex];
|
||||
const viewNewShortcode = editor.editing.view.domConverter.mapDomToView(domNewShortcode);
|
||||
const modelNewShortcode = editor.editing.mapper.toModelElement(viewNewShortcode);
|
||||
|
||||
editor.model.change((modelWriter2) => {
|
||||
modelWriter2.setSelection(modelNewShortcode, 'on');
|
||||
modelShortcode = modelNewShortcode;
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const insertPosition = modelWriter.createPositionBefore(modelShortcode);
|
||||
const modelOldShortcode = modelWriter.cloneElement(modelShortcode);
|
||||
|
||||
const modelOldFragment = modelWriter.createDocumentFragment();
|
||||
modelWriter.append(modelOldShortcode, modelOldFragment);
|
||||
|
||||
const viewOldShortcode = editor.data.toView(modelOldFragment).getChild(0);
|
||||
const dataOldShortcode = editor.data.processor.toData(viewOldShortcode);
|
||||
|
||||
const dataNewShortcode = uncollapse(collapse(dataOldShortcode));
|
||||
const viewNewShortcode = editor.data.processor.toView(dataNewShortcode).getChild(0);
|
||||
const modelNewShortcode = editor.data.toModel(viewNewShortcode, convertContext).getChild(0);
|
||||
|
||||
modelWriter.remove(modelShortcode);
|
||||
modelWriter.insert(modelNewShortcode, insertPosition);
|
||||
modelWriter.setSelection(modelNewShortcode, 'on');
|
||||
|
||||
modelShortcode = modelNewShortcode;
|
||||
});
|
||||
};
|
||||
|
||||
showSettingsPopup(argsForPopup);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
export default function uncollapse(input, args) {
|
||||
const domOutput = new DOMParser().parseFromString(input, 'text/html');
|
||||
[...domOutput.querySelectorAll('shortcode-block, shortcode-inline')].forEach((domShortcode) => {
|
||||
domShortcode.setAttribute('sc-rendered', false);
|
||||
});
|
||||
|
||||
let domShortcode = domOutput.querySelector('shortcode-block[sc-rendered], shortcode-inline[sc-rendered]');
|
||||
|
||||
while (domShortcode) {
|
||||
const name = domShortcode.getAttribute('name');
|
||||
const shortcode = window.nextgenEditor.shortcodes[name];
|
||||
const attributes = JSON.parse(decodeURIComponent(domShortcode.getAttribute('attributes')));
|
||||
|
||||
domShortcode.classList.add('ck-shortcode');
|
||||
domShortcode.classList.add(`ck-shortcode-${shortcode.type}`);
|
||||
domShortcode.removeAttribute('sc-rendered');
|
||||
|
||||
const argsForRender = {
|
||||
shortcode,
|
||||
attributes,
|
||||
innerHTML: domShortcode.innerHTML,
|
||||
parentAttributes: null,
|
||||
childAttributes: null,
|
||||
};
|
||||
|
||||
let innerHTML = '';
|
||||
|
||||
if (shortcode.type === 'block') {
|
||||
if (shortcode.parent) {
|
||||
domShortcode.classList.add('ck-shortcode-child');
|
||||
|
||||
const domParentShortcode = domShortcode.closest(`shortcode-block[name="${shortcode.parent.name}"]`);
|
||||
|
||||
argsForRender.parentAttributes = !args || !args.parentAttributes
|
||||
? domParentShortcode
|
||||
? JSON.parse(decodeURIComponent(domParentShortcode.getAttribute('attributes')))
|
||||
: {}
|
||||
: args.parentAttributes;
|
||||
}
|
||||
|
||||
if (shortcode.child) {
|
||||
argsForRender.childAttributes = [];
|
||||
domShortcode.classList.add('ck-shortcode-parent');
|
||||
|
||||
const childNodes = [...domShortcode.querySelectorAll(`shortcode-block shortcode-block[name="${shortcode.child.name}"]`)];
|
||||
const deepChildNodes = [...domShortcode.querySelectorAll(`shortcode-block shortcode-block shortcode-block[name="${shortcode.child.name}"]`)];
|
||||
|
||||
childNodes
|
||||
.filter((domChildShortcode) => !deepChildNodes.includes(domChildShortcode))
|
||||
.forEach((domChildShortcode) => {
|
||||
const childAttributes = JSON.parse(decodeURIComponent(domChildShortcode.getAttribute('attributes')));
|
||||
argsForRender.childAttributes.push(childAttributes);
|
||||
});
|
||||
}
|
||||
|
||||
/* eslint-disable indent, no-multi-spaces */
|
||||
innerHTML += '<div class="sc-header">';
|
||||
innerHTML += `<div class="sc-title">Shortcode - <span class="sc-value">${shortcode.title}</span></div>`;
|
||||
innerHTML += `<div class="sc-titlebar">${shortcode.titlebar(argsForRender)}</div>`;
|
||||
innerHTML += '<div class="sc-settings">';
|
||||
innerHTML += '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">';
|
||||
innerHTML += '<path d="M9 4.58V4c0-1.1.9-2 2-2h2a2 2 0 0 1 2 2v.58a8 8 0 0 1 1.92 1.11l.5-.29a2 2 0 0 1 2.74.73l1 1.74a2 2 0 0 1-.73 2.73l-.5.29a8.06 8.06 0 0 1 0 2.22l.5.3a2 2 0 0 1 .73 2.72l-1 1.74a2 2 0 0 1-2.73.73l-.5-.3A8 8 0 0 1 15 19.43V20a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-.58a8 8 0 0 1-1.92-1.11l-.5.29a2 2 0 0 1-2.74-.73l-1-1.74a2 2 0 0 1 .73-2.73l.5-.29a8.06 8.06 0 0 1 0-2.22l-.5-.3a2 2 0 0 1-.73-2.72l1-1.74a2 2 0 0 1 2.73-.73l.5.3A8 8 0 0 1 9 4.57zM7.88 7.64l-.54.51-1.77-1.02-1 1.74 1.76 1.01-.17.73a6.02 6.02 0 0 0 0 2.78l.17.73-1.76 1.01 1 1.74 1.77-1.02.54.51a6 6 0 0 0 2.4 1.4l.72.2V20h2v-2.04l.71-.2a6 6 0 0 0 2.41-1.4l.54-.51 1.77 1.02 1-1.74-1.76-1.01.17-.73a6.02 6.02 0 0 0 0-2.78l-.17-.73 1.76-1.01-1-1.74-1.77 1.02-.54-.51a6 6 0 0 0-2.4-1.4l-.72-.2V4h-2v2.04l-.71.2a6 6 0 0 0-2.41 1.4zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path>';
|
||||
innerHTML += '</svg>';
|
||||
innerHTML += '</div>';
|
||||
innerHTML += '</div>';
|
||||
innerHTML += '<div class="sc-content">';
|
||||
innerHTML += shortcode.content(argsForRender)
|
||||
.replace('{{content_editable}}', `<shortcode-block-editable>${domShortcode.innerHTML}</shortcode-block-editable>`)
|
||||
.replace('{{content_readonly}}', `<shortcode-block-readonly>${domShortcode.innerHTML}</shortcode-block-readonly>`);
|
||||
innerHTML += '</div>';
|
||||
|
||||
if (shortcode.child) {
|
||||
const visible = !domShortcode.innerHTML ? ' sc-visible' : '';
|
||||
innerHTML += `<div class="sc-add-child${visible}" title="Insert new ${shortcode.child.title}">`;
|
||||
innerHTML += '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">';
|
||||
innerHTML += '<path d="M17 11a1 1 0 0 1 0 2h-4v4a1 1 0 0 1-2 0v-4H7a1 1 0 0 1 0-2h4V7a1 1 0 0 1 2 0v4h4z"></path>';
|
||||
innerHTML += '</svg>';
|
||||
innerHTML += '</div>';
|
||||
}
|
||||
|
||||
if (shortcode.parent) {
|
||||
['before', 'after'].forEach((where) => {
|
||||
innerHTML += `<div class="sc-add sc-add-${where}" title="Insert new ${shortcode.title} ${where}">`;
|
||||
innerHTML += '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">';
|
||||
innerHTML += '<path d="M17 11a1 1 0 0 1 0 2h-4v4a1 1 0 0 1-2 0v-4H7a1 1 0 0 1 0-2h4V7a1 1 0 0 1 2 0v4h4z"></path>';
|
||||
innerHTML += '</svg>';
|
||||
innerHTML += '</div>';
|
||||
});
|
||||
|
||||
['up', 'down'].forEach((where) => {
|
||||
innerHTML += `<div class="sc-move sc-move-${where}" title="Move ${shortcode.title} ${where}">`;
|
||||
innerHTML += '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">';
|
||||
innerHTML += '<path fill-rule="evenodd" clip-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"></path>';
|
||||
innerHTML += '</svg>';
|
||||
innerHTML += '</div>';
|
||||
});
|
||||
}
|
||||
/* eslint-enable indent, no-multi-spaces */
|
||||
}
|
||||
|
||||
if (shortcode.type === 'inline') {
|
||||
/* eslint-disable indent, no-multi-spaces */
|
||||
innerHTML += '<span class="sc-content">';
|
||||
innerHTML += shortcode.content(argsForRender)
|
||||
.replace('{{content_editable}}', `<shortcode-inline-editable>${domShortcode.innerHTML}</shortcode-inline-editable>`)
|
||||
.replace('{{content_readonly}}', `<shortcode-inline-readonly>${domShortcode.innerHTML}</shortcode-inline-readonly>`);
|
||||
innerHTML += '</span>';
|
||||
innerHTML += '<span class="sc-settings">';
|
||||
innerHTML += '<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">';
|
||||
innerHTML += '<path d="M9 4.58V4c0-1.1.9-2 2-2h2a2 2 0 0 1 2 2v.58a8 8 0 0 1 1.92 1.11l.5-.29a2 2 0 0 1 2.74.73l1 1.74a2 2 0 0 1-.73 2.73l-.5.29a8.06 8.06 0 0 1 0 2.22l.5.3a2 2 0 0 1 .73 2.72l-1 1.74a2 2 0 0 1-2.73.73l-.5-.3A8 8 0 0 1 15 19.43V20a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-.58a8 8 0 0 1-1.92-1.11l-.5.29a2 2 0 0 1-2.74-.73l-1-1.74a2 2 0 0 1 .73-2.73l.5-.29a8.06 8.06 0 0 1 0-2.22l-.5-.3a2 2 0 0 1-.73-2.72l1-1.74a2 2 0 0 1 2.73-.73l.5.3A8 8 0 0 1 9 4.57zM7.88 7.64l-.54.51-1.77-1.02-1 1.74 1.76 1.01-.17.73a6.02 6.02 0 0 0 0 2.78l.17.73-1.76 1.01 1 1.74 1.77-1.02.54.51a6 6 0 0 0 2.4 1.4l.72.2V20h2v-2.04l.71-.2a6 6 0 0 0 2.41-1.4l.54-.51 1.77 1.02 1-1.74-1.76-1.01.17-.73a6.02 6.02 0 0 0 0-2.78l-.17-.73 1.76-1.01-1-1.74-1.77 1.02-.54-.51a6 6 0 0 0-2.4-1.4l-.72-.2V4h-2v2.04l-.71.2a6 6 0 0 0-2.41 1.4zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path>';
|
||||
innerHTML += '</svg>';
|
||||
innerHTML += '</span>';
|
||||
/* eslint-enable indent, no-multi-spaces */
|
||||
}
|
||||
|
||||
domShortcode.innerHTML = innerHTML;
|
||||
domShortcode = domOutput.querySelector('shortcode-block[sc-rendered], shortcode-inline[sc-rendered]');
|
||||
}
|
||||
|
||||
return domOutput.body.innerHTML;
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const { target } = event;
|
||||
const list = ['sc-settings', 'sc-move', 'sc-add', 'sc-add-child'];
|
||||
const action = { element: null, className: null };
|
||||
const isAction = list.some((item) => {
|
||||
let match = target.classList.contains(item);
|
||||
|
||||
if (match) {
|
||||
action.element = target;
|
||||
action.className = item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
match = target.closest(`.${item}`);
|
||||
if (match) {
|
||||
action.element = match;
|
||||
action.className = item;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (isAction) {
|
||||
switch (action.className) {
|
||||
case 'sc-move':
|
||||
window.scBlockMoveChild.call(action.element, event, action.element.classList.contains('sc-move-up') ? 'up' : 'down');
|
||||
break;
|
||||
case 'sc-add':
|
||||
window.scBlockAddChild.call(action.element, event, action.element.classList.contains('sc-add-before') ? 'before' : 'after');
|
||||
break;
|
||||
case 'sc-add-child':
|
||||
window.scBlockAddChildFromParent.call(action.element, event);
|
||||
break;
|
||||
case 'sc-settings':
|
||||
default:
|
||||
window.scDisplaySettings.call(action.element, event);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user