\s*
\s*<\/div>)+/, '').trim(),
+ htmlToPlain = html => rl.Utils.htmlToPlain(html).trim(),
+ plainToHtml = text => rl.Utils.plainToHtml(text),
+
+ getFragmentOfChildren = parent => {
+ let frag = doc.createDocumentFragment();
+ frag.append(...parent.childNodes);
+ return frag;
+ },
+
+ /**
+ * @param {Array} data
+ * @param {String} prop
+ */
+ getByProp = (data, prop) => {
+ for (let i = 0; i < data.length; i++) {
+ const outer = data[i];
+ if (outer.hasOwnProperty(prop)) {
+ return outer;
+ }
+ if (outer.items && Array.isArray(outer.items)) {
+ const item = outer.items.find(item => item.prop === prop);
+ if (item) {
+ return item;
+ }
+ }
+ }
+ throw new Error('item with prop ' + prop + ' not found');
+ },
+
+ SquireDefaultConfig = {
+ /*
+ addLinks: true // allow_smart_html_links
+ */
+ sanitizeToDOMFragment: (html) => {
+ tpl.innerHTML = (html || '')
+ .replace(/<\/?(BODY|HTML)[^>]*>/gi, '')
+ .replace(//g, '')
+ .replace(/
]*>\s*<\/span>/gi, '')
+ .trim();
+ tpl.querySelectorAll('a:empty,span:empty').forEach(el => el.remove());
+ return tpl.content;
+ }
+ },
+
+ pasteSanitizer = (event) => {
+ const frag = event.detail.fragment;
+ frag.querySelectorAll('a:empty,span:empty').forEach(el => el.remove());
+ frag.querySelectorAll(removeElements).forEach(el => el.remove());
+ frag.querySelectorAll('*').forEach(el => {
+ if (!el.matches(allowedElements)) {
+ el.replaceWith(getFragmentOfChildren(el));
+ } else if (el.hasAttributes()) {
+ [...el.attributes].forEach(attr => {
+ let name = attr.name.toLowerCase();
+ if (!allowedAttributes.includes(name)) {
+ el.removeAttribute(name);
+ }
+ });
+ }
+ });
+ },
+
+ pasteImageHandler = (e, squire) => {
+
+ const items = [...e.detail.clipboardData.items];
+ const imageItems = items.filter((item) => /image/.test(item.type));
+ if (!imageItems.length) {
+ return false;
+ }
+ let reader = new FileReader();
+ reader.onload = (loadEvent) => {
+ squire.insertImage(loadEvent.target.result);
+ };
+ reader.readAsDataURL(imageItems[0].getAsFile());
+ };
+
+
+ class CompactComposer {
+ constructor(container) {
+ const
+ plain = createElement('textarea'),
+ wysiwyg = createElement('div'),
+ toolbar = createElement('div'),
+ squire = new win.Squire2(wysiwyg, SquireDefaultConfig);
+
+ this.container = container;
+
+ plain.className = 'squire-plain';
+ wysiwyg.className = 'squire-wysiwyg';
+ wysiwyg.dir = 'auto';
+ this.mode = ''; // 'plain' | 'wysiwyg'
+ this.squire = squire;
+ this.plain = plain;
+ this.wysiwyg = wysiwyg;
+ this.toolbar = toolbar;
+
+ toolbar.className = 'squire-toolbar btn-toolbar';
+ const actions = this.#makeActions(squire, toolbar);
+
+ this.squire.addEventListener('willPaste', pasteSanitizer);
+ this.squire.addEventListener('pasteImage', (e) => {
+ pasteImageHandler(e, squire);
+ });
+
+// squire.addEventListener('focus', () => shortcuts.off());
+// squire.addEventListener('blur', () => shortcuts.on());
+
+ container.append(toolbar, wysiwyg, plain);
+
+ const fontFamilySelect = getByProp(actions, 'fontFamily').element;
+
+ const fontSizeAction = getByProp(actions, 'fontSize');
+
+ /**
+ * @param {string} fontName
+ * @return {string}
+ */
+ const normalizeFontName = (fontName) => fontName.trim().replace(/(^["']*|["']*$)/g, '').trim().toLowerCase();
+
+ /** @type {string[]} - lower cased array of available font families*/
+ const fontFamiliesLowerCase = Object.values(fontFamilySelect.options).map(option => option.value.toLowerCase());
+
+ /**
+ * A theme might have CSS like div.squire-wysiwyg[contenteditable="true"] {
+ * font-family: 'Times New Roman', Times, serif; }
+ * so let's find the best match squire.getRoot()'s font
+ * it will also help to properly handle generic font names like 'sans-serif'
+ * @type {number}
+ */
+ let defaultFontFamilyIndex = 0;
+ const squireRootFonts = getComputedStyle(squire.getRoot()).fontFamily.split(',').map(normalizeFontName);
+ fontFamiliesLowerCase.some((family, index) => {
+ const matchFound = family.split(',').some(availableFontName => {
+ const normalizedFontName = normalizeFontName(availableFontName);
+ return squireRootFonts.some(squireFontName => squireFontName === normalizedFontName);
+ });
+ if (matchFound) {
+ defaultFontFamilyIndex = index;
+ }
+ return matchFound;
+ });
+
+ /**
+ * Instead of comparing whole 'font-family' strings,
+ * we are going to look for individual font names, because we might be
+ * editing a Draft started in another email client for example
+ *
+ * @type {Object.}
+ */
+ const fontNamesMap = {};
+ /**
+ * @param {string} fontFamily
+ * @param {number} index
+ */
+ const processFontFamilyString = (fontFamily, index) => {
+ fontFamily.split(',').forEach(fontName => {
+ const key = normalizeFontName(fontName);
+ if (fontNamesMap[key] === undefined) {
+ fontNamesMap[key] = index;
+ }
+ });
+ };
+ // first deal with the default font family
+ processFontFamilyString(fontFamiliesLowerCase[defaultFontFamilyIndex], defaultFontFamilyIndex);
+ // and now with the rest of the font families
+ fontFamiliesLowerCase.forEach((fontFamily, index) => {
+ if (index !== defaultFontFamilyIndex) {
+ processFontFamilyString(fontFamily, index);
+ }
+ });
+
+ // -----
+
+ let ignoreNextSelectEvent = false;
+
+ squire.addEventListener('pathChange', e => {
+
+ const tokensMap = this.buildTokensMap(e.detail);
+
+ if (tokensMap.has('__selection__')) {
+ ignoreNextSelectEvent = false;
+ return;
+ }
+ this.indicators.forEach((indicator) => {
+ indicator.element.classList.toggle('active', indicator.selectors.some(selector => tokensMap.has(selector)));
+ });
+
+ let familySelectedIndex = defaultFontFamilyIndex;
+ const fontFamily = tokensMap.get('__font_family__');
+ if (fontFamily) {
+ familySelectedIndex = -1; // show empty select if we don't know the font
+ const fontNames = fontFamily.split(',');
+ for (let i = 0; i < fontNames.length; i++) {
+ const index = fontNamesMap[normalizeFontName(fontNames[i])];
+ if (index !== undefined) {
+ familySelectedIndex = index;
+ break;
+ }
+ }
+ }
+ fontFamilySelect.selectedIndex = familySelectedIndex;
+
+ let sizeSelectedIndex = fontSizeAction.defaultValueIndex;
+ const fontSize = tokensMap.get('__font_size__');
+ if (fontSize) {
+ // -1 is ok because it will just show a blank