chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
import grapesjs from 'grapesjs';
import grapesjsmjml from 'grapesjs-mjml';
import grapesjsnewsletter from 'grapesjs-preset-newsletter';
import grapesjswebpage from 'grapesjs-preset-webpage';
import grapesjsblocksbasic from 'grapesjs-blocks-basic';
import grapesjscomponentcountdown from 'grapesjs-component-countdown';
import grapesjsnavbar from 'grapesjs-navbar';
import grapesjscustomcode from 'grapesjs-custom-code';
import grapesjstouch from 'grapesjs-touch';
import grapesjstuiimageeditor from 'grapesjs-tui-image-editor';
import grapesjsstylebg from 'grapesjs-style-bg';
import grapesjspostcss from 'grapesjs-parser-postcss';
import contentService from 'grapesjs-preset-mautic/dist/content.service';
import grapesjsmautic from 'grapesjs-preset-mautic';
import editorFontsService from 'grapesjs-preset-mautic/dist/editorFonts/editorFonts.service';
import 'grapesjs-plugin-ckeditor5';
import StorageService from "./storage.service";
// for local dev
// import contentService from '../../../../../../grapesjs-preset-mautic/src/content.service';
// import grapesjsmautic from '../../../../../../grapesjs-preset-mautic/src';
import CodeModeButton from './codeMode/codeMode.button';
import MjmlService from 'grapesjs-preset-mautic/dist/mjml/mjml.service';
export default class BuilderService {
editor;
assets;
uploadPath;
deletePath;
storageService;
/**
* @param {*} assets
*/
constructor(assets) {
if (!assets.conf.uploadPath) {
throw Error('No uploadPath found');
}
if (!assets.conf.deletePath) {
throw Error('No deletePath found');
}
this.assets = assets.files;
this.uploadPath = assets.conf.uploadPath;
this.deletePath = assets.conf.deletePath;
}
/**
* Initialize GrapesJsBuilder
*
* @param object
*/
setListeners() {
if (!this.editor) {
throw Error('No editor found');
}
// Why would we not want to keep the history?
//
// this.editor.on('load', () => {
// const um = this.editor.UndoManager;
// // Clear stack of undo/redo
// um.clear();
// });
const keymaps = this.editor.Keymaps;
let allKeymaps;
if (mauticEditorFonts) {
this.editor.on('load', () => editorFontsService.loadEditorFonts(this.editor));
}
this.editor.on('modal:open', () => {
// Save all keyboard shortcuts
allKeymaps = { ...keymaps.getAll() };
// Remove keyboard shortcuts to prevent launch behind popup
keymaps.removeAll();
});
this.editor.on('modal:close', () => {
// ReMap keyboard shortcuts on modal close
Object.keys(allKeymaps).map((objectKey) => {
const shortcut = allKeymaps[objectKey];
keymaps.add(shortcut.id, shortcut.keys, shortcut.handler);
return keymaps;
});
});
this.editor.on('asset:remove', (response) => {
// Delete file on server
mQuery.ajax({
url: this.deletePath,
data: { filename: response.getFilename() },
});
});
const triggerBuilderHide = () => {
// trigger hide event on DOM element
mQuery('.builder').trigger('builder:hide', [this.editor]);
// trigger hide event on editor instance
this.editor.trigger('hide');
};
this.editor.on('run:mautic-editor-page-html-close', triggerBuilderHide);
this.editor.on('run:mautic-editor-email-html-close', triggerBuilderHide);
this.editor.on('run:mautic-editor-email-mjml-close', triggerBuilderHide);
// add offset to flashes container for better UI visibility when builder is on
this.editor.on('show', () => mQuery('#flashes').addClass('alert-offset'));
this.editor.on('hide', () => mQuery('#flashes').removeClass('alert-offset'));
}
/**
* Initialize the grapesjs build in the
* correct mode
*/
initGrapesJS(object) {
// grapesjs-custom-plugins: add globally defined mautic-grapesjs-plugins using name as pluginId for the plugin-function
if (window.MauticGrapesJsPlugins) {
window.MauticGrapesJsPlugins.forEach((item) => {
if (!item.name) {
console.warn('A name is required for Mautic-GrapesJs plugins in window.MauticGrapesJsPlugins. Registration skipped!');
return;
}
if (typeof item.plugin !== 'function') {
console.warn('The Mautic-GrapesJs plugin must be a function in window.MauticGrapesJsPlugins. Registration skipped!');
return;
}
grapesjs.plugins.add(item.name, item.plugin);
});
}
// disable mautic global shortcuts
Mousetrap.reset();
if (object === 'page') {
this.editor = this.initPage();
} else if (object === 'emailform') {
if (MjmlService.getOriginalContentMjml()) {
this.editor = this.initEmailMjml();
} else {
this.editor = this.initEmailHtml();
}
} else {
throw Error(`Not supported builder type: ${object}`);
}
// add code mode button
// @todo: only show button if configured: sourceEdit: 1,
const codeModeButton = new CodeModeButton(this.editor);
codeModeButton.addCommand();
codeModeButton.addButton();
this.storageService = new StorageService(this.editor, object);
this.overrideCustomRteDisable();
this.setListeners();
}
static getMauticConf(mode) {
return {
mode,
};
}
static getCkeConf(tokenCallback) {
const ckEditorToolbarOptions = ['undo', 'redo', '|', 'bold','italic', 'underline','strikethrough', '|', 'fontSize','fontFamily','fontColor','fontBackgroundColor', '|' ,'alignment','outdent', 'indent', '|', 'blockQuote', 'insertTable', '|', 'bulletedList','numberedList', '|', 'link', '|', 'TokenPlugin'];
return {
ckeditor_module: `${mauticBaseUrl}assets/ckeditor/build/ckeditor.js`,
options: Mautic.GetCkEditorConfigOptions(ckEditorToolbarOptions, tokenCallback)
};
}
/**
* Initialize the builder in the landingapge mode
*/
initPage() {
// Launch GrapesJS with body part
this.editor = grapesjs.init({
clearOnRender: true,
container: '.builder-panel',
components: contentService.getOriginalContentHtml().body.innerHTML,
height: '100%',
canvas: {
styles: contentService.getStyles(),
},
storageManager: false, // https://grapesjs.com/docs/modules/Storage.html#basic-configuration
assetManager: this.getAssetManagerConf(),
styleManager: {
clearProperties: true, // Temp fix https://github.com/artf/grapesjs-preset-webpage/issues/27
},
plugins: [
// partially copied from: https://github.com/GrapesJS/grapesjs/blob/gh-pages/demo.html
grapesjswebpage,
grapesjspostcss,
grapesjsmautic,
'gjs-plugin-ckeditor5',
grapesjsblocksbasic,
grapesjscomponentcountdown,
grapesjsnavbar,
grapesjscustomcode,
grapesjstouch,
grapesjspostcss,
grapesjstuiimageeditor,
grapesjsstylebg,
...BuilderService.getPluginNames('page'), // grapesjs-custom-plugins: load custom plugins by their name
],
pluginsOpts: {
[grapesjswebpage]: {
formsOpts: false,
useCustomTheme: false,
},
grapesjsmautic: BuilderService.getMauticConf('page-html'),
'gjs-plugin-ckeditor5': BuilderService.getCkeConf('page:getBuilderTokens'),
...BuilderService.getPluginOptions('page'), // grapesjs-custom-plugins: add the plugin-options
},
});
this.moveBlocksPage();
return this.editor;
}
initEmailMjml() {
const components = MjmlService.getOriginalContentMjml();
// validate
MjmlService.mjmlToHtml(components);
const styles = [
`${mauticBaseUrl}plugins/GrapesJsBuilderBundle/Assets/library/js/grapesjs-editor.css`
];
this.editor = grapesjs.init({
selectorManager: {
componentFirst: true,
},
avoidInlineStyle: false, // TEMP: fixes issue with disappearing inline styles
forceClass: false, // create new styles if there are some already on the element: https://github.com/GrapesJS/grapesjs/issues/1531
clearOnRender: true,
container: '.builder-panel',
height: '100%',
canvas: {
styles,
},
domComponents: {
// disable all except link components
disableTextInnerChilds: (child) => !child.is('link'), // https://github.com/GrapesJS/grapesjs/releases/tag/v0.21.2
},
storageManager: false,
assetManager: this.getAssetManagerConf(),
plugins: [grapesjsmjml, grapesjspostcss, grapesjsmautic, 'gjs-plugin-ckeditor5', ...BuilderService.getPluginNames('email-mjml')],
pluginsOpts: {
[grapesjsmjml]: {
hideSelector: false,
custom: false,
useCustomTheme: false,
},
grapesjsmautic: BuilderService.getMauticConf('email-mjml'),
'gjs-plugin-ckeditor5': BuilderService.getCkeConf('email:getBuilderTokens'),
...BuilderService.getPluginOptions('email-mjml'),
},
});
this.unsetComponentVoidTypes(this.editor);
this.editor.setComponents(components);
// Reinitialize the content after parsing MJML.
// This can be removed once the issue with self-closing tags is resolved in grapesjs-mjml.
// See: https://github.com/GrapesJS/mjml/issues/149
const parsedContent = MjmlService.getEditorMjmlContent(this.editor);
this.editor.setComponents(parsedContent);
this.editor.BlockManager.get('mj-button').set({
content: '<mj-button href="https://">Button</mj-button>',
});
return this.editor;
}
unsetComponentVoidTypes(editor) {
// Support for self-closing components is temporarily disabled due to parsing issues with mjml tags.
// Browsers only recognize explicit self-closing tags like <img /> and <br />, leading to rendering problems.
// This can be reverted once the issue with self-closing tags is resolved in grapesjs-mjml.
// See: https://github.com/GrapesJS/mjml/issues/149
const voidTypes = ['mj-image', 'mj-divider', 'mj-font'];
voidTypes.forEach(function(component) {
editor.DomComponents.addType(component, {
model: {
defaults: {
void: false
},
toHTML() {
const tag = this.get('tagName');
const attr = this.getAttrToHTML();
const content = this.get('content');
let strAttr = '';
for (let prop in attr) {
const val = attr[prop];
const hasValue = typeof val !== 'undefined' && val !== '';
strAttr += hasValue ? ` ${prop}="${val}"` : '';
}
let html = `<${tag}${strAttr}>${content}</${tag}>`;
// Add the components after the closing tag
const componentsHtml = this.get('components')
.map(model => model.toHTML())
.join('');
return html + componentsHtml;
},
}
});
});
}
initEmailHtml() {
const components = contentService.getOriginalContentHtml().body.innerHTML;
if (!components) {
throw new Error('no components');
}
const styles = [
`${mauticBaseUrl}plugins/GrapesJsBuilderBundle/Assets/library/js/grapesjs-editor.css`
];
// Launch GrapesJS with body part
this.editor = grapesjs.init({
clearOnRender: true,
container: '.builder-panel',
components,
height: '100%',
canvas: {
styles,
},
storageManager: false,
assetManager: this.getAssetManagerConf(),
plugins: [grapesjsnewsletter, grapesjspostcss, grapesjsmautic, 'gjs-plugin-ckeditor5', ...BuilderService.getPluginNames('email-html')],
pluginsOpts: {
grapesjsnewsletter: {
useCustomTheme: false,
},
grapesjsmautic: BuilderService.getMauticConf('email-html'),
'gjs-plugin-ckeditor5': BuilderService.getCkeConf('email:getBuilderTokens'),
...BuilderService.getPluginOptions('email-html'),
},
});
// add a Mautic custom block Button
this.editor.BlockManager.get('button').set({
content:
'<a href="#" target="_blank" style="display:inline-block;text-decoration:none;border-color:#4e5d9d;border-width: 10px 20px;border-style:solid; text-decoration: none; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; background-color: #4e5d9d; display: inline-block;font-size: 16px; color: #ffffff; ">\n' +
'Button\n' +
'</a>',
});
return this.editor;
}
/**
* Return the names of dynamically added plugins
* @param context
* @returns string[]
*/
static getPluginNames(context) {
let plugins = [];
if (window.MauticGrapesJsPlugins) {
window.MauticGrapesJsPlugins.forEach((item) => {
if (item.name) {
if (!item.context || !Array.isArray(item.context) || item.context.length === 0) {
// if no context is given, the plugin is always added
plugins.push(item.name);
} else {
// check if the plugin should be added for the current editor context
item.context.forEach((pluginContext) => {
if (pluginContext === context) {
plugins.push(item.name);
}
})
}
}
});
}
return plugins;
}
/**
* Return the options of dynamically added plugins
* @param context
* @returns object[]
*/
static getPluginOptions(context) {
let pluginOptions = {};
if (window.MauticGrapesJsPlugins) {
window.MauticGrapesJsPlugins.forEach((item) => {
if (!item.context || !Array.isArray(item.context) || item.context.length === 0) {
// if no context is given, the plugin is always added
pluginOptions[item.name] = item.pluginOptions ?? {};
} else {
// check if the plugin should be added for the current editor context
item.context.forEach((pluginContext) => {
if (pluginContext === context) {
pluginOptions[item.name] = item.pluginOptions ?? {};
}
})
}
});
}
return pluginOptions;
}
/**
* Manage button loading indicator
*
* @param activate - true or false
*/
static setupButtonLoadingIndicator(activate) {
const builderButton = mQuery('.btn-builder');
const saveButton = mQuery('.btn-save');
const applyButton = mQuery('.btn-apply');
if (activate) {
Mautic.activateButtonLoadingIndicator(builderButton);
Mautic.activateButtonLoadingIndicator(saveButton);
Mautic.activateButtonLoadingIndicator(applyButton);
} else {
Mautic.removeButtonLoadingIndicator(builderButton);
Mautic.removeButtonLoadingIndicator(saveButton);
Mautic.removeButtonLoadingIndicator(applyButton);
}
}
/**
* Configure the Asset Manager for all modes
* @link https://grapesjs.com/docs/modules/Assets.html#configuration
*/
getAssetManagerConf() {
return {
assets: this.assets,
noAssets: Mautic.translate('grapesjsbuilder.assetManager.noAssets'),
upload: this.uploadPath,
uploadName: 'files',
multiUpload: 1,
embedAsBase64: false,
openAssetsOnDrop: 1,
autoAdd: 1,
headers: { 'X-CSRF-Token': mauticAjaxCsrf }, // global variable
};
}
getEditor() {
return this.editor;
}
// https://github.com/artf/grapesjs-mjml/issues/193
overrideCustomRteDisable() {
const richTextEditor = this.editor.RichTextEditor;
if (!richTextEditor) {
console.error('No RichTextEditor found');
return;
}
if (richTextEditor.customRte) {
richTextEditor.customRte.disable = (el, rte) => {
el.contentEditable = false;
if (rte && rte.focusManager) {
rte.focusManager.blur(true);
}
if (rte && typeof rte.destroy == 'function') {
rte.destroy();
}
};
}
}
/**
* Move the blocks and categories in the sidebar
*/
moveBlocksPage() {
const blocks = this.editor.BlockManager.getAll();
blocks.map(block => {
// columns go into a new category, at the top
if(block.attributes.id.indexOf('column') !== -1) {
this.editor.BlockManager.get(block.attributes.id).set('category', {
label:"Sections",
order: -1
});
}
// 'Blocks' category goes after 'Basic'
if(block.attributes.category === 'Basic') {
this.editor.BlockManager.get(block.attributes.id).set('category', {
label:"Basic",
order: -1
});
}
});
}
/**
* Generate assets list from GrapesJs
*/
// getAssetsList() {
// const assetManager = this.editor.AssetManager;
// const assets = assetManager.getAll();
// const assetsList = [];
// assets.forEach((asset) => {
// if (asset.get('type') === 'image') {
// assetsList.push({
// src: asset.get('src'),
// width: asset.get('width'),
// height: asset.get('height'),
// });
// } else {
// assetsList.push(asset.get('src'));
// }
// });
// return assetsList;
// }
}