diff --git a/src/core/utils.js b/src/core/utils.js index a26557913..0d1f37537 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -1,5 +1,6 @@ import $ from "jquery"; import logging from "@patternslib/patternslib/src/core/logging"; +import _ from "lodash"; const logger = logging.getLogger("core utils"); @@ -438,6 +439,78 @@ const resolveIcon = async function (name) { } }; +/** + * This is for avoiding CSP issues with underscore's template + * + * More details: https://github.com/plone/mockup/issues/1306 + * + * Implementation was taken from https://github.com/silvermine/undertemplate/blob/master/src/index.js + * + */ +var template = function (text, userSettings) { + const ESCAPE_ENTITIES = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', // eslint-disable-line quotes + '`': '`', + }; + + const DEFAULT_SETTINGS = { + escape: /<%-([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + }; + + let parts = [], + index = 0, + settings = _.defaults({}, userSettings, DEFAULT_SETTINGS), + regExpPattern, matcher; + + regExpPattern = [ + settings.escape.source, + settings.interpolate.source, + ]; + matcher = new RegExp(regExpPattern.join('|') + '|$', 'g'); + + text.replace(matcher, function(match, escape, interpolate, offset) { + parts.push(text.slice(index, offset)); + index = offset + match.length; + + if (escape) { + parts.push(function(data) { + return escapeHTML(getValue(escape, data)); + }); + } else if (interpolate) { + parts.push(getValue.bind(null, interpolate)); + } + }); + + return function(data) { + return _.reduce(parts, function(str, part) { + return str + (_.isFunction(part) ? part(data) : part); + }, ''); + }; + + function escapeHTML(str) { + let pattern = '(?:' + _.keys(ESCAPE_ENTITIES).join('|') + ')', + testRegExp = new RegExp(pattern), + replaceRegExp = new RegExp(pattern, 'g'); + + if (testRegExp.test(str)) { + return str.replace(replaceRegExp, function(match) { + return ESCAPE_ENTITIES[match]; + }); + } + + return str; + } + + function getValue(path, data) { + return _.get(data, _.trim(path), ''); + } +} + export default { bool: bool, escapeHTML: escapeHTML, @@ -453,4 +526,5 @@ export default { resolveIcon: resolveIcon, setId: setId, storage: storage, + template: template, }; diff --git a/src/pat/relateditems/relateditems.js b/src/pat/relateditems/relateditems.js index 49a958740..252e7e44b 100644 --- a/src/pat/relateditems/relateditems.js +++ b/src/pat/relateditems/relateditems.js @@ -66,6 +66,10 @@ export default Base.extend({ resultTemplateSelector: null, selectionTemplateSelector: null, toolbarTemplateSelector: null, + toolbarNotAutoTemplateSelector: null, + toolbarRecentlyUsedTemplateSelector: null, + toolbarFavoritesTemplateSelector: null, + toolbarUploadTemplateSelector: null, // needed multiple: true, @@ -114,7 +118,7 @@ export default Base.extend({ one_level_up: _t("Go one level up"), }); options._item = item; - return _.template(template)(options); + return utils.template(template)(options); }, setAjax() { @@ -230,9 +234,9 @@ export default Base.extend({ async renderToolbar() { const path = this.currentPath; - let html; const parts = path.split("/"); + let html = ""; let itemPath = ""; let itemsHtml = ""; for (const part of parts) { @@ -255,30 +259,67 @@ export default Base.extend({ let recentlyUsedHtml = ""; if (this.options.recentlyUsed) { const recentlyUsed = this.recentlyUsed(true); // filter out only those items which can actually be selected - for (const item of recentlyUsed.reverse()) { + for (let item of recentlyUsed.reverse()) { // reverse to get newest first. + if (item['imgsrc'] === undefined) { + item['imgsrc'] = ''; + } + + if (item['getURL'] && (item['getIcon'] || item['portal_type'] === "Image")){ + item['imgsrc'] = '' + } + + var klass = [ + 'pat-relateditems-recentlyused-title', + item['portal_type'] ? ' contenttype-' + item['portal_type'].toLowerCase() : '', + item['review_state'] ? ' state-' + item['review_state'] : '' + ] + + item['itemclass'] = klass.join(' ') + recentlyUsedHtml = recentlyUsedHtml + this.applyTemplate("recentlyused", item); } } - html = this.applyTemplate("toolbar", { + if (this.options.mode !=='auto'){ + html += this.applyTemplate('toolbarNotAuto', { + searchModeClass: (this.options.mode=='search') ? 'btn-primary':'btn-default', + searchModeText: _t('Search'), + browseModeClass: (this.options.mode=='browse') ? 'btn-primary':'btn-default', + browseModeText: _t('Browse'), + }); + } + + html += this.applyTemplate("toolbar", { items: itemsHtml, - favItems: favoritesHtml, - favText: _t("Favorites"), searchText: _t("Current path:"), - searchModeText: _t("Search"), - browseModeText: _t("Browse"), - recentlyUsedItems: recentlyUsedHtml, - recentlyUsedText: _t("Recently Used"), icon_root: await utils.resolveIcon("house-fill"), - icon_recently_used: await utils.resolveIcon("grid-fill"), - icon_favorites: await utils.resolveIcon("star-fill"), - icon_upload: await utils.resolveIcon("cloud-arrow-up"), - upload: this.options.upload, - upload_text: _t("Upload"), }); + if (recentlyUsedHtml != "") { + html += this.applyTemplate('toolbarRecentlyUsed', { + recentlyUsedItems: recentlyUsedHtml, + recentlyUsedText: _t('Recently Used'), + icon_recently_used: await utils.resolveIcon("grid-fill"), + }); + } + + if (this.options.favorites.length > 0){ + html += this.applyTemplate('toolbarFavorites', { + favItems: favoritesHtml, + favText: _t('Favorites'), + icon_favorites: await utils.resolveIcon("star-fill"), + }); + } + + if (this.options.upload){ + html += this.applyTemplate('toolbarUpload', { + icon_upload: await utils.resolveIcon("cloud-arrow-up"), + upload_text: _t("Upload"), + }); + } + this.$toolbar.html(html); // unbind mouseup event from select2 to override the behavior: @@ -512,6 +553,9 @@ export default Base.extend({ this.resultTemplate = (await import("./templates/result.xml")).default; // prettier-ignore this.selectionTemplate = (await import("./templates/selection.xml")).default; // prettier-ignore this.toolbarTemplate = (await import("./templates/toolbar.xml")).default; // prettier-ignore + this.toolbarNotAutoTemplate = (await import("./templates/toolbar_not_auto.xml")).default; // prettier-ignore + this.toolbarRecentlyUsedTemplate = (await import("./templates/toolbar_recently_used.xml")).default; // prettier-ignore + this.toolbarFavoritesTemplate = (await import("./templates/toolbar_favorites.xml")).default; // prettier-ignore this.browsing = this.options.mode !== "search"; @@ -542,6 +586,13 @@ export default Base.extend({ }, item ); + if (item['imgsrc'] === undefined) { + item['imgsrc'] = ''; + } + + if (item['getURL'] && (item['getIcon'] || item['portal_type'] === "Image")){ + item['imgsrc'] = '
' + } // activate petterns on the result set. const $selection = $(this.applyTemplate("selection", item)); @@ -569,6 +620,8 @@ export default Base.extend({ item = $.extend( true, { + one_level_up: _t("Go one level up"), + open_folder: _t("Open folder"), Title: "", getIcon: "", getURL: "", @@ -577,9 +630,16 @@ export default Base.extend({ iconLevelUp: icon_level_up, iconLevelDown: icon_level_down, path: "", + result_path: "", portal_type: "", review_state: "", selectable: false, + div_class: 'pat-relateditems-result', + span_title_class: '', + not_one_level_up_open_a: '', + not_one_level_up_close_a: '', + browse_folder_a: '', + append_if_image: '', }, item ); @@ -588,6 +648,40 @@ export default Base.extend({ // do not allow already selected items to be selected again. item.selectable = false; } + + if (item['oneLevelUp']) { + item['div_class'] += ' one-level-up'; + item['result_path'] = item['currentPath']; + } else { + let klass = [ + 'pat-relateditems-result-select', + item['selectable'] ? 'selectable' : '' + ] + item['not_one_level_up_open_a'] += ''; + item['not_one_level_up_close_a'] += ''; + item['result_path'] = item['path']; + } + + let klass = [ + 'pat-relateditems-result-title', + item['portal_type'] ? 'contenttype-' + item['portal_type'].toLowerCase() : '', + item['review_state'] ? 'state-' + item['review_state'] : '' + ] + item['span_title_class'] = klass.join(' ') + + if (item['is_folderish']){ + item['browse_folder_a'] = ''+ (item['oneLevelUp'] ? item['iconLevelUp'] : item['iconLevelDown']) + ''; + } + + if (item['getURL'] && (item['getIcon'] || item['portal_type'] === "Image")) { + item['append_if_image'] += item['not_one_level_up_open_a']; + item['append_if_image'] += '
'; + item['append_if_image'] += item['not_one_level_up_close_a'] + } + const result = $(this.applyTemplate("result", item)); $(".pat-relateditems-result-select", result).on("click", (event) => { diff --git a/src/pat/relateditems/templates/recentlyused.xml b/src/pat/relateditems/templates/recentlyused.xml index 4e8d6e3c6..1adb79f2f 100644 --- a/src/pat/relateditems/templates/recentlyused.xml +++ b/src/pat/relateditems/templates/recentlyused.xml @@ -1,15 +1,7 @@ diff --git a/src/pat/relateditems/templates/result.xml b/src/pat/relateditems/templates/result.xml index c8b26179a..6a8c7c34b 100644 --- a/src/pat/relateditems/templates/result.xml +++ b/src/pat/relateditems/templates/result.xml @@ -1,41 +1,18 @@ -
+
- <% if (!oneLevelUp) { %> - - <% } %> + <%= not_one_level_up_open_a %> +
<%- Title %> - <%- oneLevelUp ? currentPath : path %> + <%- result_path %>
- <% if (is_folderish) { %> -
- <%= oneLevelUp ? iconLevelUp : iconLevelDown %> - - <% } %> - <% if (!oneLevelUp) { %> - - <% } %> + <%= browse_folder_a %> + <%= not_one_level_up_close_a %> +
- <% if (getURL && (getIcon || portal_type === "Image")) { %> - <% if (!oneLevelUp) { %> - - <% } %> -
- -
- <% if (!oneLevelUp) { %> -
- <% } %> - <% } %> + <%= append_if_image %>
diff --git a/src/pat/relateditems/templates/selection.xml b/src/pat/relateditems/templates/selection.xml index 3da7cc56c..7ff0630de 100644 --- a/src/pat/relateditems/templates/selection.xml +++ b/src/pat/relateditems/templates/selection.xml @@ -7,9 +7,5 @@ <%- path %>
- <% if (getURL && (getIcon || portal_type === "Image")) { %> -
- -
- <% } %> + <%= imgsrc %> diff --git a/src/pat/relateditems/templates/toolbar.xml b/src/pat/relateditems/templates/toolbar.xml index 89e437c71..e9b2e9811 100644 --- a/src/pat/relateditems/templates/toolbar.xml +++ b/src/pat/relateditems/templates/toolbar.xml @@ -1,50 +1,5 @@ -<% if (mode!=='auto') { %> -
- - -
-<% } %>
<%- searchText %> <%= icon_root %> <%= items %>
- -<% if (recentlyUsedItems) { %> - -<% } %> - -<% if (favorites.length > 0) { %> - -<% } %> - -<% if (upload) { %> - -<% } %> diff --git a/src/pat/relateditems/templates/toolbar_favorites.xml b/src/pat/relateditems/templates/toolbar_favorites.xml new file mode 100644 index 000000000..7d3069121 --- /dev/null +++ b/src/pat/relateditems/templates/toolbar_favorites.xml @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/pat/relateditems/templates/toolbar_not_auto.xml b/src/pat/relateditems/templates/toolbar_not_auto.xml new file mode 100644 index 000000000..3645eabfb --- /dev/null +++ b/src/pat/relateditems/templates/toolbar_not_auto.xml @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file diff --git a/src/pat/relateditems/templates/toolbar_recently_used.xml b/src/pat/relateditems/templates/toolbar_recently_used.xml new file mode 100644 index 000000000..f348da349 --- /dev/null +++ b/src/pat/relateditems/templates/toolbar_recently_used.xml @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/pat/relateditems/templates/toolbar_upload.xml b/src/pat/relateditems/templates/toolbar_upload.xml new file mode 100644 index 000000000..d6ebb88a3 --- /dev/null +++ b/src/pat/relateditems/templates/toolbar_upload.xml @@ -0,0 +1,10 @@ +