diff options
| author | 2022-02-05 13:13:26 +0100 | |
|---|---|---|
| committer | 2022-02-05 13:13:26 +0100 | |
| commit | 9224668285d47830830abd0f123650cadea79f80 (patch) | |
| tree | 18109297821399e4779905fa66f6d3f9720a8392 | |
| parent | 87b181af21ad3644e30bdf44b6704083c8a391a2 (diff) | |
Improve subscription list drag and drop (#3953)
* it works
* more beautiful
* optimize JS
* CSS - optimized for dark theme
* delete not used form
* phpcs
* optimize
* more optimization
* fixed javaScript syntax
* better css class name
* template.css RTL
* fix failed test for RTL
* fix broken CSS Syntax
* fixed failed test
* fixed: empty lines in RTL CSS
* again a fixed CSS test....
* fixed test
* less magic numbers
| -rw-r--r-- | app/views/subscription/index.phtml | 81 | ||||
| -rw-r--r-- | p/scripts/category.js | 65 | ||||
| -rw-r--r-- | p/themes/base-theme/template.css | 54 | ||||
| -rw-r--r-- | p/themes/base-theme/template.rtl.css | 54 |
4 files changed, 182 insertions, 72 deletions
diff --git a/app/views/subscription/index.phtml b/app/views/subscription/index.phtml index 92b1c7ec9..8b9176e27 100644 --- a/app/views/subscription/index.phtml +++ b/app/views/subscription/index.phtml @@ -3,7 +3,7 @@ $this->partial('aside_subscription'); ?> -<main class="post drop-section"> +<main class="post"> <div class="link-back-wrapper"> <a class="link-back" href="<?= _url('index', 'index') ?>"><?= _t('gen.action.back_to_rss_feeds') ?></a> </div> @@ -25,55 +25,53 @@ <a class="btn" href="<?= _url('subscription', 'index', 'error', '1') ?>"><?= _i('look') ?> <?= _t('sub.feed.show.error') ?></a> </div> <?php } ?> - - <form id="controller-category" method="post" aria-hidden="true"> - <input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" /> - </form> - - <?php - $signalError = false; - foreach ($this->categories as $cat) { - $feeds = $cat->feeds(); - ?> - <div class="box"> - <div class="box-title"> - <a class="configure open-slider" href="<?= _url('subscription', 'category', 'id', $cat->id()) ?>"><?= _i('configure') ?></a> - <?= $cat->name() ?> - </div> - <ul class="box-content" data-cat-id="<?= $cat->id() ?>"> - <?php if (!empty($feeds)) { ?> - <?php + + <div class="drop-section"> + <?php + $signalError = false; + foreach ($this->categories as $cat) { + $feeds = $cat->feeds(); + ?> + <div class="box"> + <div class="box-title"> + <a class="configure open-slider" href="<?= _url('subscription', 'category', 'id', $cat->id()) ?>"><?= _i('configure') ?></a> + <?= $cat->name() ?> + </div> + <ul class="box-content drop-zone" dropzone="move" data-cat-id="<?= $cat->id() ?>"> + <?php + if (!empty($feeds)) { foreach ($feeds as $feed) { if ($this->onlyFeedsWithError && !$feed->inError()) { continue; } $error = $feed->inError() ? ' error' : ''; $empty = $feed->nbEntries() == 0 ? ' empty' : ''; - ?> - <li class="item feed<?= $error, $empty, $feed->mute() ? ' mute' : '' ?>" - draggable="true" - data-feed-id="<?= $feed->id() ?>" - dropzone="move"> - <a class="configure open-slider" href="<?= _url('subscription', 'feed', 'id', $feed->id()) ?>"><?= _i('configure') ?></a> - <?php if (FreshRSS_Context::$user_conf->show_favicons): ?><img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php endif; ?> - <?= $feed->name() ?> - </li> - <?php } + ?> + <li class="item feed<?= $error, $empty, $feed->mute() ? ' mute' : '' ?>" + draggable="true" + data-feed-id="<?= $feed->id() ?>"> + <a class="configure open-slider" href="<?= _url('subscription', 'feed', 'id', $feed->id()) ?>"><?= _i('configure') ?></a> + <?php if (FreshRSS_Context::$user_conf->show_favicons): ?><img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php endif; ?> + <?= $feed->name() ?> + </li> + <?php + } } else { - ?> - <li class="item feed disabled" dropzone="move"><div class="alert-warn"><?= _t('sub.category.empty') ?></div></li> - <?php } ?> - <li class="item feed">✚ <a href="<?= _url('subscription', 'add') ?>"><?= _t('sub.feed.add') ?></a></li> - </ul> - </div> - <?php } ?> + ?> + <li class="item feed disabled"><div class="alert-warn"><?= _t('sub.category.empty') ?></div></li> + <?php } ?> + <li class="item feed">✚ <a href="<?= _url('subscription', 'add') ?>"><?= _t('sub.feed.add') ?></a></li> + </ul> + </div> + <?php } ?> - <div class="box visible-semi"> - <div class="box-title"> - ✚ <a href="<?= _url('subscription', 'add') ?>"><?= _t('sub.category.add') ?></a> + <div class="box visible-semi"> + <div class="box-title"> + ✚ <a href="<?= _url('subscription', 'add') ?>"><?= _t('sub.category.add') ?></a> + </div> + <div class="box-content"> + </div> </div> - <ul class="box-content"> - </ul> </div> <?php $class = $this->displaySlider ? ' class="active"' : ''; ?> @@ -89,5 +87,4 @@ } ?> </div> - </main> diff --git a/p/scripts/category.js b/p/scripts/category.js index cb8616b97..d83b3b22b 100644 --- a/p/scripts/category.js +++ b/p/scripts/category.js @@ -25,7 +25,7 @@ function dragend_process(t) { if (p.childElementCount <= 1) { p.insertAdjacentHTML('afterbegin', - '<li class="item feed disabled" dropzone="move"><div class="alert-warn">' + context.i18n.category_empty + '</div></li>'); + '<li class="item feed disabled"><div class="alert-warn">' + context.i18n.category_empty + '</div></li>'); } } } @@ -47,41 +47,53 @@ function init_draggable() { const dropSection = document.querySelector('.drop-section'); dropSection.ondragstart = function (ev) { - const li = ev.target.closest ? ev.target.closest(draggable) : null; - if (li) { + const li_draggable = ev.target.closest ? ev.target.closest(draggable) : null; + if (li_draggable) { + const ulClosest = li_draggable.closest('ul'); + ulClosest.classList.add('drag-disallowed'); + ulClosest.removeAttribute('dropzone', ''); const drag = ev.target.closest('[draggable]'); ev.dataTransfer.effectAllowed = 'move'; dragHtml = drag.outerHTML; dragFeedId = drag.getAttribute('data-feed-id'); ev.dataTransfer.setData('text', dragFeedId); - drag.style.opacity = 0.3; + drag.style.opacity = 0.5; + drag.classList.add('dragging'); + li_draggable.closest('.drop-section').classList.add('drag-active'); dnd_successful = false; } }; dropSection.ondragend = function (ev) { - const li = ev.target.closest ? ev.target.closest(draggable) : null; - if (li) { - dragend_process(li); + const li_draggable = ev.target.closest ? ev.target.closest(draggable) : null; + if (li_draggable) { + dragend_process(li_draggable); + } + li_draggable.classList.remove('dragging'); + const disallowDragging = document.getElementsByClassName('drag-disallowed'); + for (let i = 0; i < disallowDragging.length; i++) { + disallowDragging[i].setAttribute('dropzone', 'move'); + disallowDragging[i].classList.remove('drag-disallowed'); } + li_draggable.closest('.drag-active').classList.remove('drag-active'); }; dropSection.ondragenter = function (ev) { - const li = ev.target.closest ? ev.target.closest(dropzone) : null; - if (li) { - li.classList.add('drag-hover'); + const ul_dropzone = ev.target.closest ? ev.target.closest(dropzone) : null; + if (ul_dropzone) { + ul_dropzone.classList.add('drag-hover'); return false; } }; dropSection.ondragleave = function (ev) { - const li = ev.target.closest ? ev.target.closest(dropzone) : null; - if (li) { + const ul_dropzone = ev.target.closest ? ev.target.closest(dropzone) : null; + if (ul_dropzone) { const scroll_top = document.documentElement.scrollTop; - const top = li.offsetTop; - const left = li.offsetLeft; - const right = left + li.clientWidth; - const bottom = top + li.clientHeight; + const top = ul_dropzone.offsetTop; + const left = ul_dropzone.offsetLeft; + const right = left + ul_dropzone.clientWidth; + const bottom = top + ul_dropzone.clientHeight; const mouse_x = ev.screenX; const mouse_y = ev.clientY + scroll_top; @@ -90,21 +102,22 @@ function init_draggable() { // HACK because dragleave is triggered when hovering children! return; } - li.classList.remove('drag-hover'); + ul_dropzone.classList.remove('drag-hover'); } }; dropSection.ondragover = function (ev) { const li = ev.target.closest ? ev.target.closest(dropzone) : null; if (li) { + li.closest('ul').classList.remove('drag-drop'); ev.dataTransfer.dropEffect = 'move'; return false; } }; dropSection.ondrop = function (ev) { - const li = ev.target.closest ? ev.target.closest(dropzone) : null; - if (li) { + const ul_dropzone = ev.target.closest ? ev.target.closest(dropzone) : null; + if (ul_dropzone) { loading = true; const req = new XMLHttpRequest(); @@ -112,9 +125,12 @@ function init_draggable() { req.responseType = 'json'; req.onload = function (e) { if (this.status == 200) { - li.insertAdjacentHTML('afterend', dragHtml); - if (li.classList.contains('disabled')) { - li.remove(); + ul_dropzone.insertAdjacentHTML('afterbegin', dragHtml); + ul_dropzone.firstChild.classList.add('moved'); + ul_dropzone.scrollTop = 0; + const disabledElement = ul_dropzone.getElementsByClassName('disabled'); + if (disabledElement.length > 0) { + disabledElement[0].remove(); } dnd_successful = true; } @@ -127,11 +143,12 @@ function init_draggable() { req.setRequestHeader('Content-Type', 'application/json'); req.send(JSON.stringify({ f_id: dragFeedId, - c_id: li.parentElement.getAttribute('data-cat-id'), + c_id: ul_dropzone.getAttribute('data-cat-id'), _csrf: context.csrf, })); - li.classList.remove('drag-hover'); + ul_dropzone.closest('ul').classList.add('drag-drop'); + ul_dropzone.closest('ul').classList.remove('drag-hover'); return false; } }; diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index 649188432..2642d9cac 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -516,6 +516,14 @@ a.btn { display: block; } +.box .box-content .item.feed.moved { + font-style: italic; +} + +.box .box-content .item.feed.moved .favicon { + opacity: 0.4; +} + .box .box-content .item.disabled { text-align: center; font-style: italic; @@ -531,13 +539,53 @@ a.btn { } /*=== Draggable */ -.drag-hover { +[draggable=true]:hover { + cursor: move; +} + +.dragging { + background-color: rgba(255, 255, 0, 0.7); +} + +.dragging .icon { + visibility: hidden; +} + +.drag-disallowed { + opacity: 0.5; +} + +.drag-active .drop-zone:not(.drag-disallowed) { + background: repeating-linear-gradient(45deg, transparent, transparent 40px, rgba(250,250,210, 0.7) 40px, rgba(250,250,210, 0.7) 60px); +} + +.drag-active .drag-hover.drop-zone { + background: rgba(250,250,210, 0.7); + transition: background 0.5s; +} + +li.drag-hover { margin: 0 0 5px; border-bottom: 2px solid #ccc; } -[draggable=true] { - cursor: grab; +.drag-drop { + animation-name: droppedKeyframe; + animation-duration: 0.7s; +} + +@keyframes droppedKeyframe { + 0% { + background-color: rgba(250,250,210, 0.7); + } + + 50% { + background-color: yellow; + } + + 100% { + background-color: none; + } } /*=== Scrollbar */ diff --git a/p/themes/base-theme/template.rtl.css b/p/themes/base-theme/template.rtl.css index b92f14c0c..0e5dbde39 100644 --- a/p/themes/base-theme/template.rtl.css +++ b/p/themes/base-theme/template.rtl.css @@ -516,6 +516,14 @@ a.btn { display: block; } +.box .box-content .item.feed.moved { + font-style: italic; +} + +.box .box-content .item.feed.moved .favicon { + opacity: 0.4; +} + .box .box-content .item.disabled { text-align: center; font-style: italic; @@ -531,13 +539,53 @@ a.btn { } /*=== Draggable */ -.drag-hover { +[draggable=true]:hover { + cursor: move; +} + +.dragging { + background-color: rgba(255, 255, 0, 0.7); +} + +.dragging .icon { + visibility: hidden; +} + +.drag-disallowed { + opacity: 0.5; +} + +.drag-active .drop-zone:not(.drag-disallowed) { + background: repeating-linear-gradient(-45deg, transparent, transparent 40px, rgba(250,250,210, 0.7) 40px, rgba(250,250,210, 0.7) 60px); +} + +.drag-active .drag-hover.drop-zone { + background: rgba(250,250,210, 0.7); + transition: background 0.5s; +} + +li.drag-hover { margin: 0 0 5px; border-bottom: 2px solid #ccc; } -[draggable=true] { - cursor: grab; +.drag-drop { + animation-name: droppedKeyframe; + animation-duration: 0.7s; +} + +@keyframes droppedKeyframe { + 0% { + background-color: rgba(250,250,210, 0.7); + } + + 50% { + background-color: yellow; + } + + 100% { + background-color: none; + } } /*=== Scrollbar */ |
