Import existing attempt to port to vue

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
This commit is contained in:
Carl Schwan 2022-08-30 19:27:37 +02:00
parent 753e88793e
commit 47cc783b63
No known key found for this signature in database
GPG Key ID: C3AA6B3A5EFA7AC5
7 changed files with 623 additions and 0 deletions

25
src/App.vue Normal file
View File

@ -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>

171
src/components/AddFeed.vue Normal file
View File

@ -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>

View File

@ -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>

211
src/components/Sidebar.vue Normal file
View File

@ -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>

132
src/main.js Normal file
View File

@ -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),
})

View File

@ -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>
*/

View File

@ -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/')