diff --git a/app/assets/stylesheets/pageflow/editor/base.scss b/app/assets/stylesheets/pageflow/editor/base.scss index 1071d381a8..33d80bc97a 100644 --- a/app/assets/stylesheets/pageflow/editor/base.scss +++ b/app/assets/stylesheets/pageflow/editor/base.scss @@ -61,6 +61,7 @@ @import "./drop_down_button"; @import "./outline"; + @import "./sortable"; @import "./failures"; @import "./menu"; @import "./inputs"; diff --git a/app/assets/stylesheets/pageflow/editor/drop_down_button.scss b/app/assets/stylesheets/pageflow/editor/drop_down_button.scss index 3ca5439806..81ee6b55dc 100644 --- a/app/assets/stylesheets/pageflow/editor/drop_down_button.scss +++ b/app/assets/stylesheets/pageflow/editor/drop_down_button.scss @@ -12,7 +12,13 @@ > button.ellipsis_icon { @include fa-ellipsis-v-icon; - width: 31px; + width: var(--drop-down-button-width, 31px); + } + + > button.borderless { + --ui-button-border-color: transparent; + --ui-button-hover-border-color: transparent; + --ui-button-focus-ring-color: transparent; } &.full_width { diff --git a/app/assets/stylesheets/pageflow/editor/list.scss b/app/assets/stylesheets/pageflow/editor/list.scss index d9ed69cf6b..cb0b1d58cf 100644 --- a/app/assets/stylesheets/pageflow/editor/list.scss +++ b/app/assets/stylesheets/pageflow/editor/list.scss @@ -1,6 +1,7 @@ .list { .list_items { margin-bottom: 10px; + user-select: none; } .list_blank_slate { @@ -104,8 +105,4 @@ @include icon-only-button("destructive"); @include trash-icon; } - - .sortable-placeholder { - height: 35px; - } } diff --git a/app/assets/stylesheets/pageflow/editor/outline.scss b/app/assets/stylesheets/pageflow/editor/outline.scss index 6eec9705d2..03e2275be1 100644 --- a/app/assets/stylesheets/pageflow/editor/outline.scss +++ b/app/assets/stylesheets/pageflow/editor/outline.scss @@ -99,10 +99,6 @@ ul.outline { &.hide_in_navigation a { color: var(--ui-on-surface-color-light); } - - &.ui-sortable-helper { - box-shadow: var(--ui-box-shadow-xl); - } } } @@ -122,10 +118,6 @@ ul.chapters { font-weight: bold; } } - - > .sortable-placeholder { - border: 0; - } } ul.pages { @@ -145,8 +137,3 @@ ul.dragged { margin: 0; padding: 0; } - -.sortable-placeholder { - outline: 1px dotted var(--ui-on-surface-color-lighter); - border-radius: rounded(); -} diff --git a/app/assets/stylesheets/pageflow/editor/sortable.scss b/app/assets/stylesheets/pageflow/editor/sortable.scss new file mode 100644 index 0000000000..8228631a59 --- /dev/null +++ b/app/assets/stylesheets/pageflow/editor/sortable.scss @@ -0,0 +1,12 @@ +.sortable-placeholder { + outline: 1px dotted var(--ui-on-surface-color-lighter); + + // scss-lint:disable ImportantRule + border-color: transparent !important; + background-color: transparent !important; + // scss-lint:enable ImportantRule + + * { + visibility: hidden; + } +} diff --git a/app/assets/stylesheets/pageflow/mixins/buttons.scss b/app/assets/stylesheets/pageflow/mixins/buttons.scss index 7f5c8af85a..8c5577f10c 100644 --- a/app/assets/stylesheets/pageflow/mixins/buttons.scss +++ b/app/assets/stylesheets/pageflow/mixins/buttons.scss @@ -49,8 +49,8 @@ } } @else { background-color: transparent; - border: solid 1px var(--ui-primary-color-light); - color: var(--ui-primary-color); + border: solid 1px var(--ui-button-border-color); + color: var(--ui-on-button-color); @if $type == "destructive" { &:hover:not(:disabled):not(.disabled) { @@ -61,7 +61,7 @@ } @else { &.hover:not(:disabled):not(.disabled), &:hover:not(:disabled):not(.disabled) { - border: 1px solid var(--ui-primary-color); + border-color: var(--ui-button-hover-border-color); } } @@ -78,6 +78,6 @@ &:active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) { - box-shadow: 0 0 0 2px var(--ui-primary-color-lighter); + box-shadow: 0 0 0 2px var(--ui-button-focus-ring-color); } } diff --git a/app/assets/stylesheets/pageflow/ui/properties.scss b/app/assets/stylesheets/pageflow/ui/properties.scss index 84117e897e..c598b9f5cb 100644 --- a/app/assets/stylesheets/pageflow/ui/properties.scss +++ b/app/assets/stylesheets/pageflow/ui/properties.scss @@ -41,4 +41,9 @@ --ui-box-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --ui-box-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --ui-box-shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); + + --ui-on-button-color: var(--ui-primary-color); + --ui-button-border-color: var(--ui-primary-color-light); + --ui-button-hover-border-color: var(--ui-primary-color); + --ui-button-focus-ring-color: var(--ui-primary-color-lighter); } diff --git a/entry_types/scrolled/config/locales/new/reorder_chapters.de.yml b/entry_types/scrolled/config/locales/new/reorder_chapters.de.yml new file mode 100644 index 0000000000..2243e94b7b --- /dev/null +++ b/entry_types/scrolled/config/locales/new/reorder_chapters.de.yml @@ -0,0 +1,7 @@ +de: + pageflow_scrolled: + editor: + entry_outline: + reorder_chapters: "Kapitel umsortieren..." + reorder_chapters_header: "Kapitel umsortieren" + done: Fertig diff --git a/entry_types/scrolled/config/locales/new/reorder_chapters.en.yml b/entry_types/scrolled/config/locales/new/reorder_chapters.en.yml new file mode 100644 index 0000000000..bb11f6fb70 --- /dev/null +++ b/entry_types/scrolled/config/locales/new/reorder_chapters.en.yml @@ -0,0 +1,7 @@ +en: + pageflow_scrolled: + editor: + entry_outline: + reorder_chapters: "Reorder chapters..." + reorder_chapters_header: "Reorder chapters" + done: Done diff --git a/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css b/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css index 7cc075a10e..42bb293090 100644 --- a/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css +++ b/entry_types/scrolled/package/src/editor/views/ChapterItemView.module.css @@ -9,10 +9,6 @@ border-radius: rounded(lg); } -.root:global(.ui-sortable-helper) { - box-shadow: var(--ui-box-shadow-xl); -} - .selectableHover { background-color: var(--ui-selection-color-lighter); } @@ -24,10 +20,9 @@ } .outlineLink { - composes: rightOpen from './icons.module.css'; + composes: chapterLink from './outline.module.css'; composes: link; position: relative; - padding-left: 30px; } .link::before { @@ -40,11 +35,7 @@ .dragHandle { composes: dragHandle from './outline.module.css'; - color: var(--ui-on-surface-color-light); -} - -.link:hover .dragHandle { - opacity: 1; + color: var(--ui-on-surface-color); } .number { @@ -54,6 +45,7 @@ .title {} .sections { + composes: sections from './outline.module.css'; margin: 10px 0 10px 0; min-height: 20px; } @@ -69,4 +61,5 @@ .addSection { composes: addButton from './buttons.module.css'; + composes: button from './outline.module.css'; } diff --git a/entry_types/scrolled/package/src/editor/views/EntryOutlineView.js b/entry_types/scrolled/package/src/editor/views/EntryOutlineView.js index 51bdcd172e..6a3a1ac054 100644 --- a/entry_types/scrolled/package/src/editor/views/EntryOutlineView.js +++ b/entry_types/scrolled/package/src/editor/views/EntryOutlineView.js @@ -1,6 +1,8 @@ import Marionette from 'backbone.marionette'; +import Backbone from 'backbone'; import I18n from 'i18n-js'; import {cssModulesUtils, SortableCollectionView} from 'pageflow/ui'; +import {DropDownButtonView} from 'pageflow/editor'; import {ChapterItemView} from './ChapterItemView'; @@ -11,7 +13,15 @@ export const EntryOutlineView = Marionette.Layout.extend({ className: styles.root, template: () => ` - + +
+
+ +
@@ -19,22 +29,84 @@ export const EntryOutlineView = Marionette.Layout.extend({ `, - ui: cssModulesUtils.ui(styles, 'chapters'), + ui: cssModulesUtils.ui(styles, 'header', 'dropDownButton', 'chapters'), events: cssModulesUtils.events(styles, { 'click addChapter': function() { this.options.entry.addChapter(); + }, + + 'click expandChapters': function() { + this.toggleCollapsed(); } }), + initialize() { + this.collapsed = false; + }, + onRender() { - this.subview(new SortableCollectionView({ + const dropDownMenuItems = new Backbone.Collection(); + + this.reorderChaptersMenutItem = new MenuItem({ + label: I18n.t('pageflow_scrolled.editor.entry_outline.reorder_chapters') + }, { + selected: () => + this.toggleCollapsed() + }) + + dropDownMenuItems.add(this.reorderChaptersMenutItem); + + this.appendSubview(new DropDownButtonView({ + items: dropDownMenuItems, + alignMenu: 'right', + ellipsisIcon: true, + borderless: true, + openOnClick: true + }), {to: this.ui.dropDownButton}); + + this.sortableCollectionView = new SortableCollectionView({ el: this.ui.chapters, collection: this.options.entry.chapters, itemViewConstructor: ChapterItemView, itemViewOptions: { entry: this.options.entry } - })); + }); + + this.subview(this.sortableCollectionView); + this.sortableCollectionView.disableSorting(); + }, + + toggleCollapsed() { + this.collapsed = !this.collapsed; + + this.$el.toggleClass(styles.collapsed, this.collapsed); + + if (this.collapsed) { + this.ui.header.text( + I18n.t('pageflow_scrolled.editor.entry_outline.reorder_chapters_header') + ); + + this.sortableCollectionView.enableSorting(); + } + else { + this.ui.header.text( + I18n.t('pageflow_scrolled.editor.entry_outline.header') + ); + + this.sortableCollectionView.disableSorting(); + + } + } +}); + +const MenuItem = Backbone.Model.extend({ + initialize: function(attributes, options) { + this.options = options; + }, + + selected: function() { + this.options.selected(); } }); diff --git a/entry_types/scrolled/package/src/editor/views/EntryOutlineView.module.css b/entry_types/scrolled/package/src/editor/views/EntryOutlineView.module.css index cb0e44c7fc..20ebb04018 100644 --- a/entry_types/scrolled/package/src/editor/views/EntryOutlineView.module.css +++ b/entry_types/scrolled/package/src/editor/views/EntryOutlineView.module.css @@ -1,13 +1,51 @@ -.root {} +.root { + position: relative; + user-select: none; + composes: outline from './outline.module.css'; +} -.chapters {} +.collapsed { + composes: collapsed from './outline.module.css'; +} + +.root .header { + margin-bottom: 10px; +} + +.toolbar { + position: absolute; + top: -6px; + right: -1px; +} + +.dropDownButton { + --drop-down-button-width: 26px; + --ui-on-button-color: var(--ui-primary-color-light); -.chapters > :global(.sortable-placeholder) { - margin-bottom: 12px; - padding: 0 10px 10px 10px; - border-radius: rounded(lg); + button.hover, + button:hover { + --ui-on-button-color: var(--ui-primary-color); + } } +.collapsed .dropDownButton, +.expandChapters { + display: none !important; +} + +.expandChapters { + composes: saveButton from './buttons.module.css'; + padding-top: 3px !important; + padding-bottom: 3px !important; +} + +.collapsed .expandChapters { + display: block !important; +} + +.chapters {} + .addChapter { composes: addButton from './buttons.module.css'; + composes: button from './outline.module.css'; } diff --git a/entry_types/scrolled/package/src/editor/views/SectionItemView.js b/entry_types/scrolled/package/src/editor/views/SectionItemView.js index 8211e96a91..cda5f109c0 100644 --- a/entry_types/scrolled/package/src/editor/views/SectionItemView.js +++ b/entry_types/scrolled/package/src/editor/views/SectionItemView.js @@ -150,6 +150,7 @@ export const SectionItemView = Marionette.ItemView.extend({ items: dropDownMenuItems, alignMenu: 'right', ellipsisIcon: true, + borderless: true, openOnClick: true }), {to: this.ui.dropDownButton}); }, diff --git a/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css b/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css index b7d112ad87..35d04cdc69 100644 --- a/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css +++ b/entry_types/scrolled/package/src/editor/views/SectionItemView.module.css @@ -72,6 +72,7 @@ .dragHandle { composes: dragHandle from './outline.module.css'; + --outline-drag-handle-transition-delay: 0.2s; color: var(--ui-on-primary-color); text-shadow: 0 0 2px #000; } @@ -93,8 +94,7 @@ } .dropDownButton button { - border: 0 !important; - color: var(--ui-on-primary-color) !important; + --ui-on-button-color: var(--ui-on-primary-color); text-shadow: 0 0 2px #000; box-shadow: none !important; opacity: 0.3; @@ -103,7 +103,7 @@ } .invert .dropDownButton button { - color: var(--ui-primary-color) !important; + --ui-on-button-color: var(--ui-primary-color); text-shadow: 0 0 2px #fff; } diff --git a/entry_types/scrolled/package/src/editor/views/outline.module.css b/entry_types/scrolled/package/src/editor/views/outline.module.css index a3a26bae21..1a2fd56e4a 100644 --- a/entry_types/scrolled/package/src/editor/views/outline.module.css +++ b/entry_types/scrolled/package/src/editor/views/outline.module.css @@ -1,9 +1,36 @@ @value indicatorIconColor, errorIconColor from './colors.module.css'; +.chapter { + padding: 0 10px 10px 10px; +} + +.chapterLink { + composes: rightOpen from './icons.module.css'; +} + +.collapsed .chapter { + padding-bottom: 0; + cursor: move; +} + +.collapsed .chapterLink { + padding-left: 30px; + pointer-events: none; +} + +.collapsed .chapterLink::before { + display: none; +} + .chapter:first-child .sectionWithTransition:first-child .transition { display: none; } +.collapsed .sections, +.collapsed .button { + display: none; +} + .indicator { display: none; position: absolute; @@ -43,5 +70,9 @@ height: 100%; width: 30px; transition: opacity 0.1s ease; - transition-delay: 0.2s; + transition-delay: var(--outline-drag-handle-transition-delay); +} + +.outline:not(.collapsed) .chapterLink .dragHandle { + display: none; } diff --git a/package/package.json b/package/package.json index 48a44589d9..3e6ecacd36 100644 --- a/package/package.json +++ b/package/package.json @@ -27,6 +27,7 @@ "backbone-events-standalone": "^0.2.7", "classlist.js": "^1.1.20150312", "core-js": "^3.4.1", + "sortablejs": "^1.15.3", "i18n-js": "^3.5.1", "postcss-functions": "^3.0.0" } diff --git a/package/src/editor/views/DropDownButtonView.js b/package/src/editor/views/DropDownButtonView.js index 6ff4afb6b0..5feeb6f37e 100644 --- a/package/src/editor/views/DropDownButtonView.js +++ b/package/src/editor/views/DropDownButtonView.js @@ -76,6 +76,7 @@ export const DropDownButtonView = Marionette.ItemView.extend({ this.ui.button.toggleClass('has_icon_and_text', !!this.options.label); this.ui.button.toggleClass('has_icon_only', !this.options.label); this.ui.button.toggleClass('ellipsis_icon', !!this.options.ellipsisIcon); + this.ui.button.toggleClass('borderless', !!this.options.borderless); this.ui.button.text(this.options.label); this.ui.button.addClass(this.options.buttonClassName); diff --git a/package/src/ui/views/SortableCollectionView.js b/package/src/ui/views/SortableCollectionView.js index 3be004e00e..2c31b0ec26 100644 --- a/package/src/ui/views/SortableCollectionView.js +++ b/package/src/ui/views/SortableCollectionView.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import _ from 'underscore'; +import Sortable from 'sortablejs'; import {CollectionView} from './CollectionView'; @@ -7,38 +8,52 @@ export const SortableCollectionView = CollectionView.extend({ render: function() { CollectionView.prototype.render.call(this); - this.$el.sortable({ - connectWith: this.options.connectWith, - placeholder: 'sortable-placeholder', - forcePlaceholderSize: true, - delay: 200, + this.sortable = Sortable.create(this.el, { + group: this.options.connectWith, + animation: 150, - update: _.bind(function(event, ui) { - if (ui.item.parent().is(this.el)) { + ghostClass: 'sortable-placeholder', + + onEnd: event => { + const item = $(event.item); + + if (item.parent().is(this.el)) { this.updateOrder(); } - }, this), + }, - receive: _.bind(function(event, ui) { - var view = ui.item.data('view'); + onRemove: event => { + const view = $(event.item).data('view'); - this.reindexPositions(); + this.itemViews.remove(view); + this.collection.remove(view.model); + }, - this.itemViews.add(view); - this.collection.add(view.model); - }, this), + onSort: event => { + if (event.from !== event.to && event.to === this.el) { + const view = $(event.item).data('view'); - remove: _.bind(function(event, ui) { - var view = ui.item.data('view'); + this.reindexPositions(); - this.itemViews.remove(view); - this.collection.remove(view.model); - }, this) + this.itemViews.add(view); + this.collection.add(view.model); + + this.collection.saveOrder(); + } + } }); return this; }, + disableSorting() { + this.sortable.option('disabled', true); + }, + + enableSorting() { + this.sortable.option('disabled', false); + }, + addItem: function(item) { if (!this.itemViews.findByModel(item)) { CollectionView.prototype.addItem.call(this, item); @@ -63,4 +78,4 @@ export const SortableCollectionView = CollectionView.extend({ $(this).data('view').model.set('position', index); }); } -}); \ No newline at end of file +}); diff --git a/yarn.lock b/yarn.lock index 2d195dffa7..f3d1b113b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11795,6 +11795,11 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +sortablejs@^1.15.3: + version "1.15.3" + resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.3.tgz#033668db5ebfb11167d1249ab88e748f27959e29" + integrity sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg== + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"