From a267d62c988c0480e814fb3facdc74b32c722ca4 Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Thu, 3 Oct 2024 14:48:37 +0000 Subject: [PATCH] fix: add user settings (compact and compactExpand are hidden at the moment) Signed-off-by: Wolfgang --- lib/AppInfo/Application.php | 5 + lib/Controller/PageController.php | 24 ++ lib/Listeners/UserSettingsListener.php | 29 +++ src/components/Sidebar.vue | 150 ++++++++++++ .../feed-display/FeedItemDisplayList.vue | 6 +- src/components/modals/HelpModal.vue | 229 ++++++++++++++++++ src/dataservices/item.service.ts | 21 +- src/store/app.ts | 51 ++++ 8 files changed, 502 insertions(+), 13 deletions(-) create mode 100644 lib/Listeners/UserSettingsListener.php create mode 100644 src/components/modals/HelpModal.vue diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index df52cdeb5b..6c307a7820 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -25,6 +25,7 @@ use OCA\News\Search\FolderSearchProvider; use OCA\News\Search\ItemSearchProvider; use OCA\News\Listeners\AddMissingIndicesListener; +use OCA\News\Listeners\UserSettingsListener; use OCA\News\Utility\Cache; use OCP\AppFramework\Bootstrap\IBootContext; @@ -36,6 +37,8 @@ use OCA\News\Fetcher\FeedFetcher; use OCA\News\Fetcher\Fetcher; use OCP\User\Events\BeforeUserDeletedEvent; +use OCP\Config\BeforePreferenceDeletedEvent; +use OCP\Config\BeforePreferenceSetEvent; use OCP\DB\Events\AddMissingIndicesEvent; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; @@ -91,6 +94,8 @@ public function register(IRegistrationContext $context): void $context->registerEventListener(BeforeUserDeletedEvent::class, UserDeleteHook::class); $context->registerEventListener(AddMissingIndicesEvent::class, AddMissingIndicesListener::class); + $context->registerEventListener(BeforePreferenceDeletedEvent::class, UserSettingsListener::class); + $context->registerEventListener(BeforePreferenceSetEvent::class, UserSettingsListener::class); // parameters $context->registerParameter('exploreDir', __DIR__ . '/../Explore/feeds'); diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php index abdd3f2f78..9ef158eaee 100644 --- a/lib/Controller/PageController.php +++ b/lib/Controller/PageController.php @@ -22,6 +22,7 @@ use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http; use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Services\IInitialState; use OCA\News\Service\StatusService; use OCA\News\Explore\RecommendedSites; @@ -57,6 +58,11 @@ class PageController extends Controller */ private $statusService; + /* + * @var IInitialState + */ + private $initialState; + public function __construct( IRequest $request, IConfig $settings, @@ -64,6 +70,7 @@ public function __construct( IL10N $l10n, RecommendedSites $recommendedSites, StatusService $statusService, + IInitialState $initialState, ?IUserSession $userSession ) { parent::__construct($request, $userSession); @@ -72,6 +79,7 @@ public function __construct( $this->l10n = $l10n; $this->recommendedSites = $recommendedSites; $this->statusService = $statusService; + $this->initialState = $initialState; } @@ -92,6 +100,22 @@ public function index(): TemplateResponse ] ); + $usersettings = [ + 'compact', + 'compactExpand', + 'preventReadOnScroll', + 'oldestFirst', + 'showAll' + ]; + foreach ($usersettings as $setting) { + $this->initialState->provideInitialState($setting, $this->settings->getUserValue( + $this->getUserId(), + $this->appName, + $setting, + '0') + ); + } + $csp = new ContentSecurityPolicy(); $csp->addAllowedImageDomain('*') ->addAllowedMediaDomain('*') diff --git a/lib/Listeners/UserSettingsListener.php b/lib/Listeners/UserSettingsListener.php new file mode 100644 index 0000000000..35f3abb44f --- /dev/null +++ b/lib/Listeners/UserSettingsListener.php @@ -0,0 +1,29 @@ + */ +class UserSettingsListener implements IEventListener { + + public function handle(Event $event): void { + if ($event instanceof BeforePreferenceSetEvent) { + if ($event->getAppId() === 'news') { + $event->setValid(true); + } + } elseif ($event instanceof BeforePreferenceDeletedEvent) { + if ($event->getAppId() === 'news') { + $event->setValid(true); + } + } + } +} diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 805b3bdb49..ca80516e84 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -109,6 +109,63 @@ + @@ -116,13 +173,19 @@ import { mapState } from 'vuex' import Vue from 'vue' +import axios from '@nextcloud/axios' +import { generateOcsUrl } from '@nextcloud/router' +import { showError, showSuccess } from '@nextcloud/dialogs' import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js' import NcAppNavigationNew from '@nextcloud/vue/dist/Components/NcAppNavigationNew.js' import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js' import NcAppNavigationNewItem from '@nextcloud/vue/dist/Components/NcAppNavigationNewItem.js' +import NcAppNavigationSettings from '@nextcloud/vue/dist/Components/NcAppNavigationSettings.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import RssIcon from 'vue-material-design-icons/Rss.vue' import FolderIcon from 'vue-material-design-icons/Folder.vue' @@ -137,6 +200,8 @@ import { ACTIONS, AppState } from '../store' import AddFeed from './AddFeed.vue' import SidebarFeedLinkActions from './SidebarFeedLinkActions.vue' +import HelpModal from './modals/HelpModal.vue' +import { subscribe } from '@nextcloud/event-bus' import { Folder } from '../types/Folder' import { Feed } from '../types/Feed' @@ -166,8 +231,11 @@ export default Vue.extend({ NcAppNavigationNew, NcAppNavigationItem, NcAppNavigationNewItem, + NcAppNavigationSettings, + NcCheckboxRadioSwitch, NcCounterBubble, NcActionButton, + NcButton, AddFeed, RssIcon, FolderIcon, @@ -176,23 +244,105 @@ export default Vue.extend({ FolderPlusIcon, PlusIcon, SidebarFeedLinkActions, + HelpModal, }, data: () => { return { showAddFeed: false, ROUTES, + showHelp: false, } }, computed: { ...mapState(['feeds', 'folders', 'items']), ...mapState(SideBarState), + compact: { + get() { + return this.$store.getters.compact + }, + set(newValue) { + this.saveSetting('compact', newValue) + }, + }, + compactExpand: { + get() { + return this.$store.getters.compactExpand + }, + set(newValue) { + this.saveSetting('compactExpand', newValue) + }, + }, + oldestFirst: { + get() { + return this.$store.getters.oldestFirst + + }, + set(newValue) { + this.saveSetting('oldestFirst', newValue) + }, + }, + preventReadOnScroll: { + get() { + return this.$store.getters.preventReadOnScroll + + }, + set(newValue) { + this.saveSetting('preventReadOnScroll', newValue) + }, + }, + showAll: { + get() { + return this.$store.getters.showAll + + }, + set(newValue) { + this.saveSetting('showAll', newValue) + }, + }, }, created() { if (this.$route.query.subscribe_to) { this.showAddFeed = true } }, + mounted() { + subscribe('news:global:toggle-help-dialog', () => { + this.showHelp = !this.showHelp + }) + }, methods: { + async saveSetting(key, value) { + this.$store.commit(key, {value: value}) + const url = generateOcsUrl( + '/apps/provisioning_api/api/v1/config/users/{appId}/{key}', + { + appId: 'news', + key, + }, + ) + value = value ? '1' : '0' + try { + const { data } = await axios.post(url, { + configValue: value, + }) + this.handleResponse({ + status: data.ocs?.meta?.status, + }) + } catch (e) { + this.handleResponse({ + errorMessage: t('news', 'Unable to update news config'), + error: e, + }) + } + }, + handleResponse({ status, errorMessage, error }) { + if (status !== 'ok') { + showError(errorMessage) + console.error(errorMessage, error) + } else { + showSuccess(t('news', 'Successfully updated news configuration')) + } + }, newFolder(value: string) { const folderName = value.trim() const folder = { name: folderName } diff --git a/src/components/feed-display/FeedItemDisplayList.vue b/src/components/feed-display/FeedItemDisplayList.vue index d2fd722f5e..32798760a6 100644 --- a/src/components/feed-display/FeedItemDisplayList.vue +++ b/src/components/feed-display/FeedItemDisplayList.vue @@ -118,10 +118,10 @@ export default Vue.extend({ // Always want to sort by date (most recent first) sort: (a: FeedItem, b: FeedItem) => { - if (a.pubDate > b.pubDate) { - return -1 + if (this.$store.getters.oldestFirst) { + return a.pubDate < b.pubDate ? -1 : 1 } else { - return 1 + return a.pubDate > b.pubDate ? -1 : 1 } }, cache: [] as FeedItem[] | undefined, diff --git a/src/components/modals/HelpModal.vue b/src/components/modals/HelpModal.vue new file mode 100644 index 0000000000..4a9fde964f --- /dev/null +++ b/src/components/modals/HelpModal.vue @@ -0,0 +1,229 @@ + + + + + + diff --git a/src/dataservices/item.service.ts b/src/dataservices/item.service.ts index 348cb936f8..ad012cbe91 100644 --- a/src/dataservices/item.service.ts +++ b/src/dataservices/item.service.ts @@ -1,6 +1,7 @@ import _ from 'lodash' import { AxiosResponse } from 'axios' import axios from '@nextcloud/axios' +import store from './../store/app.ts' import { API_ROUTES } from '../types/ApiRoutes' import { FeedItem } from '../types/FeedItem' @@ -31,9 +32,9 @@ export class ItemService { return await axios.get(API_ROUTES.ITEMS, { params: { limit: 40, - oldestFirst: false, + oldestFirst: store.state.oldestFirst, search: '', - showAll: true, + showAll: store.state.showAll, type: ITEM_TYPES.ALL, offset: start, }, @@ -50,9 +51,9 @@ export class ItemService { return await axios.get(API_ROUTES.ITEMS, { params: { limit: 40, - oldestFirst: false, + oldestFirst: store.state.oldestFirst, search: '', - showAll: true, + showAll: store.state.showAll, type: ITEM_TYPES.STARRED, offset: start, }, @@ -69,9 +70,9 @@ export class ItemService { return await axios.get(API_ROUTES.ITEMS, { params: { limit: 40, - oldestFirst: false, + oldestFirst: store.state.oldestFirst, search: '', - showAll: false, + showAll: store.state.showAll, type: ITEM_TYPES.UNREAD, offset: start, }, @@ -89,9 +90,9 @@ export class ItemService { return await axios.get(API_ROUTES.ITEMS, { params: { limit: 40, - oldestFirst: false, + oldestFirst: store.state.oldestFirst, search: '', - showAll: true, + showAll: store.state.showAll, type: ITEM_TYPES.FEED, offset: start, id: feedId, @@ -110,9 +111,9 @@ export class ItemService { return await axios.get(API_ROUTES.ITEMS, { params: { limit: 40, - oldestFirst: false, + oldestFirst: store.state.oldestFirst, search: '', - showAll: true, + showAll: store.state.showAll, type: ITEM_TYPES.FOLDER, offset: start, id: folderId, diff --git a/src/store/app.ts b/src/store/app.ts index 2b470c9469..d24aef793c 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -1,3 +1,4 @@ +import { loadState } from '@nextcloud/initial-state' import { APPLICATION_MUTATION_TYPES } from '../types/MutationTypes' export const APPLICATION_ACTION_TYPES = { @@ -10,12 +11,32 @@ export type AppInfoState = { const state: AppInfoState = { error: undefined, + compact: loadState('news', 'compact') === '1', + compactExpand: loadState('news', 'compactExpand') === '1', + oldestFirst: loadState('news', 'oldestFirst') === '1', + preventReadOnScroll: loadState('news', 'preventReadOnScroll') === '1', + showAll: loadState('news', 'showAll') === '1' } const getters = { error(state: AppInfoState) { return state.error }, + compact() { + return state.compact + }, + compactExpand() { + return state.compactExpand + }, + oldestFirst() { + return state.oldestFirst + }, + preventReadOnScroll() { + return state.preventReadOnScroll + }, + showAll() { + return state.showAll + } } export const actions = { @@ -31,6 +52,36 @@ export const mutations = { ) { state.error = error }, + compact ( + state: AppInfoState, + { value }: { value: newValue }, + ) { + state.compact = value + }, + compactExpand ( + state: AppInfoState, + { value }: { value: newValue }, + ) { + state.compactExpand = value + }, + oldestFirst ( + state: AppInfoState, + { value }: { value: newValue }, + ) { + state.oldestFirst = value + }, + preventReadOnScroll ( + state: AppInfoState, + { value }: { value: newValue }, + ) { + state.preventReadOnScroll = value + }, + showAll( + state: AppInfoState, + { value }: { value: newValue }, + ) { + state.showAll = value + } } export default {