From 40843b8eed2e2202955216d49791def8eb9a11b8 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Fri, 16 Oct 2020 16:39:20 -0600 Subject: [PATCH 01/10] cleanup for premium --- CHANGELOG.md | 7 +++++++ themes/grav/templates/partials/plugin-data.html.twig | 10 +++++++--- .../grav/templates/partials/themes-details.html.twig | 10 +++++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be51e651..73f5810f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v1.9.18 +## mm/dd/2020 + +1. [](#improved) + * Auto-link a plugin/theme license in details if it starts with `http` + * Don't link to readme unless a + # v1.9.17 ## 10/07/2020 diff --git a/themes/grav/templates/partials/plugin-data.html.twig b/themes/grav/templates/partials/plugin-data.html.twig index 751a6355..ec92aa9c 100644 --- a/themes/grav/templates/partials/plugin-data.html.twig +++ b/themes/grav/templates/partials/plugin-data.html.twig @@ -41,7 +41,11 @@ {% if plugin.license %} {{ "PLUGIN_ADMIN.LICENSE"|tu }}: - {{ plugin.license }} + {% if plugin.license|starts_with('http') %} + {{ plugin.license }} + {% else %} + {{ plugin.license }} + {% endif %} {% endif %} @@ -52,8 +56,8 @@ {% endif %} - {% if plugin.readme or plugin.homepage %} - {% set readme_link = plugin.readme ?: plugin.docs|default(plugin.homepage ~ '/blob/master/README.md') %} + {% if plugin.readme %} + {% set readme_link = plugin.readme %} {{ "PLUGIN_ADMIN.README"|tu }}: {{ readme_link }} diff --git a/themes/grav/templates/partials/themes-details.html.twig b/themes/grav/templates/partials/themes-details.html.twig index bea4dec3..65d4b8a8 100644 --- a/themes/grav/templates/partials/themes-details.html.twig +++ b/themes/grav/templates/partials/themes-details.html.twig @@ -74,7 +74,11 @@ {% if theme.license %} {{ "PLUGIN_ADMIN.LICENSE"|tu }}: - {{ theme.license }} + {% if theme.license|starts_with('http') %} + {{ theme.license }} + {% else %} + {{ theme.license }} + {% endif %} {% endif %} {% if theme.description %} @@ -84,8 +88,8 @@ {% endif %} - {% if theme.readme or theme.homepage %} - {% set readme_link = theme.readme ?: theme.homepage ~ '/blob/master/README.md' %} + {% if theme.readme %} + {% set readme_link = theme.readme %} {{ "PLUGIN_ADMIN.README"|tu }}: {{ readme_link }} From 13e659a74849b4e3cc5696b8a1fbd013f5f271be Mon Sep 17 00:00:00 2001 From: Ole Vik Date: Sat, 17 Oct 2020 20:40:38 +0200 Subject: [PATCH 02/10] Output raw text in paragraph for fieldset (#1956) --- .../grav/templates/forms/fields/fieldset/fieldset.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/grav/templates/forms/fields/fieldset/fieldset.html.twig b/themes/grav/templates/forms/fields/fieldset/fieldset.html.twig index 68885218..5fa27ede 100644 --- a/themes/grav/templates/forms/fields/fieldset/fieldset.html.twig +++ b/themes/grav/templates/forms/fields/fieldset/fieldset.html.twig @@ -50,9 +50,9 @@ {% block group %} {% if field.text %} {% if grav.twig.twig.filters['tu'] is defined %} - {{ field.markdown ? field.text|tu|markdown : '

' ~ field.text|tu ~ '

' }} + {{ field.markdown ? field.text|tu|markdown : ('

' ~ field.text|tu ~ '

')|raw }} {% else %} - {{ field.markdown ? field.text|t|markdown : '

' ~ field.t ~ '

' }} + {{ field.markdown ? field.text|t|markdown : ('

' ~ field.t ~ '

')|raw }} {% endif %} {% endif %} From eb1e8b72b266c72e5a648659c3aec256fcc56818 Mon Sep 17 00:00:00 2001 From: Adam McKenna Date: Sat, 17 Oct 2020 19:42:02 +0100 Subject: [PATCH 03/10] Add focus states to login buttons (#1839) This improves accessibility for keyboard users. --- themes/grav/scss/template/modules/_buttons.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/themes/grav/scss/template/modules/_buttons.scss b/themes/grav/scss/template/modules/_buttons.scss index c543b5e4..cd86edf9 100644 --- a/themes/grav/scss/template/modules/_buttons.scss +++ b/themes/grav/scss/template/modules/_buttons.scss @@ -41,6 +41,7 @@ } @if ($lighter) { + &:focus, &:hover { background: shade($color,15%); color: $text; @@ -49,6 +50,7 @@ border-left: 1px solid lighten($color, 5%); } } @else { + &:focus, &:hover { background: tint($color,15%); color: $text; From 7047535b5cd9421e883fd2317076f2fb959226de Mon Sep 17 00:00:00 2001 From: Djamil Legato Date: Mon, 19 Oct 2020 13:32:25 -0700 Subject: [PATCH 04/10] Reverted logic to display readme/docs and added proper Docs language reference --- CHANGELOG.md | 2 +- languages/en.yaml | 1 + themes/grav/templates/partials/plugin-data.html.twig | 6 +++--- themes/grav/templates/partials/themes-details.html.twig | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f5810f..08ed8079 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ 1. [](#improved) * Auto-link a plugin/theme license in details if it starts with `http` - * Don't link to readme unless a + * Allow to fallback to `docs:` instead of `readme:` # v1.9.17 ## 10/07/2020 diff --git a/languages/en.yaml b/languages/en.yaml index c851ad6d..2e0964c7 100644 --- a/languages/en.yaml +++ b/languages/en.yaml @@ -72,6 +72,7 @@ PLUGIN_ADMIN: LICENSE: "License" DESCRIPTION: "Description" README: "Readme" + DOCS: "Docs" REMOVE_THEME: "Remove Theme" INSTALL_THEME: "Install Theme" THEME: "Theme" diff --git a/themes/grav/templates/partials/plugin-data.html.twig b/themes/grav/templates/partials/plugin-data.html.twig index ec92aa9c..866e5055 100644 --- a/themes/grav/templates/partials/plugin-data.html.twig +++ b/themes/grav/templates/partials/plugin-data.html.twig @@ -56,10 +56,10 @@ {% endif %} - {% if plugin.readme %} - {% set readme_link = plugin.readme %} + {% if plugin.readme or plugin.homepage %} + {% set readme_link = plugin.readme ?: plugin.docs|default(plugin.homepage ~ '/blob/master/README.md') %} - {{ "PLUGIN_ADMIN.README"|tu }}: + {{ plugin.readme ? "PLUGIN_ADMIN.README"|tu : "PLUGIN_ADMIN.DOCS"|tu }}: {{ readme_link }} {% endif %} diff --git a/themes/grav/templates/partials/themes-details.html.twig b/themes/grav/templates/partials/themes-details.html.twig index 65d4b8a8..74e60d75 100644 --- a/themes/grav/templates/partials/themes-details.html.twig +++ b/themes/grav/templates/partials/themes-details.html.twig @@ -88,10 +88,10 @@ {% endif %} - {% if theme.readme %} - {% set readme_link = theme.readme %} + {% if theme.readme or theme.homepage %} + {% set readme_link = theme.readme ?: theme.docs|default(theme.homepage ~ '/blob/master/README.md') %} - {{ "PLUGIN_ADMIN.README"|tu }}: + {{ plugin.readme ? "PLUGIN_ADMIN.README"|tu : "PLUGIN_ADMIN.DOCS"|tu }}: {{ readme_link }} {% endif %} From d2c861933b41c72a259d66c1aa92254ad0b3bcb0 Mon Sep 17 00:00:00 2001 From: Djamil Legato Date: Fri, 23 Oct 2020 15:37:49 -0700 Subject: [PATCH 05/10] Backported finder/pages navigation from 1.10 (you will still need 1.10 for the fancy Parent Picker) --- CHANGELOG.md | 1 + themes/grav/app/forms/fields/index.js | 5 + themes/grav/app/forms/fields/parents.js | 260 ++++++++++++++ themes/grav/app/utils/finderjs.js | 325 ++++++++++++++++++ themes/grav/css-compiled/fonts.css | 2 +- themes/grav/css-compiled/fonts.css.map | 12 +- themes/grav/css-compiled/nucleus.css | 2 +- themes/grav/css-compiled/nucleus.css.map | 94 ++++- themes/grav/css-compiled/preset.css | 4 +- themes/grav/css-compiled/preset.css.map | 17 +- themes/grav/css-compiled/simple-fonts.css | 2 +- themes/grav/css-compiled/simple-fonts.css.map | 11 +- themes/grav/css-compiled/template.css | 4 +- themes/grav/css-compiled/template.css.map | 127 ++++++- themes/grav/js/admin.min.js | 38 +- themes/grav/js/vendor.min.js | 4 +- themes/grav/package.json | 1 + themes/grav/scss/template/_forms.scss | 43 +++ themes/grav/scss/template/modules/_base.scss | 1 + .../grav/scss/template/modules/_finderjs.scss | 161 +++++++++ themes/grav/yarn.lock | 5 + 21 files changed, 1086 insertions(+), 33 deletions(-) create mode 100644 themes/grav/app/forms/fields/parents.js create mode 100644 themes/grav/app/utils/finderjs.js create mode 100644 themes/grav/scss/template/modules/_finderjs.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ed8079..34b637d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ 1. [](#improved) * Auto-link a plugin/theme license in details if it starts with `http` * Allow to fallback to `docs:` instead of `readme:` + * Backported finder/pages navigation from 1.10 (you will still need 1.10 for the fancy Parent Picker) # v1.9.17 ## 10/07/2020 diff --git a/themes/grav/app/forms/fields/index.js b/themes/grav/app/forms/fields/index.js index 9ad371ca..04b0a05e 100644 --- a/themes/grav/app/forms/fields/index.js +++ b/themes/grav/app/forms/fields/index.js @@ -12,6 +12,7 @@ import SelectUniqueField, { Instance as SelectUniqueInstance } from './selectuni import IconpickerField, { Instance as IconpickerInstance } from './iconpicker'; import CronField, { Instance as CronFieldInstance } from './cron'; import TextField, { Instance as TextFieldInstance } from './text'; +import ParentsField, { Instance as ParentsFieldInstance } from './parents'; export default { FilepickerField: { @@ -67,6 +68,10 @@ export default { }, TextField: {TextField, Insance: TextFieldInstance + }, + ParentsField: { + ParentsField, + Instance: ParentsFieldInstance } }; diff --git a/themes/grav/app/forms/fields/parents.js b/themes/grav/app/forms/fields/parents.js new file mode 100644 index 00000000..cc28dac7 --- /dev/null +++ b/themes/grav/app/forms/fields/parents.js @@ -0,0 +1,260 @@ +import $ from 'jquery'; +import Finder from '../../utils/finderjs'; +import { config as gravConfig } from 'grav-config'; + +let XHRUUID = 0; +export const Instances = {}; + +export class Parents { + constructor(container, field, data) { + this.container = $(container); + this.fieldName = field.attr('name'); + this.field = $(`[name="${this.fieldName}"]`); + this.data = data; + this.parentLabel = $(`[data-parents-field-label="${this.fieldName}"]`); + this.parentName = $(`[data-parents-field-name="${this.fieldName}"]`); + + const dataLoad = this.dataLoad; + + this.finder = new Finder( + this.container, + (parent, callback) => { + return dataLoad.call(this, parent, callback); + }, + { + labelKey: 'name', + defaultPath: this.field.val(), + createItemContent: function(item) { + return Parents.createItemContent(this.config, item); + } + } + ); + + /* + this.finder.$emitter.on('leaf-selected', (item) => { + console.log('selected', item); + this.finder.emit('create-column', () => this.createSimpleColumn(item)); + }); + + this.finder.$emitter.on('item-selected', (selected) => { + console.log('selected', selected); + // for future use only - create column-card creation for file with details like in macOS finder + // this.finder.$emitter('create-column', () => this.createSimpleColumn(selected)); + }); */ + + this.finder.$emitter.on('column-created', () => { + this.container[0].scrollLeft = this.container[0].scrollWidth - this.container[0].clientWidth; + }); + } + + static createItemContent(config, item) { + const frag = document.createDocumentFragment(); + + const label = $(``); + const infoContainer = $(''); + const iconPrepend = $(''); + const iconAppend = $(''); + const badge = $(''); + const prependClasses = ['fa']; + const appendClasses = ['fa']; + + // prepend icon + if (item.children || item.type === 'dir') { + prependClasses.push('fa-folder'); + } else if (item.type === 'root') { + prependClasses.push('fa-sitemap'); + } else if (item.type === 'file') { + prependClasses.push('fa-file-o'); + } + + iconPrepend.addClass(prependClasses.join(' ')); + + // text label + label.text(item[config.labelKey]).prepend(iconPrepend); + label.appendTo(frag); + + // append icon + if (item.children || item['has-children']) { + appendClasses.push('fa-caret-right'); + badge.text(item.size || item.count || 0); + badge.appendTo(infoContainer); + } + + iconAppend.addClass(appendClasses.join(' ')); + iconAppend.appendTo(infoContainer); + infoContainer.appendTo(frag); + + return frag; + } + + static createLoadingColumn() { + return $(` +
+
+
Loading...
+
+
+ `); + } + + static createErrorColumn(error) { + return $(` +
+
+ + ${error} +
+
+ `); + } + + createSimpleColumn(item) {} + + dataLoad(parent, callback) { + if (!parent) { + return callback(this.data); + } + + if (parent.type !== 'dir' || !parent['has-children']) { + return false; + } + + const UUID = ++XHRUUID; + this.startLoader(); + + $.ajax({ + url: `${gravConfig.current_url}`, + method: 'post', + data: Object.assign({}, getExtraFormData(this.container), { + route: b64_encode_unicode(parent.value), + field: this.field.data('fieldName'), + action: 'getLevelListing' + }), + success: (response) => { + this.stopLoader(); + + if (response.status === 'error') { + this.finder.$emitter.emit('create-column', Parents.createErrorColumn(response.message)[0]); + return false; + } + // stale request + if (UUID !== XHRUUID) { + return false; + } + + return callback(response.data); + } + }); + } + + startLoader() { + this.loadingIndicator = Parents.createLoadingColumn(); + this.finder.$emitter.emit('create-column', this.loadingIndicator[0]); + + return this.loadingIndicator; + } + + stopLoader() { + return this.loadingIndicator && this.loadingIndicator.remove(); + } +} + +export const b64_encode_unicode = (str) => { + return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, + function toSolidBytes(match, p1) { + return String.fromCharCode('0x' + p1); + })); +}; + +export const b64_decode_unicode = (str) => { + return decodeURIComponent(atob(str).split('').map(function(c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); +}; + +const getExtraFormData = (container) => { + let form = container.closest('form'); + if (container.closest('[data-remodal-id]').length) { + form = $('form#blueprints'); + } + const data = {}; + const unique_id = form.find('[name="__unique_form_id__"]'); + + data['__form-name__'] = form.find('[name="__form-name__"]').val(); + data['form-nonce'] = form.find('[name="form-nonce"]').val(); + + if (unique_id.length) { + data['__unique_form_id__'] = unique_id.val(); + } + + return data; +}; + +$(document).on('click', '[data-parents]', (event) => { + event.preventDefault(); + event.stopPropagation(); + + const target = $(event.currentTarget); + let field = target.closest('.parents-wrapper').find('input[name]'); + let fieldName = field.attr('name'); + + if (!field.length) { + fieldName = target.data('parents'); + field = $(`[name="${target.data('parents')}"]`).first(); + } + + const modal = $(`[data-remodal-id="${target.data('remodalTarget') || 'parents'}"]`); + const loader = modal.find('.grav-loading'); + const content = modal.find('.parents-content'); + + loader.css('display', 'block'); + content.html(''); + $.ajax({ + url: `${gravConfig.current_url}`, + method: 'post', + data: Object.assign({}, getExtraFormData(target), { + route: b64_encode_unicode(field.val()), + field: field.data('fieldName'), + action: 'getLevelListing', + initial: true + }), + success(response) { + loader.css('display', 'none'); + + if (response.status === 'error') { + content.html(response.message); + return true; + } + + if (!Instances[`${fieldName}-${modal.data('remodalId')}`]) { + Instances[`${fieldName}-${modal.data('remodalId')}`] = new Parents(content, field, response.data); + } else { + Instances[`${fieldName}-${modal.data('remodalId')}`].finder.reload(response.data); + } + + modal.data('parents', Instances[`${fieldName}-${modal.data('remodalId')}`]); + + } + }); +}); + +// apply finder selection to field +$(document).on('click', '[data-remodal-id].parents-container [data-parents-select]', (event) => { + const modal = $(event.currentTarget).closest('[data-remodal-id]'); + const parents = modal.data('parents'); + const finder = parents.finder; + const field = parents.field; + const parentLabel = parents.parentLabel; + const parentName = parents.parentName; + const selection = finder.findLastActive().item[0]; + const value = selection._item[finder.config.valueKey]; + const name = selection._item[finder.config.labelKey]; + + field.val(value); + parentLabel.text(value); + parentName.text(name); + finder.config.defaultPath = value; + + const remodal = $.remodal.lookup[$(`[data-remodal-id="${modal.data('remodalId')}"]`).data('remodal')]; + remodal.close(); +}); diff --git a/themes/grav/app/utils/finderjs.js b/themes/grav/app/utils/finderjs.js new file mode 100644 index 00000000..29778313 --- /dev/null +++ b/themes/grav/app/utils/finderjs.js @@ -0,0 +1,325 @@ +/** + * (c) Trilby Media, LLC + * Author Djamil Legato + * + * Based on Mark Matyas's Finderjs + * MIT License + */ + +import $ from 'jquery'; +import EventEmitter from 'eventemitter3'; + +export const DEFAULTS = { + labelKey: 'name', + valueKey: 'value', // new + childKey: 'children', + iconKey: 'icon', // new + itemKey: 'item-key', // new + pathBar: true, + className: { + container: 'fjs-container', + pathBar: 'fjs-path-bar', + col: 'fjs-col', + list: 'fjs-list', + item: 'fjs-item', + active: 'fjs-active', + children: 'fjs-has-children', + url: 'fjs-url', + itemPrepend: 'fjs-item-prepend', + itemContent: 'fjs-item-content', + itemAppend: 'fjs-item-append' + } +}; + +class Finder { + constructor(container, data, options) { + this.$emitter = new EventEmitter(); + this.container = $(container); + this.data = data; + + this.config = $.extend({}, DEFAULTS, options); + + // dom events + this.container.on('click', this.clickEvent.bind(this)); + this.container.on('keydown', this.keydownEvent.bind(this)); + + // internal events + this.$emitter.on('item-selected', this.itemSelected.bind(this)); + this.$emitter.on('create-column', this.addColumn.bind(this)); + this.$emitter.on('navigate', this.navigate.bind(this)); + this.$emitter.on('go-to', this.goTo.bind(this, this.data)); + + this.container.addClass(this.config.className.container).attr('tabindex', 0); + + this.createColumn(this.data); + + if (this.config.pathBar) { + this.pathBar = this.createPathBar(); + this.pathBar.on('click', '[data-breadcrumb-node]', (event) => { + event.preventDefault(); + const location = $(event.currentTarget).data('breadcrumbNode'); + this.goTo(this.data, location); + }); + } + + // '' is + if (this.config.defaultPath || this.config.defaultPath === '') { + this.goTo(this.data, this.config.defaultPath); + } + } + + reload(data = this.data) { + this.createColumn(data); + + // '' is + if (this.config.defaultPath || this.config.defaultPath === '') { + this.goTo(data, this.config.defaultPath); + } + } + + createColumn(data, parent) { + const callback = (data) => this.createColumn(data, parent); + + if (typeof data === 'function') { + data.call(this, parent, callback); + } else if (Array.isArray(data) || typeof data === 'object') { + if (typeof data === 'object') { + data = Array.from(data); + } + const list = this.createList(data); + const div = $('
'); + div.append(list).addClass(this.config.className.col); + this.$emitter.emit('create-column', div); + + return div; + } else { + throw new Error('Unknown data type'); + } + } + + createPathBar() { + this.container.siblings(`.${this.config.className.pathBar}`).remove(); + const pathBar = $(`
`); + pathBar.insertAfter(this.container); + + return pathBar; + } + + clickEvent(event) { + event.stopPropagation(); + event.preventDefault(); + + const target = $(event.target); + const column = target.closest(`.${this.config.className.col}`); + const item = target.closest(`.${this.config.className.item}`); + + if (item.length) { + this.$emitter.emit('item-selected', { column, item }); + } + } + + keydownEvent(event) { + const codes = { 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; + + if (event.keyCode in codes) { + event.stopPropagation(); + event.preventDefault(); + + this.$emitter.emit('navigate', { + direction: codes[event.keyCode] + }); + } + } + + itemSelected(value) { + const element = value.item; + if (!element.length) { return false; } + const item = element[0]._item; + const column = value.column; + const data = item[this.config.childKey] || this.data; + const active = $(column).find(`.${this.config.className.active}`); + + if (active.length) { + active.removeClass(this.config.className.active); + } + + element.addClass(this.config.className.active); + column.nextAll().remove(); // ?!?!? + + this.container[0].focus(); + window.scrollTo(window.pageXOffset, window.pageYOffset); + + this.updatePathBar(); + + let newColumn; + if (data) { + newColumn = this.createColumn(data, item); + this.$emitter.emit('interior-selected', item); + } else { + this.$emitter.emit('leaf-selected', item); + } + + return newColumn; + } + + addColumn(column) { + this.container.append(column); + this.$emitter.emit('column-created', column); + } + + navigate(value) { + const active = this.findLastActive(); + const direction = value.direction; + let column; + let item; + let target; + + if (active) { + item = active.item; + column = active.column; + + if (direction === 'up' && item.prev().length) { + target = item.prev(); + } else if (direction === 'down' && item.next().length) { + target = item.next(); + } else if (direction === 'right' && column.next().length) { + column = column.next(); + target = column.find(`.${this.config.className.item}`).first(); + } else if (direction === 'left' && column.prev().length) { + column = column.prev(); + target = column.find(`.${this.config.className.active}`).first() || column.find(`.${this.config.className.item}`); + } + } else { + column = this.container.find(`.${this.config.className.col}`).first(); + target = column.find(`.${this.config.className.item}`).first(); + } + + if (target) { + this.$emitter.emit('item-selected', { + column, + item: target + }); + } + } + + goTo(data, path) { + path = Array.isArray(path) ? path : path.split('/').map(bit => bit.trim()).filter(Boolean); + + if (path.length) { + this.container.children().remove(); + } + + if (typeof data === 'function') { + data.call(this, null, (data) => this.selectPath(path, data)); + } else { + this.selectPath(path, data); + } + } + + selectPath(path, data, column) { + column = column || (path.length ? this.createColumn(data) : this.container.find(`> .${this.config.className.col}`)); + + const current = path[0] || ''; + const children = data.find((item) => item[this.config.itemKey] === current); + const newColumn = this.itemSelected({ + column, + item: column.find(`[data-fjs-item="${current}"]`).first() + }); + + path.shift(); + + if (path.length && children) { + this.selectPath(path, children[this.config.childKey], newColumn); + } + } + + findLastActive() { + const active = this.container.find(`.${this.config.className.active}`); + if (!active.length) { + return null; + } + + const item = active.last(); + const column = item.closest(`.${this.config.className.col}`); + + return { item, column }; + } + + createList(data) { + const list = $('