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_browse')} + + + {_('source_delete')} + + {sourceBeingDeleted && + + {' '} + + + } + + +
{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); +}