aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar maTh <math-home@web.de> 2022-02-05 13:13:26 +0100
committerGravatar GitHub <noreply@github.com> 2022-02-05 13:13:26 +0100
commit9224668285d47830830abd0f123650cadea79f80 (patch)
tree18109297821399e4779905fa66f6d3f9720a8392
parent87b181af21ad3644e30bdf44b6704083c8a391a2 (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.phtml81
-rw-r--r--p/scripts/category.js65
-rw-r--r--p/themes/base-theme/template.css54
-rw-r--r--p/themes/base-theme/template.rtl.css54
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 */