diff options
author | HK2FB <tom.schmidt@helsana.ch> | 2022-05-15 13:16:56 +0200 |
---|---|---|
committer | Sean Molenaar <sean@seanmolenaar.eu> | 2022-05-22 16:07:30 +0200 |
commit | b036d309c27132e4f10df952e9335afdb3edfeb5 (patch) | |
tree | e888f1fa178f2fe36baefcfb2ccc080ef27645ce /src | |
parent | 50c8960f8d36ab6012f62034f815561078380dc0 (diff) |
Vue Rewrite
Signed-off-by: Sean Molenaar <sean@seanmolenaar.eu>
Diffstat (limited to 'src')
-rw-r--r-- | src/App.vue | 26 | ||||
-rw-r--r-- | src/README.md | 31 | ||||
-rw-r--r-- | src/components/AddFeed.vue | 148 | ||||
-rw-r--r-- | src/components/Explore.vue | 78 | ||||
-rw-r--r-- | src/components/Sidebar.vue | 164 | ||||
-rw-r--r-- | src/main.js | 126 |
6 files changed, 573 insertions, 0 deletions
diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 000000000..b4119937e --- /dev/null +++ b/src/App.vue @@ -0,0 +1,26 @@ +<template> + <Content app-name="news"> + <Sidebar /> + <AppContent> + <router-view></router-view> + </AppContent> + </Content> +</template> + +<script> +import Content from '@nextcloud/vue/dist/Components/Content' +import AppContent from '@nextcloud/vue/dist/Components/AppContent' +import Sidebar from './components/Sidebar.vue' + +export default { + components: { + Content, + Sidebar, + AppContent, + }, + created() { + this.$store.dispatch('loadFolder') + } + +} +</script> diff --git a/src/README.md b/src/README.md new file mode 100644 index 000000000..5edd4eb7c --- /dev/null +++ b/src/README.md @@ -0,0 +1,31 @@ +# JavaScript Development +JavaScript is built and minified using webpack. + +Therefore you need to install **Node.js 6+ and npm**. + +Then install the local dependencies by running: + + npm install + +## Tasks +The following tasks are available: + +* **Build the JavaScript**: + + npm run build + +* **Build the JavaScript in Dev Mode**: + + npm run build + +* **Watch for changes and build JavaScript**: + + npm run watch + +* **Run JavaScript unit tests**: + + npm run karma + +* **Watch for changes and run JavaScript unit tests**: + + npm run watch-karma diff --git a/src/components/AddFeed.vue b/src/components/AddFeed.vue new file mode 100644 index 000000000..2adacd8ad --- /dev/null +++ b/src/components/AddFeed.vue @@ -0,0 +1,148 @@ +<template> + <Modal @close="$emit('close')"> + <div id="new-feed" news-add-feed="Navigation.feed"> + + <form ng-submit="Navigation.createFeed(Navigation.feed)" + ng-init="Navigation.feed.autoDiscover=true" + name="feedform"> + <fieldset ng-disabled="Navigation.addingFeed" style="padding: 16px"> + <input type="text" + :value="feed" + ng-model="Navigation.feed.url" + ng-class="{'ng-invalid': + !Navigation.addingFeed && + Navigation.feedUrlExists(Navigation.feed.url) + }" + :placeholder="t('news','Web address')" + name="address" + pattern="[^\s]+" + required + autofocus> + + <p class="error" + ng-show="!Navigation.addingFeed && + Navigation.feedUrlExists(Navigation.feed.url)"> + {{ t('news', 'Feed exists already!') }} + </p> + + <!-- select a folder --> + <CheckboxRadioSwitch :checked.sync="createNewFolder" type="switch"> + {{ t('news', 'New folder') }}? + </CheckboxRadioSwitch> + + <Multiselect v-if="!createNewFolder" v-model="folder" :options="folders" track-by="id" label="name"/> + + <!-- add a folder --> + <input type="text" + ng-model="Navigation.feed.newFolder" + ng-class="{'ng-invalid': + !Navigation.addingFeed && + !Navigation.addingFeed && + Navigation.showNewFolder && + Navigation.folderNameExists( + Navigation.feed.newFolder + ) + }" + :placeholder="t('news','Folder name')" + name="folderName" + v-if="createNewFolder" + style="width: 90%" + required> + + + <p class="error" ng-show="!Navigation.addingFeed && + Navigation.folderNameExists(Navigation.feed.newFolder)"> + {{ t('news', 'Folder exists already!') }} + </p> + + <!-- basic auth --> + + <CheckboxRadioSwitch :checked.sync="withBasicAuth" type="switch"> + {{ t('news', 'Credentials') }}? + </CheckboxRadioSwitch> + + <div v-if="withBasicAuth" class="add-feed-basicauth"> + <p class="warning">{{ + t('news', + 'HTTP Basic Auth credentials must be stored unencrypted! Everyone with access to the server or database will be able to access them!') + }}></p> + <input type="text" + ng-model="Navigation.feed.user" + :placeholder="t('news','Username')" + name="user" + autofocus> + + <input type="password" + ng-model="Navigation.feed.password" + :placeholder="t('news','Password')" + name="password" autocomplete="new-password"> + </div> + + <CheckboxRadioSwitch :checked.sync="autoDiscover" type="switch"> + {{ t('news', 'Auto discover Feed') }}? + </CheckboxRadioSwitch> + + <Button :wide="true" type="primary" @click="addFeed()" ng-disabled=" + Navigation.feedUrlExists(Navigation.feed.url) || + ( + Navigation.showNewFolder && + Navigation.folderNameExists(folder.name) + )"> + {{t('news','Subscribe')}} + </Button> + </fieldset> + </form> + </div> + </Modal> +</template> + +<script> +import Modal from '@nextcloud/vue/dist/Components/Modal' +import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch' +import Button from '@nextcloud/vue/dist/Components/Button' +import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' + +export default { + components: { + Modal, + CheckboxRadioSwitch, + Button, + Multiselect + }, + computed: { + folders() { + return this.$store.state.folders + } + }, + methods: { + newFolder() { + this.createNewFolder = true; + }, + abortNewFolder() { + this.createNewFolder = false; + }, + addFeed() { + this.$store.dispatch('addFeed', {feedReq: { url: this.feed, folder: this.folder, autoDiscover: true}}) + } + }, + props: { + feed: '', + folder: {}, + autoDiscover: true, + withBasicAuth: false, + createNewFolder: false + }, + emits: ['close'] +} +</script> + +<style scoped> + + input { + width: 100%; + } + .multiselect { + width: 100%; + } + +</style> diff --git a/src/components/Explore.vue b/src/components/Explore.vue new file mode 100644 index 000000000..5738649cf --- /dev/null +++ b/src/components/Explore.vue @@ -0,0 +1,78 @@ +<template> + <div id="explore"> + <AddFeed v-if="showAddFeed" @close="closeShowAddFeed()" :feed="feed"></AddFeed> + <div class="grid-container"> + <div v-for="entry in explorableSites" class="explore-feed grid-item"> + <h2 class="explore-title" v-if="entry.favicon" + :style="{ backgroundImage: 'url(' + entry.favicon + ')'}"> + <a target="_blank" rel="noreferrer" :href="entry.url">{{ entry.title }}</a> + </h2> + <h2 class="icon-rss explore-title" v-if="!entry.favicon"> + {{ entry.title }} + </h2> + <div class="explore-content"> + <p>{{ entry.description }}</p> + + <div class="explore-logo"> + <img :src="entry.image"> + </div> + </div> + <Button @click="subscribe(entry.feed)"> + {{ t('news', 'Subscribe to') }} {{ entry.title }} + </Button> + </div> + </div> + </div> +</template> + +<script> +import Modal from '@nextcloud/vue/dist/Components/Modal' +import Button from '@nextcloud/vue/dist/Components/Button' +import axios from "@nextcloud/axios"; +import AddFeed from "./AddFeed"; +import {generateUrl} from "@nextcloud/router"; + +export default { + components: { + Modal, + Button, + AddFeed + }, + props: { + explorableSites: { + type: Array, + default: () => [], + required: true, + }, + showAddFeed: false, + feed: '' + }, + methods: { + async sites() { + const settings = await axios.get(generateUrl("/apps/news/settings")); + console.log(settings.data); + console.log(settings.data.settings.exploreUrl); + + const exploreUrl = settings.data.settings.exploreUrl + 'feeds.en.json'; + const explore = await axios.get(exploreUrl); + console.log(explore.data); + + Object.keys(explore.data).forEach(key => + explore.data[key].forEach(value => + this.explorableSites.push(value) + ) + ); + }, + async subscribe(feed) { + this.feed = feed; + this.showAddFeed = true; + }, + closeShowAddFeed() { + this.showAddFeed = false; + } + }, + created() { + this.sites(); + } +} +</script> diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue new file mode 100644 index 000000000..33923f89b --- /dev/null +++ b/src/components/Sidebar.vue @@ -0,0 +1,164 @@ +<template> + <AppNavigation> + <AddFeed v-if="showAddFeed" @close="closeShowAddFeed()"></AddFeed> + <AppNavigationNew + :text="t('news','Subscribe')" + button-id="new-feed-button" + button-class="icon-add" + @click="showShowAddFeed()"/> + + <ul id="locations" class="with-icon"> + + <AppNavigationNewItem :title="t('news','New folder')" + icon="icon-add-folder" + @new-item="newFolder"> + </AppNavigationNewItem> + + <AppNavigationItem :title="t('news','Unread articles')" icon="icon-rss"> + <template #actions> + <ActionButton icon="icon-checkmark" @click="alert('Edit')"> + t('news','Mark read') + </ActionButton> + </template> + <template #counter> + <CounterBubble>5</CounterBubble> + </template> + </AppNavigationItem> + <AppNavigationItem :title="t('news','All articles')" icon="icon-rss"> + <template #actions> + <ActionButton icon="icon-checkmark" @click="alert('Edit')"> + t('news','Mark read') + </ActionButton> + </template> + </AppNavigationItem> + <AppNavigationItem :title="t('news','Starred')" icon="icon-starred"> + <template #counter> + <CounterBubble>35</CounterBubble> + </template> + </AppNavigationItem> + + <AppNavigationItem v-for="folder in folders" :title="folder.name" icon="icon-folder" + :allowCollapse="true"> + <template #default> + <AppNavigationItem v-for="feed in folder.feeds" :title="feed.title" > + <template #icon> + <img :src="feed.faviconLink" v-if="feed.faviconLink" alt="feedIcon"> + <div class="icon-rss" v-if="!feed.faviconLink"></div> + </template> + <template #actions> + <ActionButton icon="icon-checkmark" @click="alert('Mark read')"> + {{ t('news', 'Mark read') }} + </ActionButton> + <ActionButton icon="icon-pinned" @click="alert('Rename')"> + {{ t('news', 'Unpin from top') }} + </ActionButton> + <ActionButton icon="icon-caret-dark" @click="deleteFolder(folder)"> + {{ t('news', 'Newest first') }} + </ActionButton> + <ActionButton icon="icon-caret-dark" @click="deleteFolder(folder)"> + {{ t('news', 'Oldest first') }} + </ActionButton> + <ActionButton icon="icon-caret-dark" @click="deleteFolder(folder)"> + {{ t('news', 'Default order') }} + </ActionButton> + <ActionButton icon="icon-full-text-disabled" @click="deleteFolder(folder)"> + {{ t('news', 'Enable full text') }} + </ActionButton> + <ActionButton icon="icon-full-text-enabled" @click="deleteFolder(folder)"> + {{ t('news', 'Disable full text') }} + </ActionButton> + <ActionButton icon="icon-updatemode-default" @click="deleteFolder(folder)"> + {{ t('news', 'Unread updated') }} + </ActionButton> + <ActionButton icon="icon-updatemode-unread" @click="deleteFolder(folder)"> + {{ t('news', 'Ignore updated') }} + </ActionButton> + <ActionButton icon="icon-icon-rss" @click="deleteFolder(folder)"> + {{ t('news', 'Open feed URL') }} + </ActionButton> + <ActionButton icon="icon-icon-rename" @click="deleteFolder(folder)"> + {{ t('news', 'Rename') }} + </ActionButton> + <ActionButton icon="icon-delete" @click="deleteFolder(folder)"> + {{ t('news', 'Delete') }} + </ActionButton> + </template> + </AppNavigationItem> + </template> + <template #counter v-if="folder.feedCount > 0"> + <CounterBubble>{{ folder.feedCount }}</CounterBubble> + </template> + <template #actions> + <ActionButton icon="icon-checkmark" @click="alert('Mark read')"> + {{ t('news', 'Mark read') }} + </ActionButton> + <ActionButton icon="icon-rename" @click="alert('Rename')"> + {{ t('news', 'Rename') }} + </ActionButton> + <ActionButton icon="icon-delete" @click="deleteFolder(folder)"> + {{ t('news', 'Delete') }} + </ActionButton> + </template> + </AppNavigationItem> + + <AppNavigationItem :title="t('news','Explore')" + icon="icon-link" + :to="{ name: 'explore' }"> + <template #counter> + <CounterBubble>35</CounterBubble> + </template> + </AppNavigationItem> + </ul> + </AppNavigation> +</template> + +<script> +import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation' +import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew' +import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem' +import AppNavigationNewItem from '@nextcloud/vue/dist/Components/AppNavigationNewItem' +import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter' +import CounterBubble from '@nextcloud/vue/dist/Components/CounterBubble' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import AddFeed from "./AddFeed"; + +export default { + components: { + AppNavigation, + AppNavigationNew, + AppNavigationItem, + AppNavigationNewItem, + AppNavigationCounter, + CounterBubble, + ActionButton, + AddFeed + }, + props: { + showAddFeed: false, + }, + computed: { + folders() { + return this.$store.state.folders + }, + }, + methods: { + newFolder(value) { + const folderName = value.trim(); + const folder = {name: folderName}; + this.$store.dispatch('addFolder', {folder}) + }, + deleteFolder(folder) { + this.$store.dispatch('deleteFolder', {folder}) + window.location.reload(true); + }, + showShowAddFeed() { + this.showAddFeed = true; + }, + closeShowAddFeed() { + this.showAddFeed = false; + } + }, + created() { + } +} +</script> diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..51aec5617 --- /dev/null +++ b/src/main.js @@ -0,0 +1,126 @@ +import Vue from 'vue' +import App from './App' +import VueRouter from 'vue-router' +import Explore from './components/Explore' +import {generateUrl} from "@nextcloud/router"; +import Vuex from 'vuex' +import axios from "@nextcloud/axios"; + +Vue.prototype.t = t +Vue.prototype.n = n +Vue.prototype.OC = OC +Vue.prototype.OCA = OCA + +Vue.use(Vuex) +Vue.use(VueRouter) + +import { Tooltip } from '@nextcloud/vue' + +Vue.directive('tooltip', Tooltip) + +const feedUrl = generateUrl("/apps/news/feeds") +const folderUrl = generateUrl("/apps/news/folders") + +const routes = [ + { + name: 'explore', + path: '#explore', + component: Explore + }, +] + +const router = new VueRouter({ + mode: 'history', + base: generateUrl('apps/news'), + routes +}); + +const store = new Vuex.Store({ + state: { + folders: [], + feeds: [] + }, + mutations: { + addFolders(state, folders) { + folders.forEach(it => { + it.feedCount = 0 + state.folders.push(it) + }) + }, + addFeeds(state, feeds) { + feeds.forEach(it => { + state.feeds.push(it) + const folder = state.folders.find(folder => folder.id === it.folderId) + folder.feeds.push(it) + folder.feedCount += it.unreadCount + }) + } + }, + actions: { + addFolder({commit}, {folder}) { + axios.post(folderUrl, {folderName: folder.name}).then( + response => commit('addFolders', response.data.folders) + ); + }, + deleteFolder({commit}, {folder}) { + /** + this.getByFolderId(folderId).forEach(function (feed) { + promises.push(self.reversiblyDelete(feed.id, false, true)); + }); + + this.updateUnreadCache(); + */ + axios.delete(folderUrl + '/' + folder.id).then() + }, + loadFolder({commit}) { + console.log('loading folders') + axios.get(folderUrl).then( + response => { + commit('addFolders', response.data.folders); + axios.get(feedUrl).then( + response => commit('addFeeds', response.data.feeds) + ) + } + ) + }, + addFeed({commit}, {feedReq}) { + console.log(feedReq) + let url = feedReq.url.trim(); + if (!url.startsWith('http')) { + url = 'https://' + url; + } + + /** + if (title !== undefined) { + title = title.trim(); + } + */ + + let feed = { + url: url, + folderId: feedReq.folder.id || 0, + title: null, + unreadCount: 0 + }; + + // this.add(feed); + // this.updateFolderCache(); + + axios.post(feedUrl, { + url: feed.url, + parentFolderId: feed.folderId, + title: null, + user: null, + password: null, + fullDiscover: feed.autoDiscover + }).then(); + } + } +}); + +export default new Vue({ + router, + store, + el: '#content', + render: h => h(App), +}) |