summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorHK2FB <tom.schmidt@helsana.ch>2022-05-15 13:16:56 +0200
committerSean Molenaar <sean@seanmolenaar.eu>2022-05-22 16:07:30 +0200
commitb036d309c27132e4f10df952e9335afdb3edfeb5 (patch)
treee888f1fa178f2fe36baefcfb2ccc080ef27645ce /src
parent50c8960f8d36ab6012f62034f815561078380dc0 (diff)
Vue Rewrite
Signed-off-by: Sean Molenaar <sean@seanmolenaar.eu>
Diffstat (limited to 'src')
-rw-r--r--src/App.vue26
-rw-r--r--src/README.md31
-rw-r--r--src/components/AddFeed.vue148
-rw-r--r--src/components/Explore.vue78
-rw-r--r--src/components/Sidebar.vue164
-rw-r--r--src/main.js126
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),
+})