diff --git a/NEWS.md b/NEWS.md
index df131c3602..5190bf50e5 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -19,6 +19,7 @@
- Search will be carried out using regular expressions when the search query is wrapped in forward slashes, e.g. `/regex/`. The expression syntax is database specific. ([#1205](https://github.com/fossar/selfoss/pull/1205))
- YouTube spout now supports following playlists. ([#1260](https://github.com/fossar/selfoss/pull/1260))
- Confirmation is now required when leaving the setting page with unsaved source changes. ([#1300](https://github.com/fossar/selfoss/pull/1300))
+- Add link from settings page to individual sources. ([#1329](https://github.com/fossar/selfoss/pull/1329))
- Translations into several new languages were added:
- English (United Kingdom): `en-GB`
- French (Canada): `fr-CA`
diff --git a/assets/js/templates/Source.jsx b/assets/js/templates/Source.jsx
index d70c1f80cd..43a2ca3d2e 100644
--- a/assets/js/templates/Source.jsx
+++ b/assets/js/templates/Source.jsx
@@ -1,4 +1,7 @@
import React from 'react';
+import { Button as MenuButton, Wrapper as MenuWrapper, Menu, MenuItem } from 'react-aria-menubutton';
+import { useHistory, useLocation } from 'react-router-dom';
+import { makeEntriesLinkLocation } from '../helpers/uri';
import PropTypes from 'prop-types';
import nullable from 'prop-types-nullable';
import { unescape } from 'html-escaper';
@@ -562,16 +565,24 @@ export default function Source({ source, setSources, spouts, setSpouts, dirty, s
[source.id, setDirtySources]
);
- const deleteOnClick = React.useCallback(
- (event) =>
- handleDelete({
- event,
- source,
- setSources,
- setSourceBeingDeleted,
- setDirty,
- }),
- [source, setSources, setDirty]
+ const history = useHistory();
+ const location = useLocation();
+
+ const extraMenuOnSelection = React.useCallback(
+ (value, event) => {
+ if (value === 'delete') {
+ handleDelete({
+ event,
+ source,
+ setSources,
+ setSourceBeingDeleted,
+ setDirty,
+ });
+ } else if (value === 'browse') {
+ history.push(makeEntriesLinkLocation(location, { category: `source-${source.id}` }));
+ }
+ },
+ [source, setSources, setDirty, location, history]
);
const _ = React.useContext(LocalizationContext);
@@ -612,21 +623,41 @@ export default function Source({ source, setSources, spouts, setSpouts, dirty, s
{' • '}
}
-
+
+ {_('source_menu')}
+
+
+
{source.lastentry
diff --git a/assets/locale/cs.json b/assets/locale/cs.json
index 7b255d80fe..ab20057fdf 100644
--- a/assets/locale/cs.json
+++ b/assets/locale/cs.json
@@ -31,6 +31,8 @@
"lang_source_export": "Export zdrojů",
"lang_source_edit": "Upravit",
"lang_source_filter": "Filtr",
+ "lang_source_menu": "Více akcí",
+ "lang_source_browse": "Prohlížet",
"lang_source_delete": "Smazat",
"lang_source_warn": "Opravdu chcete tento zdroj smazat?",
"lang_source_warn_cancel_dirty": "Opravdu chcete zahodit neuložené změny?",
diff --git a/assets/locale/en.json b/assets/locale/en.json
index 29847e1037..950f719e27 100644
--- a/assets/locale/en.json
+++ b/assets/locale/en.json
@@ -43,6 +43,8 @@
"lang_source_export": "Export sources",
"lang_source_edit": "Edit",
"lang_source_filter": "Filter",
+ "lang_source_menu": "More actions",
+ "lang_source_browse": "Browse",
"lang_source_delete": "Delete",
"lang_source_warn": "Really delete this source?",
"lang_source_warn_cancel_dirty": "Discard unsaved changes?",
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 3acce5ae13..bf1e24dafe 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -25,6 +25,7 @@
"prop-types-nullable": "^1.0.1",
"ramda": "^0.28.0",
"react": "^17.0.1",
+ "react-aria-menubutton": "^7.0.3",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"reset-css": "^5.0.1",
@@ -3076,6 +3077,11 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
+ "node_modules/focus-group": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/focus-group/-/focus-group-0.3.1.tgz",
+ "integrity": "sha1-4PMu2GsNq91v/OvfiY7LMuR/7c4="
+ },
"node_modules/focus-trap": {
"version": "6.7.3",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.7.3.tgz",
@@ -5500,6 +5506,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-aria-menubutton": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/react-aria-menubutton/-/react-aria-menubutton-7.0.3.tgz",
+ "integrity": "sha512-Ql4W3rNiZmuVJ1wQ0UUeV4OZX3IZq2evsfEqJGefSMdfkK6o8X/6Ufxrzu0wL+/Dr7JUY3xnrnIQimSCFghlCQ==",
+ "dependencies": {
+ "focus-group": "^0.3.1",
+ "prop-types": "^15.6.0",
+ "teeny-tap": "^0.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.3.0 || ^17.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -6470,6 +6489,11 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
+ "node_modules/teeny-tap": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/teeny-tap/-/teeny-tap-0.2.0.tgz",
+ "integrity": "sha1-Fn5kUYLQasIi1iuyq2eUenClimg="
+ },
"node_modules/terser": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz",
@@ -8907,6 +8931,11 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true
},
+ "focus-group": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/focus-group/-/focus-group-0.3.1.tgz",
+ "integrity": "sha1-4PMu2GsNq91v/OvfiY7LMuR/7c4="
+ },
"focus-trap": {
"version": "6.7.3",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.7.3.tgz",
@@ -10636,6 +10665,16 @@
"object-assign": "^4.1.1"
}
},
+ "react-aria-menubutton": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/react-aria-menubutton/-/react-aria-menubutton-7.0.3.tgz",
+ "integrity": "sha512-Ql4W3rNiZmuVJ1wQ0UUeV4OZX3IZq2evsfEqJGefSMdfkK6o8X/6Ufxrzu0wL+/Dr7JUY3xnrnIQimSCFghlCQ==",
+ "requires": {
+ "focus-group": "^0.3.1",
+ "prop-types": "^15.6.0",
+ "teeny-tap": "^0.2.0"
+ }
+ },
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -11369,6 +11408,11 @@
}
}
},
+ "teeny-tap": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/teeny-tap/-/teeny-tap-0.2.0.tgz",
+ "integrity": "sha1-Fn5kUYLQasIi1iuyq2eUenClimg="
+ },
"terser": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz",
diff --git a/assets/package.json b/assets/package.json
index 2a2964cadd..3d2a88e943 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -20,6 +20,7 @@
"prop-types-nullable": "^1.0.1",
"ramda": "^0.28.0",
"react": "^17.0.1",
+ "react-aria-menubutton": "^7.0.3",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"reset-css": "^5.0.1",
diff --git a/assets/styles/main.scss b/assets/styles/main.scss
index 3b183ba190..912a9978c2 100644
--- a/assets/styles/main.scss
+++ b/assets/styles/main.scss
@@ -15,6 +15,7 @@ $text-color: black;
// Sass does not interportate code in CSS variable definitions unless explicitly unescaped.
// https://github.com/sass/libsass/issues/2621
--primary: #{$primary};
+ --primary-contrast: #ffffff;
--primary-highlight: #{$primary-highlight};
--primary-highlight-shadow: #{$primary-highlight-shadow};
--text-color: #{$text-color};
@@ -58,6 +59,8 @@ button::-moz-focus-inner {
border: 0;
}
+@import 'popup-menu';
+
#js-loading-message {
margin: 1em;
text-align: center;
@@ -715,7 +718,7 @@ span.offline-count.diff {
}
.source-showparams,
-.source-delete,
+.source-menu-button,
.source-save,
.source-cancel {
padding: 0;
@@ -1406,3 +1409,21 @@ img[src^='https://s.w.org/images/core/emoji']
.collapse-css-transition {
transition: height 280ms cubic-bezier(0.4, 0, 0.2, 1);
}
+
+.menu {
+ width: 200px;
+ display: flex;
+ flex-direction: column;
+ background: #ffffff;
+}
+
+.menu-item {
+ cursor: pointer;
+ padding: 5px;
+ height: 28px;
+ border-bottom: 1px solid rgb(187 187 187);
+}
+
+.menu-item:hover {
+ color: #2980b9;
+}
diff --git a/assets/styles/popup-menu.scss b/assets/styles/popup-menu.scss
new file mode 100644
index 0000000000..df3053c723
--- /dev/null
+++ b/assets/styles/popup-menu.scss
@@ -0,0 +1,27 @@
+.popup-menu-wrapper {
+ display: inline-block;
+ position: relative;
+}
+
+.popup-menu {
+ background: var(--background-color);
+ color: var(--text-color);
+ border: 1px solid rgb(200 200 200 / 40%);
+ position: absolute;
+ top: 100%;
+ left: -1em;
+ z-index: 100;
+ margin: 0.25em 0 0;
+ box-shadow: 0 0.2em 1em rgb(0 0 0 / 15%);
+}
+
+.popup-menu-item {
+ cursor: pointer;
+ padding: 0.3em 1em;
+}
+
+.popup-menu-item:hover,
+.popup-menu-item:focus {
+ background-color: var(--primary);
+ color: var(--primary-contrast);
+}