diff options
author | Carl Schwan <carl@carlschwan.eu> | 2022-08-30 19:27:37 +0200 |
---|---|---|
committer | Carl Schwan <carl@carlschwan.eu> | 2022-08-30 19:33:16 +0200 |
commit | 47cc783b637de75a72a6f8b721edb3cb34934ea0 (patch) | |
tree | 4fd983f9583a3770dbc98d20b66f9787d5f42f9d | |
parent | 753e88793e0525cadd8a544e3f4093cc3cf7b331 (diff) |
Import existing attempt to port to vue
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
-rw-r--r-- | src/App.vue | 25 | ||||
-rw-r--r-- | src/components/AddFeed.vue | 171 | ||||
-rw-r--r-- | src/components/Explore.vue | 78 | ||||
-rw-r--r-- | src/components/Sidebar.vue | 211 | ||||
-rw-r--r-- | src/main.js | 132 | ||||
-rw-r--r-- | templates/index.php | 5 | ||||
-rw-r--r-- | webpack.config.js | 1 |
7 files changed, 623 insertions, 0 deletions
diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 000000000..ad80f18d5 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,25 @@ +<template> + <Content app-name="news"> + <Sidebar /> + <AppContent> + <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/components/AddFeed.vue b/src/components/AddFeed.vue new file mode 100644 index 000000000..7ab421554 --- /dev/null +++ b/src/components/AddFeed.vue @@ -0,0 +1,171 @@ +<template> + <NcModal @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> + + <NcMultiselect v-if="!createNewFolder" + v-model="folder" + :options="folders" + track-by="id" + label="name" /> + + <!-- add a folder --> + <input v-if="createNewFolder" + 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" + 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> + + <NcCheckboxRadioSwitch :checked.sync="autoDiscover" type="switch"> + {{ t("news", "Auto discover Feed") }}? + </NcCheckboxRadioSwitch> + + <NcButton :wide="true" + type="primary" + ng-disabled=" + Navigation.feedUrlExists(Navigation.feed.url) || + ( + Navigation.showNewFolder && + Navigation.folderNameExists(folder.name) + )" + @click="addFeed()"> + {{ t("news", "Subscribe") }} + </NcButton> + </fieldset> + </form> + </div> + </NcModal> +</template> + +<script> +/* eslint-disable vue/require-prop-type-constructor */ + +import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' + +export default { + name: 'AddFeed', + components: { + NcModal, + NcCheckboxRadioSwitch, + NcButton, + NcMultiselect, + }, + props: { + feed: '', + }, + emits: ['close'], + data() { + return { + folder: {}, + autoDiscover: true, + createNewFolder: false, + withBasicAuth: false, + } + }, + 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, + }, + }) + }, + }, +} +</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..562d72ae8 --- /dev/null +++ b/src/components/Explore.vue @@ -0,0 +1,78 @@ +<template> + <div id="explore"> + <AddFeed v-if="showAddFeed" :feed="feed" @close="closeShowAddFeed()" /> + <div class="grid-container"> + <div v-for="entry in exploreSites" + :key="entry.title" + class="explore-feed grid-item"> + <h2 v-if="entry.favicon" + class="explore-title" + :style="{ backgroundImage: 'url(' + entry.favicon + ')' }"> + <a target="_blank" rel="noreferrer" :href="entry.url"> + {{ entry.title }} + </a> + </h2> + <h2 v-if="!entry.favicon" class="icon-rss explore-title"> + {{ 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 NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import axios from '@nextcloud/axios' +import AddFeed from './AddFeed.vue' +import { generateUrl } from '@nextcloud/router' + +export default { + name: 'ExploreComponent', + components: { + NcButton, + AddFeed, + }, + data() { + return { + exploreSites: [], + feed: {}, + showAddFeed: false, + } + }, + created() { + this.sites() + }, + + methods: { + async sites() { + const settings = await axios.get(generateUrl('/apps/news/settings')) + + const exploreUrl = settings.data.settings.exploreUrl + 'feeds.en.json' + const explore = await axios.get(exploreUrl) + + Object.keys(explore.data).forEach((key) => + explore.data[key].forEach((value) => + this.exploreSites.push(value), + ), + ) + }, + async subscribe(feed) { + this.feed = feed + this.showAddFeed = true + }, + closeShowAddFeed() { + this.showAddFeed = false + }, + }, +} +</script> diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue new file mode 100644 index 000000000..0b17b77ed --- /dev/null +++ b/src/components/Sidebar.vue @@ -0,0 +1,211 @@ +<template> + <NcAppNavigation> + <AddFeed v-if="showAddFeed" @close="closeShowAddFeed()" /> + <NcAppNavigationNew :text="t('news', 'Subscribe')" + button-id="new-feed-button" + button-class="icon-add" + @click="showShowAddFeed()" /> + + <div class="news-navigation"> + <NcAppNavigationNewItem :title="t('news', 'New folder')" @new-item="newFolder"> + <template #icon> + <PlusCircle :size="16" /> + </template> + </NcAppNavigationNewItem> + + <NcAppNavigationItem :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> + <NcCounterBubble>5</NcCounterBubble> + </template> + </NcAppNavigationItem> + <NcAppNavigationItem :title="t('news', 'All articles')" icon="icon-rss"> + <template #actions> + <NcActionButton icon="icon-checkmark" @click="alert('Edit')"> + t('news','Mark read') + </NcActionButton> + </template> + </NcAppNavigationItem> + <NcAppNavigationItem :title="t('news', 'Starred')" icon="icon-starred"> + <template #counter> + <NcCounterBubble>35</NcCounterBubble> + </template> + </NcAppNavigationItem> + + <NcAppNavigationItem v-for="folder in folders" + :key="folder.name" + :title="folder.name" + icon="icon-folder" + :allow-collapse="true"> + <template #default> + <NcAppNavigationItem v-for="feed in folder.feeds" + :key="feed.name" + :title="feed.title"> + <template #icon> + <img v-if="feed.faviconLink" + :src="feed.faviconLink" + alt="feedIcon"> + <div v-if="!feed.faviconLink" class="icon-rss" /> + </template> + <template #actions> + <NcActionButton icon="icon-checkmark" @click="alert('Mark read')"> + {{ t("news", "Mark read") }} + </NcActionButton> + <NcActionButton icon="icon-pinned" @click="alert('Rename')"> + {{ t("news", "Unpin from top") }} + </NcActionButton> + <NcActionButton icon="icon-caret-dark" + @click="deleteFolder(folder)"> + {{ t("news", "Newest first") }} + </NcActionButton> + <NcActionButton icon="icon-caret-dark" + @click="deleteFolder(folder)"> + {{ t("news", "Oldest first") }} + </NcActionButton> + <NcActionButton icon="icon-caret-dark" + @click="deleteFolder(folder)"> + {{ t("news", "Default order") }} + </NcActionButton> + <NcActionButton icon="icon-full-text-disabled" + @click="deleteFolder(folder)"> + {{ t("news", "Enable full text") }} + </NcActionButton> + <NcActionButton icon="icon-full-text-enabled" + @click="deleteFolder(folder)"> + {{ t("news", "Disable full text") }} + </NcActionButton> + <NcActionButton icon="icon-updatemode-default" + @click="deleteFolder(folder)"> + {{ t("news", "Unread updated") }} + </NcActionButton> + <NcActionButton icon="icon-updatemode-unread" + @click="deleteFolder(folder)"> + {{ t("news", "Ignore updated") }} + </NcActionButton> + <NcActionButton icon="icon-icon-rss" @click="deleteFolder(folder)"> + {{ t("news", "Open feed URL") }} + </NcActionButton> + <NcActionButton icon="icon-icon-rename" + @click="deleteFolder(folder)"> + {{ t("news", "Rename") }} + </NcActionButton> + <NcActionButton icon="icon-delete" @click="deleteFolder(folder)"> + {{ t("news", "Delete") }} + </NcActionButton> + </template> + </NcAppNavigationItem> + </template> + <template v-if="folder.feedCount > 0" #counter> + <NcCounterBubble>{{ folder.feedCount }}</NcCounterBubble> + </template> + <template #actions> + <NcActionButton icon="icon-checkmark" @click="alert('Mark read')"> + {{ t("news", "Mark read") }} + </NcActionButton> + <NcActionButton icon="icon-rename" @click="alert('Rename')"> + {{ t("news", "Rename") }} + </NcActionButton> + <NcActionButton icon="icon-delete" @click="deleteFolder(folder)"> + {{ t("news", "Delete") }} + </NcActionButton> + </template> + </NcAppNavigationItem> + + <NcAppNavigationItem :title="t('news', 'Explore')" + icon="icon-link" + :to="{ name: 'explore' }"> + <template #counter> + <NcCounterBubble>35</NcCounterBubble> + </template> + </NcAppNavigationItem> + </div> + </NcAppNavigation> +</template> + +<script> + +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 AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter' +import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js' +import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import AddFeed from './AddFeed.vue' +import PlusCircle from 'vue-material-design-icons/PlusCircle.vue' + +export default { + components: { + NcAppNavigation, + NcAppNavigationNew, + NcAppNavigationItem, + NcAppNavigationNewItem, + // AppNavigationCounter, + NcCounterBubble, + NcActionButton, + AddFeed, + PlusCircle, + }, + data: () => { + return { + showAddFeed: false, + } + }, + computed: { + folders() { + return this.$store.state.folders + }, + }, + created() { + // TODO? + }, + 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 + }, + alert(msg) { + window.alert(msg) + }, + }, +} +</script> + +<style lang="scss" scoped> +.news-navigation { + display: flex; + flex-direction: column; + padding-left: 10px; + padding-right: 10px; + gap: 10px; + + & ::v-deep .app-navigation-input-confirm { + form { + gap: 4px; + align-items: center; + } + + button.button-vue--icon-only { + min-height: 36px !important; + width: 36px !important; + height: 36px !important; + min-width: 36px !important; + } + } +} +</style> diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..ae9478a02 --- /dev/null +++ b/src/main.js @@ -0,0 +1,132 @@ + +import Vue from 'vue' +import App from './App.vue' +import VueRouter from 'vue-router' +import Explore from './components/Explore.vue' +import { generateUrl } from '@nextcloud/router' +import Vuex, { Store } from 'vuex' +import axios from '@nextcloud/axios' + +import { Tooltip } from '@nextcloud/vue' + +Vue.prototype.t = t +Vue.prototype.n = n +Vue.prototype.OC = OC +Vue.prototype.OCA = OCA + +Vue.use(Vuex) +Vue.use(VueRouter) + +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 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, + ) + if (folder) { + 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 }) { + axios.get(folderUrl).then((response) => { + commit('addFolders', response.data.folders) + axios + .get(feedUrl) + .then((response) => + commit('addFeeds', response.data.feeds), + ) + }) + }, + addFeed({ commit }, { feedReq }) { + let url = feedReq.url.trim() + if (!url.startsWith('http')) { + url = 'https://' + url + } + + /** + if (title !== undefined) { + title = title.trim(); + } + */ + + const feed = { + 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), +}) diff --git a/templates/index.php b/templates/index.php index 9ad38a0d5..38a3b3d27 100644 --- a/templates/index.php +++ b/templates/index.php @@ -1,4 +1,8 @@ <?php + +\OCP\Util::addScript('news', 'build/news-main'); + +/* use OCA\News\Plugin\Client\Plugin; script('news', [ @@ -97,3 +101,4 @@ foreach (Plugin::getScripts() as $appName => $fileName) { news-scroll-enabled-mark-read="Content.markReadEnabled()" news-scroll-auto-page="Content.autoPage()" news-scroll-mark-read="Content.scrollRead(itemIds)"></div> + */ diff --git a/webpack.config.js b/webpack.config.js index c329f79d1..310550144 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,6 +6,7 @@ const webpackConfig = require('@nextcloud/webpack-vue-config') webpackConfig.entry = { 'admin-settings': path.join(__dirname, 'src', 'main-admin.js'), + 'main': path.join(__dirname, 'src', 'main.js'), } webpackConfig.output.path = path.resolve('./js/build/') webpackConfig.output.publicPath = path.join('/apps/', process.env.npm_package_name, '/js/build/') |