typescript #149 #152

Merged
Xefir merged 8 commits from typescript into main 2024-09-14 15:26:18 +00:00
47 changed files with 636 additions and 327 deletions
Showing only changes of commit 38bc986bb3 - Show all commits

View File

@ -4,6 +4,7 @@ module.exports = {
'plugin:pinia/recommended',
'plugin:prettier/recommended',
],
parser: 'vue-eslint-parser',
rules: {
'jsdoc/require-jsdoc': 'off',
'vue/first-attribute-linebreak': 'off',

580
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "repod",
"license": "AGPL-3.0-or-later",
"scripts": {
"build": "vite build --mode production",
"build": "vue-tsc && vite build --mode production",
"dev": "vite build --mode development",
"dev:watch": "vite build --mode development --watch",
"watch": "npm run dev:watch",
@ -24,10 +24,11 @@
"@nextcloud/vue": "9.0.0-alpha.5",
"dompurify": "^3.1.6",
"linkify-html": "^4.1.3",
"petite-utils": "^0.0.5-3",
"pinia": "^2.2.2",
"toastify-js": "^1.12.0",
"vite": "^5.4.3",
"vite-plugin-vue-devtools": "^7.4.4",
"vite": "^5.4.4",
"vite-plugin-vue-devtools": "^7.4.5",
"vue": "^3.5.4",
"vue-material-design-icons": "^5.3.0",
"vue-router": "^4.4.4"
@ -37,8 +38,14 @@
"@nextcloud/eslint-config": "^8.4.1",
"@nextcloud/prettier-config": "^1.1.0",
"@nextcloud/stylelint-config": "^3.0.1",
"@types/toastify-js": "^1.12.3",
"@vue/tsconfig": "^0.5.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-pinia": "^0.4.1",
"eslint-plugin-prettier": "^5.2.1"
"eslint-plugin-prettier": "^5.2.1",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^2.1.6"
}
}

View File

@ -15,7 +15,7 @@ import GPodder from './views/GPodder.vue'
import { NcContent } from '@nextcloud/vue'
import Subscriptions from './components/Sidebar/Subscriptions.vue'
import { loadState } from '@nextcloud/initial-state'
import { usePlayer } from './store/player.js'
import { usePlayer } from './store/player.ts'
export default {
name: 'App',

View File

@ -4,10 +4,10 @@
</NcAppContent>
</template>
<script>
<script lang="ts">
import { NcAppContent } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'AppContent',

View File

@ -10,10 +10,10 @@
</NcAppNavigation>
</template>
<script>
<script lang="ts">
import { NcAppNavigation } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'AppNavigation',

View File

@ -16,7 +16,7 @@
</NcEmptyContent>
</template>
<script>
<script lang="ts">
import { NcEmptyContent } from '@nextcloud/vue'
export default {

View File

@ -2,7 +2,7 @@
<NcLoadingIcon class="loading" />
</template>
<script>
<script lang="ts">
import { NcLoadingIcon } from '@nextcloud/vue'
export default {

View File

@ -29,13 +29,14 @@
</div>
</template>
<script>
<script lang="ts">
import { NcAvatar, NcButton } from '@nextcloud/vue'
import DownloadIcon from 'vue-material-design-icons/Download.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import SafeHtml from './SafeHtml.vue'
import { filenameFromUrl } from '../../utils/url.js'
import { humanFileSize } from '../../utils/size.js'
import { filenameFromUrl } from '../../utils/url.ts'
import { humanFileSize } from '../../utils/size.ts'
import { t } from '@nextcloud/l10n'
export default {
name: 'Modal',
@ -55,6 +56,7 @@ export default {
methods: {
filenameFromUrl,
humanFileSize,
t,
},
}
</script>

View File

@ -2,7 +2,7 @@
<div v-sanitize="source" class="html" />
</template>
<script>
<script lang="ts">
import dompurify from 'dompurify'
import linkifyHtml from 'linkify-html'

View File

@ -10,10 +10,11 @@
</NcAppNavigationList>
</template>
<script>
<script lang="ts">
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import { toFeedUrl } from '../../utils/url.js'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
export default {
name: 'AddRss',
@ -23,6 +24,7 @@ export default {
PlusIcon,
},
methods: {
t,
toFeedUrl,
},
}

View File

@ -34,18 +34,19 @@
</div>
</template>
<script>
<script lang="ts">
import { NcActionButton, NcAvatar, NcListItem } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import Loading from '../Atoms/Loading.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import axios from '@nextcloud/axios'
import { debounce } from '../../utils/debounce.js'
import { formatLocaleDate } from '../../utils/time.js'
import { debounce } from 'petite-utils'
import { formatLocaleDate } from '../../utils/time.ts'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
import { showError } from '../../utils/toast.ts'
import { t } from '@nextcloud/l10n'
import { toFeedUrl } from '../../utils/url.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
name: 'Search',
@ -77,8 +78,9 @@ export default {
methods: {
...mapActions(useSubscriptions, ['fetch']),
formatLocaleDate,
t,
toFeedUrl,
async addSubscription(url) {
addSubscription: async (url) => {
try {
await axios.post(
generateUrl('/apps/gpoddersync/subscription_change/create'),

View File

@ -16,8 +16,8 @@
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { toFeedUrl } from '../../utils/url.js'
import { showError } from '../../utils/toast.ts'
import { toFeedUrl } from '../../utils/url.ts'
export default {
name: 'Toplist',

View File

@ -38,14 +38,14 @@
<script>
import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import { showError, showSuccess } from '../../utils/toast.js'
import { showError, showSuccess } from '../../utils/toast.ts'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import RssIcon from 'vue-material-design-icons/Rss.vue'
import SafeHtml from '../Atoms/SafeHtml.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.js'
import { decodeUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { useSubscriptions } from '../../store/subscriptions.js'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
name: 'Banner',

View File

@ -102,8 +102,8 @@ import {
durationToSeconds,
formatEpisodeTimestamp,
formatLocaleDate,
} from '../../utils/time.js'
import { hasEnded, isListening } from '../../utils/status.js'
} from '../../utils/time.ts'
import { hasEnded, isListening } from '../../utils/status.ts'
import { mapActions, mapState } from 'pinia'
import DownloadIcon from 'vue-material-design-icons/Download.vue'
import Modal from '../Atoms/Modal.vue'
@ -113,10 +113,10 @@ import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
import StopIcon from 'vue-material-design-icons/Stop.vue'
import axios from '@nextcloud/axios'
import { filenameFromUrl } from '../../utils/url.js'
import { filenameFromUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
import { showError } from '../../utils/toast.ts'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Episode',

View File

@ -12,16 +12,16 @@
</template>
<script>
import { hasEnded, isListening } from '../../utils/status.js'
import { hasEnded, isListening } from '../../utils/status.ts'
import Episode from './Episode.vue'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.js'
import { decodeUrl } from '../../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
import { useSettings } from '../../store/settings.js'
import { showError } from '../../utils/toast.ts'
import { usePlayer } from '../../store/player.ts'
import { useSettings } from '../../store/settings.ts'
export default {
name: 'Episodes',

View File

@ -29,10 +29,10 @@ import Episode from './Episode.vue'
import Loading from '../Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { hasEnded } from '../../utils/status.js'
import { hasEnded } from '../../utils/status.ts'
import { mapState } from 'pinia'
import { showError } from '../../utils/toast.js'
import { useSubscriptions } from '../../store/subscriptions.js'
import { showError } from '../../utils/toast.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
name: 'Favorite',

View File

@ -21,7 +21,7 @@ import ProgressBar from './ProgressBar.vue'
import Timer from './Timer.vue'
import Volume from './Volume.vue'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Bar',

View File

@ -9,7 +9,7 @@
import { mapActions, mapState } from 'pinia'
import PauseIcon from 'vue-material-design-icons/Pause.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Controls',

View File

@ -16,8 +16,8 @@
import Modal from '../Atoms/Modal.vue'
import { NcModal } from '@nextcloud/vue'
import { mapState } from 'pinia'
import { toFeedUrl } from '../../utils/url.js'
import { usePlayer } from '../../store/player.js'
import { toFeedUrl } from '../../utils/url.ts'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Infos',

View File

@ -10,7 +10,7 @@
<script>
import { mapActions, mapState } from 'pinia'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'ProgressBar',

View File

@ -7,9 +7,9 @@
</template>
<script>
import { formatTimer } from '../../utils/time.js'
import { formatTimer } from '../../utils/time.ts'
import { mapState } from 'pinia'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Timer',

View File

@ -36,7 +36,7 @@ import VolumeHighIcon from 'vue-material-design-icons/VolumeHigh.vue'
import VolumeLowIcon from 'vue-material-design-icons/VolumeLow.vue'
import VolumeMediumIcon from 'vue-material-design-icons/VolumeMedium.vue'
import VolumeMuteIcon from 'vue-material-design-icons/VolumeMute.vue'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Volume',

View File

@ -44,7 +44,7 @@ import { NcActionCheckbox, NcAppNavigationItem } from '@nextcloud/vue'
import { mapActions, mapState } from 'pinia'
import FilterIcon from 'vue-material-design-icons/Filter.vue'
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
import { useSettings } from '../../store/settings.js'
import { useSettings } from '../../store/settings.ts'
export default {
name: 'Filters',

View File

@ -23,7 +23,7 @@ import PlusIcon from 'vue-material-design-icons/Plus.vue'
import SpeedometerIcon from 'vue-material-design-icons/Speedometer.vue'
import SpeedometerMediumIcon from 'vue-material-design-icons/SpeedometerMedium.vue'
import SpeedometerSlowIcon from 'vue-material-design-icons/SpeedometerSlow.vue'
import { usePlayer } from '../../store/player.js'
import { usePlayer } from '../../store/player.ts'
export default {
name: 'Speed',

View File

@ -47,9 +47,9 @@ import StarPlusIcon from 'vue-material-design-icons/StarPlus.vue'
import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { toFeedUrl } from '../../utils/url.js'
import { useSubscriptions } from '../../store/subscriptions.js'
import { showError } from '../../utils/toast.ts'
import { toFeedUrl } from '../../utils/url.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
name: 'Subscription',

View File

@ -43,8 +43,8 @@ import Loading from '../Atoms/Loading.vue'
import PlusIcon from 'vue-material-design-icons/Plus.vue'
import Settings from '../Settings/Settings.vue'
import Subscription from './Subscription.vue'
import { showError } from '../../utils/toast.js'
import { useSubscriptions } from '../../store/subscriptions.js'
import { showError } from '../../utils/toast.ts'
import { useSubscriptions } from '../../store/subscriptions.ts'
export default {
name: 'Subscriptions',

View File

@ -1,13 +1,11 @@
import { n, t } from '@nextcloud/l10n'
import App from './App.vue'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router.js'
import router from './router'
const Vue = createApp(App)
const pinia = createPinia()
Vue.mixin({ methods: { t, n } })
Vue.use(pinia)
Vue.use(router)
Vue.mount('#content')

View File

@ -1,18 +1,19 @@
import type { EpisodeInterface } from '../utils/types'
import axios from '@nextcloud/axios'
import { defineStore } from 'pinia'
import { formatEpisodeTimestamp } from '../utils/time.js'
import { formatEpisodeTimestamp } from '../utils/time'
import { generateUrl } from '@nextcloud/router'
const audio = new Audio()
export const usePlayer = defineStore('player', {
state: () => ({
currentTime: null,
duration: null,
episode: null,
currentTime: null as number | null,
duration: null as number | null,
episode: null as EpisodeInterface | null,
loaded: false,
paused: null,
podcastUrl: null,
paused: true,
podcastUrl: null as string | null,
volume: 1,
rate: 1,
started: 0,
@ -29,11 +30,11 @@ export const usePlayer = defineStore('player', {
audio.ontimeupdate = () => (this.currentTime = audio.currentTime)
audio.onvolumechange = () => (this.volume = audio.volume)
},
async load(episode, podcastUrl) {
async load(episode: EpisodeInterface | null, podcastUrl?: string) {
this.episode = episode
this.podcastUrl = podcastUrl
this.podcastUrl = podcastUrl || null
if (this.episode) {
if (this.episode?.url) {
audio.src = this.episode.url
audio.load()
@ -72,7 +73,7 @@ export const usePlayer = defineStore('player', {
this.paused = false
this.started = audio.currentTime
},
seek(currentTime) {
seek(currentTime: number) {
audio.currentTime = currentTime
this.time()
},
@ -81,7 +82,13 @@ export const usePlayer = defineStore('player', {
this.episode = null
},
time() {
this.episode.action = {
if (!this.podcastUrl || !this.episode?.url) {
return
}
this.episode = {
...this.episode,
action: {
podcast: this.podcastUrl,
episode: this.episode.url,
guid: this.episode.guid,
@ -90,15 +97,17 @@ export const usePlayer = defineStore('player', {
started: Math.round(this.started),
position: Math.round(audio.currentTime),
total: Math.round(audio.duration),
},
}
axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [
this.episode.action,
])
},
setVolume(volume) {
setVolume(volume: number) {
audio.volume = volume
},
setRate(rate) {
setRate(rate: number) {
audio.playbackRate = rate
},
},

View File

@ -1,31 +0,0 @@
import { getCookie, setCookie } from '../utils/cookies.js'
import { defineStore } from 'pinia'
export const useSettings = defineStore('settings', {
state: () => {
try {
const filters = JSON.parse(getCookie('repod.filters'))
return {
filters: {
listened: filters.listened,
listening: filters.listening,
unlistened: filters.unlistened,
},
}
} catch {
return {
filters: {
listened: true,
listening: true,
unlistened: true,
},
}
}
},
actions: {
setFilters(filters) {
this.filters = { ...this.filters, ...filters }
setCookie('repod.filters', JSON.stringify(this.filters), 365)
},
},
})

36
src/store/settings.ts Normal file
View File

@ -0,0 +1,36 @@
import { getCookie, setCookie } from '../utils/cookies'
import type { FiltersInterface } from '../utils/types'
import { defineStore } from 'pinia'
export const useSettings = defineStore('settings', {
state: () => {
const cookie = getCookie('repod.filters')
if (cookie) {
try {
const filters = JSON.parse(cookie)
return {
filters: {
listened: filters.listened,
listening: filters.listening,
unlistened: filters.unlistened,
},
}
} catch {}
}
return {
filters: {
listened: true,
listening: true,
unlistened: true,
},
}
},
actions: {
setFilters(filters: FiltersInterface) {
this.filters = { ...this.filters, ...filters }
setCookie('repod.filters', JSON.stringify(this.filters), 365)
},
},
})

View File

@ -1,4 +1,4 @@
import { getCookie, setCookie } from '../utils/cookies.js'
import { getCookie, setCookie } from '../utils/cookies'
import axios from '@nextcloud/axios'
import { defineStore } from 'pinia'
import { generateUrl } from '@nextcloud/router'

View File

@ -4,7 +4,7 @@
* @param {string} name Nom du cookie à récupérer
* @return {string|null}
*/
export const getCookie = (name) => {
export const getCookie = (name: string): string | null => {
const cookies = document.cookie.split('; ')
const value = cookies.find((c) => c.startsWith(name + '='))?.split('=')[1]
if (value === undefined) {
@ -19,7 +19,7 @@ export const getCookie = (name) => {
* @param {string} value Value du cookie
* @param {number} days Durée de vie du cookie (en jours)
*/
export const setCookie = (name, value, days) => {
export const setCookie = (name: string, value: string, days: number) => {
const date = new Date()
date.setDate(date.getDate() + days)
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; SameSite=Strict;`

View File

@ -1,12 +0,0 @@
// https://stackoverflow.com/a/53486112
export const debounce = (fn, delay) => {
let timeoutID = null
return function () {
clearTimeout(timeoutID)
const args = arguments
const that = this
timeoutID = setTimeout(function () {
fn.apply(that, args)
}, delay)
}
}

View File

@ -1,8 +1,8 @@
// https://stackoverflow.com/a/20732091
export const humanFileSize = (size) => {
export const humanFileSize = (size: number) => {
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024))
return (
(size / Math.pow(1024, i)).toFixed(2) * 1 +
(size / Math.pow(1024, i)).toFixed(2) +
' ' +
['B', 'kB', 'MB', 'GB', 'TB'][i]
)

View File

@ -1,4 +1,6 @@
export const hasEnded = (episode) =>
import type { EpisodeInterface } from './types'
export const hasEnded = (episode: EpisodeInterface) =>
episode.action &&
episode.action.action &&
(episode.action.action.toLowerCase() === 'delete' ||
@ -6,7 +8,7 @@ export const hasEnded = (episode) =>
episode.action.total > 0 &&
episode.action.position >= episode.action.total))
export const isListening = (episode) =>
export const isListening = (episode: EpisodeInterface) =>
episode.action &&
episode.action.action &&
episode.action.action.toLowerCase() === 'play' &&

View File

@ -3,9 +3,9 @@
* @param {Date} date The date
* @return {string}
*/
export const formatTimer = (date) => {
const minutes = date.getUTCMinutes().toString().padStart(2, 0)
const seconds = date.getUTCSeconds().toString().padStart(2, 0)
export const formatTimer = (date: Date): string => {
const minutes = date.getUTCMinutes().toString().padStart(2, '0')
const seconds = date.getUTCSeconds().toString().padStart(2, '0')
let timer = `${minutes}:${seconds}`
if (date.getUTCHours()) {
@ -20,7 +20,7 @@ export const formatTimer = (date) => {
* @param {Date} date The date
* @return {string}
*/
export const formatEpisodeTimestamp = (date) => {
export const formatEpisodeTimestamp = (date: Date): string => {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
@ -36,7 +36,7 @@ export const formatEpisodeTimestamp = (date) => {
* @param {Date} date The date
* @return {string}
*/
export const formatLocaleDate = (date) =>
export const formatLocaleDate = (date: Date): string =>
date.toLocaleDateString(undefined, { dateStyle: 'medium' })
/**
@ -44,7 +44,7 @@ export const formatLocaleDate = (date) =>
* @param {string} duration The duration feed's entry
* @return {number}
*/
export const durationToSeconds = (duration) => {
export const durationToSeconds = (duration: string): number => {
const splitDuration = duration.split(':').reverse()
let seconds = parseInt(splitDuration[0])
seconds += splitDuration.length > 1 ? parseInt(splitDuration[1]) * 60 : 0

View File

@ -1,12 +0,0 @@
import toastify from 'toastify-js'
export const showMessage = (text, backgroundColor) =>
toastify({
text,
backgroundColor,
}).showToast()
export const showError = (text) => showMessage(text, 'var(--color-error)')
export const showWarning = (text) => showMessage(text, 'var(--color-warning)')
export const showInfo = (text) => showMessage(text, 'var(--color-primary)')
export const showSuccess = (text) => showMessage(text, 'var(--color-success)')

14
src/utils/toast.ts Normal file
View File

@ -0,0 +1,14 @@
import toastify from 'toastify-js'
export const showMessage = (text: string, backgroundColor: string) =>
toastify({
text,
backgroundColor,
}).showToast()
export const showError = (text: string) => showMessage(text, 'var(--color-error)')
export const showWarning = (text: string) =>
showMessage(text, 'var(--color-warning)')
export const showInfo = (text: string) => showMessage(text, 'var(--color-primary)')
export const showSuccess = (text: string) =>
showMessage(text, 'var(--color-success)')

33
src/utils/types.ts Normal file
View File

@ -0,0 +1,33 @@
export interface EpisodeActionInterface {
podcast: string
episode: string
action: string
timestamp: string
started: number
position: number
total: number
guid?: string
id?: number
}
export interface EpisodeInterface {
title: string
url?: string
name: string
link?: string
image?: string
description?: string
fetchedAtUnix: number
guid: string
type?: string
size?: number
pubDate?: Date
duration?: string
action?: EpisodeActionInterface
}
export interface FiltersInterface {
listened: boolean
listening: boolean
unlistened: boolean
}

View File

@ -1,4 +0,0 @@
export const encodeUrl = (url) => encodeURIComponent(btoa(url))
export const decodeUrl = (url) => atob(decodeURIComponent(url))
export const toFeedUrl = (url) => `/feed/${encodeUrl(url)}`
export const filenameFromUrl = (url) => new URL(url).pathname.split('/').pop()

5
src/utils/url.ts Normal file
View File

@ -0,0 +1,5 @@
export const encodeUrl = (url: string) => encodeURIComponent(btoa(url))
export const decodeUrl = (url: string) => atob(decodeURIComponent(url))
export const toFeedUrl = (url: string) => `/feed/${encodeUrl(url)}`
export const filenameFromUrl = (url: string) =>
new URL(url).pathname.split('/').pop()

View File

@ -25,7 +25,7 @@ import EmptyContent from '../components/Atoms/EmptyContent.vue'
import Episodes from '../components/Feed/Episodes.vue'
import Loading from '../components/Atoms/Loading.vue'
import axios from '@nextcloud/axios'
import { decodeUrl } from '../utils/url.js'
import { decodeUrl } from '../utils/url.ts'
import { generateUrl } from '@nextcloud/router'
export default {

View File

@ -24,7 +24,7 @@ import EmptyContent from '../components/Atoms/EmptyContent.vue'
import Favorite from '../components/Feed/Favorite.vue'
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
import { mapState } from 'pinia'
import { useSubscriptions } from '../store/subscriptions.js'
import { useSubscriptions } from '../store/subscriptions.ts'
export default {
name: 'Home',

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@vue/tsconfig/tsconfig.json",
"include": ["./src/**/*.ts", "./src/**/*.vue", "**/*.ts"],
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"noImplicitAny": false,
"rootDir": ".",
"strict": true,
"noEmit": true,
"allowImportingTsExtensions": true,
},
"vueCompilerOptions": {
"target": 3.3,
},
}

View File

@ -19,7 +19,7 @@ const config = defineConfig(({ mode }) => ({
export default createAppConfig(
{
main: 'src/main.js',
main: 'src/main.ts',
},
{ config, inlineCSS: true },
)