typescript #149 #152
@ -4,6 +4,7 @@ module.exports = {
|
|||||||
'plugin:pinia/recommended',
|
'plugin:pinia/recommended',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
],
|
],
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
rules: {
|
rules: {
|
||||||
'jsdoc/require-jsdoc': 'off',
|
'jsdoc/require-jsdoc': 'off',
|
||||||
'vue/first-attribute-linebreak': 'off',
|
'vue/first-attribute-linebreak': 'off',
|
||||||
|
580
package-lock.json
generated
580
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "repod",
|
"name": "repod",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build --mode production",
|
"build": "vue-tsc && vite build --mode production",
|
||||||
"dev": "vite build --mode development",
|
"dev": "vite build --mode development",
|
||||||
"dev:watch": "vite build --mode development --watch",
|
"dev:watch": "vite build --mode development --watch",
|
||||||
"watch": "npm run dev:watch",
|
"watch": "npm run dev:watch",
|
||||||
@ -24,10 +24,11 @@
|
|||||||
"@nextcloud/vue": "9.0.0-alpha.5",
|
"@nextcloud/vue": "9.0.0-alpha.5",
|
||||||
"dompurify": "^3.1.6",
|
"dompurify": "^3.1.6",
|
||||||
"linkify-html": "^4.1.3",
|
"linkify-html": "^4.1.3",
|
||||||
|
"petite-utils": "^0.0.5-3",
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
"toastify-js": "^1.12.0",
|
"toastify-js": "^1.12.0",
|
||||||
"vite": "^5.4.3",
|
"vite": "^5.4.4",
|
||||||
"vite-plugin-vue-devtools": "^7.4.4",
|
"vite-plugin-vue-devtools": "^7.4.5",
|
||||||
"vue": "^3.5.4",
|
"vue": "^3.5.4",
|
||||||
"vue-material-design-icons": "^5.3.0",
|
"vue-material-design-icons": "^5.3.0",
|
||||||
"vue-router": "^4.4.4"
|
"vue-router": "^4.4.4"
|
||||||
@ -37,8 +38,14 @@
|
|||||||
"@nextcloud/eslint-config": "^8.4.1",
|
"@nextcloud/eslint-config": "^8.4.1",
|
||||||
"@nextcloud/prettier-config": "^1.1.0",
|
"@nextcloud/prettier-config": "^1.1.0",
|
||||||
"@nextcloud/stylelint-config": "^3.0.1",
|
"@nextcloud/stylelint-config": "^3.0.1",
|
||||||
|
"@types/toastify-js": "^1.12.3",
|
||||||
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-pinia": "^0.4.1",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import GPodder from './views/GPodder.vue'
|
|||||||
import { NcContent } from '@nextcloud/vue'
|
import { NcContent } from '@nextcloud/vue'
|
||||||
import Subscriptions from './components/Sidebar/Subscriptions.vue'
|
import Subscriptions from './components/Sidebar/Subscriptions.vue'
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
import { loadState } from '@nextcloud/initial-state'
|
||||||
import { usePlayer } from './store/player.js'
|
import { usePlayer } from './store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
</NcAppContent>
|
</NcAppContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcAppContent } from '@nextcloud/vue'
|
import { NcAppContent } from '@nextcloud/vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppContent',
|
name: 'AppContent',
|
||||||
|
@ -10,10 +10,10 @@
|
|||||||
</NcAppNavigation>
|
</NcAppNavigation>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcAppNavigation } from '@nextcloud/vue'
|
import { NcAppNavigation } from '@nextcloud/vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppNavigation',
|
name: 'AppNavigation',
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</NcEmptyContent>
|
</NcEmptyContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcEmptyContent } from '@nextcloud/vue'
|
import { NcEmptyContent } from '@nextcloud/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<NcLoadingIcon class="loading" />
|
<NcLoadingIcon class="loading" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcLoadingIcon } from '@nextcloud/vue'
|
import { NcLoadingIcon } from '@nextcloud/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -29,13 +29,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcAvatar, NcButton } from '@nextcloud/vue'
|
import { NcAvatar, NcButton } from '@nextcloud/vue'
|
||||||
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
||||||
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
||||||
import SafeHtml from './SafeHtml.vue'
|
import SafeHtml from './SafeHtml.vue'
|
||||||
import { filenameFromUrl } from '../../utils/url.js'
|
import { filenameFromUrl } from '../../utils/url.ts'
|
||||||
import { humanFileSize } from '../../utils/size.js'
|
import { humanFileSize } from '../../utils/size.ts'
|
||||||
|
import { t } from '@nextcloud/l10n'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Modal',
|
name: 'Modal',
|
||||||
@ -55,6 +56,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
filenameFromUrl,
|
filenameFromUrl,
|
||||||
humanFileSize,
|
humanFileSize,
|
||||||
|
t,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div v-sanitize="source" class="html" />
|
<div v-sanitize="source" class="html" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import dompurify from 'dompurify'
|
import dompurify from 'dompurify'
|
||||||
import linkifyHtml from 'linkify-html'
|
import linkifyHtml from 'linkify-html'
|
||||||
|
|
||||||
|
@ -10,10 +10,11 @@
|
|||||||
</NcAppNavigationList>
|
</NcAppNavigationList>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
|
import { NcAppNavigationList, NcAppNavigationNewItem } from '@nextcloud/vue'
|
||||||
import PlusIcon from 'vue-material-design-icons/Plus.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 {
|
export default {
|
||||||
name: 'AddRss',
|
name: 'AddRss',
|
||||||
@ -23,6 +24,7 @@ export default {
|
|||||||
PlusIcon,
|
PlusIcon,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
t,
|
||||||
toFeedUrl,
|
toFeedUrl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -34,18 +34,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { NcActionButton, NcAvatar, NcListItem } from '@nextcloud/vue'
|
import { NcActionButton, NcAvatar, NcListItem } from '@nextcloud/vue'
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { debounce } from '../../utils/debounce.js'
|
import { debounce } from 'petite-utils'
|
||||||
import { formatLocaleDate } from '../../utils/time.js'
|
import { formatLocaleDate } from '../../utils/time.ts'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { toFeedUrl } from '../../utils/url.js'
|
import { t } from '@nextcloud/l10n'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { toFeedUrl } from '../../utils/url.ts'
|
||||||
|
import { useSubscriptions } from '../../store/subscriptions.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Search',
|
name: 'Search',
|
||||||
@ -77,8 +78,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
...mapActions(useSubscriptions, ['fetch']),
|
...mapActions(useSubscriptions, ['fetch']),
|
||||||
formatLocaleDate,
|
formatLocaleDate,
|
||||||
|
t,
|
||||||
toFeedUrl,
|
toFeedUrl,
|
||||||
async addSubscription(url) {
|
addSubscription: async (url) => {
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
generateUrl('/apps/gpoddersync/subscription_change/create'),
|
generateUrl('/apps/gpoddersync/subscription_change/create'),
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { toFeedUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Toplist',
|
name: 'Toplist',
|
||||||
|
@ -38,14 +38,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
|
import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
|
||||||
import { mapActions, mapState } from 'pinia'
|
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 PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import RssIcon from 'vue-material-design-icons/Rss.vue'
|
import RssIcon from 'vue-material-design-icons/Rss.vue'
|
||||||
import SafeHtml from '../Atoms/SafeHtml.vue'
|
import SafeHtml from '../Atoms/SafeHtml.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { decodeUrl } from '../../utils/url.js'
|
import { decodeUrl } from '../../utils/url.ts'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Banner',
|
name: 'Banner',
|
||||||
|
@ -102,8 +102,8 @@ import {
|
|||||||
durationToSeconds,
|
durationToSeconds,
|
||||||
formatEpisodeTimestamp,
|
formatEpisodeTimestamp,
|
||||||
formatLocaleDate,
|
formatLocaleDate,
|
||||||
} from '../../utils/time.js'
|
} from '../../utils/time.ts'
|
||||||
import { hasEnded, isListening } from '../../utils/status.js'
|
import { hasEnded, isListening } from '../../utils/status.ts'
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
||||||
import Modal from '../Atoms/Modal.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 PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
|
||||||
import StopIcon from 'vue-material-design-icons/Stop.vue'
|
import StopIcon from 'vue-material-design-icons/Stop.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { filenameFromUrl } from '../../utils/url.js'
|
import { filenameFromUrl } from '../../utils/url.ts'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Episode',
|
name: 'Episode',
|
||||||
|
@ -12,16 +12,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { hasEnded, isListening } from '../../utils/status.js'
|
import { hasEnded, isListening } from '../../utils/status.ts'
|
||||||
import Episode from './Episode.vue'
|
import Episode from './Episode.vue'
|
||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { decodeUrl } from '../../utils/url.js'
|
import { decodeUrl } from '../../utils/url.ts'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
import { useSettings } from '../../store/settings.js'
|
import { useSettings } from '../../store/settings.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Episodes',
|
name: 'Episodes',
|
||||||
|
@ -29,10 +29,10 @@ import Episode from './Episode.vue'
|
|||||||
import Loading from '../Atoms/Loading.vue'
|
import Loading from '../Atoms/Loading.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { hasEnded } from '../../utils/status.js'
|
import { hasEnded } from '../../utils/status.ts'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Favorite',
|
name: 'Favorite',
|
||||||
|
@ -21,7 +21,7 @@ import ProgressBar from './ProgressBar.vue'
|
|||||||
import Timer from './Timer.vue'
|
import Timer from './Timer.vue'
|
||||||
import Volume from './Volume.vue'
|
import Volume from './Volume.vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Bar',
|
name: 'Bar',
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import PauseIcon from 'vue-material-design-icons/Pause.vue'
|
import PauseIcon from 'vue-material-design-icons/Pause.vue'
|
||||||
import PlayIcon from 'vue-material-design-icons/Play.vue'
|
import PlayIcon from 'vue-material-design-icons/Play.vue'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Controls',
|
name: 'Controls',
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
import Modal from '../Atoms/Modal.vue'
|
import Modal from '../Atoms/Modal.vue'
|
||||||
import { NcModal } from '@nextcloud/vue'
|
import { NcModal } from '@nextcloud/vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { toFeedUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.ts'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Infos',
|
name: 'Infos',
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProgressBar',
|
name: 'ProgressBar',
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { formatTimer } from '../../utils/time.js'
|
import { formatTimer } from '../../utils/time.ts'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Timer',
|
name: 'Timer',
|
||||||
|
@ -36,7 +36,7 @@ import VolumeHighIcon from 'vue-material-design-icons/VolumeHigh.vue'
|
|||||||
import VolumeLowIcon from 'vue-material-design-icons/VolumeLow.vue'
|
import VolumeLowIcon from 'vue-material-design-icons/VolumeLow.vue'
|
||||||
import VolumeMediumIcon from 'vue-material-design-icons/VolumeMedium.vue'
|
import VolumeMediumIcon from 'vue-material-design-icons/VolumeMedium.vue'
|
||||||
import VolumeMuteIcon from 'vue-material-design-icons/VolumeMute.vue'
|
import VolumeMuteIcon from 'vue-material-design-icons/VolumeMute.vue'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Volume',
|
name: 'Volume',
|
||||||
|
@ -44,7 +44,7 @@ import { NcActionCheckbox, NcAppNavigationItem } from '@nextcloud/vue'
|
|||||||
import { mapActions, mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
import FilterIcon from 'vue-material-design-icons/Filter.vue'
|
||||||
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
|
import FilterSettingsIcon from 'vue-material-design-icons/FilterSettings.vue'
|
||||||
import { useSettings } from '../../store/settings.js'
|
import { useSettings } from '../../store/settings.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Filters',
|
name: 'Filters',
|
||||||
|
@ -23,7 +23,7 @@ import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
|||||||
import SpeedometerIcon from 'vue-material-design-icons/Speedometer.vue'
|
import SpeedometerIcon from 'vue-material-design-icons/Speedometer.vue'
|
||||||
import SpeedometerMediumIcon from 'vue-material-design-icons/SpeedometerMedium.vue'
|
import SpeedometerMediumIcon from 'vue-material-design-icons/SpeedometerMedium.vue'
|
||||||
import SpeedometerSlowIcon from 'vue-material-design-icons/SpeedometerSlow.vue'
|
import SpeedometerSlowIcon from 'vue-material-design-icons/SpeedometerSlow.vue'
|
||||||
import { usePlayer } from '../../store/player.js'
|
import { usePlayer } from '../../store/player.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Speed',
|
name: 'Speed',
|
||||||
|
@ -47,9 +47,9 @@ import StarPlusIcon from 'vue-material-design-icons/StarPlus.vue'
|
|||||||
import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue'
|
import StarRemoveIcon from 'vue-material-design-icons/StarRemove.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { toFeedUrl } from '../../utils/url.js'
|
import { toFeedUrl } from '../../utils/url.ts'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Subscription',
|
name: 'Subscription',
|
||||||
|
@ -43,8 +43,8 @@ import Loading from '../Atoms/Loading.vue'
|
|||||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import Settings from '../Settings/Settings.vue'
|
import Settings from '../Settings/Settings.vue'
|
||||||
import Subscription from './Subscription.vue'
|
import Subscription from './Subscription.vue'
|
||||||
import { showError } from '../../utils/toast.js'
|
import { showError } from '../../utils/toast.ts'
|
||||||
import { useSubscriptions } from '../../store/subscriptions.js'
|
import { useSubscriptions } from '../../store/subscriptions.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Subscriptions',
|
name: 'Subscriptions',
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { n, t } from '@nextcloud/l10n'
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import router from './router.js'
|
import router from './router'
|
||||||
|
|
||||||
const Vue = createApp(App)
|
const Vue = createApp(App)
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
Vue.mixin({ methods: { t, n } })
|
|
||||||
Vue.use(pinia)
|
Vue.use(pinia)
|
||||||
Vue.use(router)
|
Vue.use(router)
|
||||||
Vue.mount('#content')
|
Vue.mount('#content')
|
@ -1,18 +1,19 @@
|
|||||||
|
import type { EpisodeInterface } from '../utils/types'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { formatEpisodeTimestamp } from '../utils/time.js'
|
import { formatEpisodeTimestamp } from '../utils/time'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
const audio = new Audio()
|
const audio = new Audio()
|
||||||
|
|
||||||
export const usePlayer = defineStore('player', {
|
export const usePlayer = defineStore('player', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
currentTime: null,
|
currentTime: null as number | null,
|
||||||
duration: null,
|
duration: null as number | null,
|
||||||
episode: null,
|
episode: null as EpisodeInterface | null,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
paused: null,
|
paused: true,
|
||||||
podcastUrl: null,
|
podcastUrl: null as string | null,
|
||||||
volume: 1,
|
volume: 1,
|
||||||
rate: 1,
|
rate: 1,
|
||||||
started: 0,
|
started: 0,
|
||||||
@ -29,11 +30,11 @@ export const usePlayer = defineStore('player', {
|
|||||||
audio.ontimeupdate = () => (this.currentTime = audio.currentTime)
|
audio.ontimeupdate = () => (this.currentTime = audio.currentTime)
|
||||||
audio.onvolumechange = () => (this.volume = audio.volume)
|
audio.onvolumechange = () => (this.volume = audio.volume)
|
||||||
},
|
},
|
||||||
async load(episode, podcastUrl) {
|
async load(episode: EpisodeInterface | null, podcastUrl?: string) {
|
||||||
this.episode = episode
|
this.episode = episode
|
||||||
this.podcastUrl = podcastUrl
|
this.podcastUrl = podcastUrl || null
|
||||||
|
|
||||||
if (this.episode) {
|
if (this.episode?.url) {
|
||||||
audio.src = this.episode.url
|
audio.src = this.episode.url
|
||||||
audio.load()
|
audio.load()
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ export const usePlayer = defineStore('player', {
|
|||||||
this.paused = false
|
this.paused = false
|
||||||
this.started = audio.currentTime
|
this.started = audio.currentTime
|
||||||
},
|
},
|
||||||
seek(currentTime) {
|
seek(currentTime: number) {
|
||||||
audio.currentTime = currentTime
|
audio.currentTime = currentTime
|
||||||
this.time()
|
this.time()
|
||||||
},
|
},
|
||||||
@ -81,7 +82,13 @@ export const usePlayer = defineStore('player', {
|
|||||||
this.episode = null
|
this.episode = null
|
||||||
},
|
},
|
||||||
time() {
|
time() {
|
||||||
this.episode.action = {
|
if (!this.podcastUrl || !this.episode?.url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.episode = {
|
||||||
|
...this.episode,
|
||||||
|
action: {
|
||||||
podcast: this.podcastUrl,
|
podcast: this.podcastUrl,
|
||||||
episode: this.episode.url,
|
episode: this.episode.url,
|
||||||
guid: this.episode.guid,
|
guid: this.episode.guid,
|
||||||
@ -90,15 +97,17 @@ export const usePlayer = defineStore('player', {
|
|||||||
started: Math.round(this.started),
|
started: Math.round(this.started),
|
||||||
position: Math.round(audio.currentTime),
|
position: Math.round(audio.currentTime),
|
||||||
total: Math.round(audio.duration),
|
total: Math.round(audio.duration),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [
|
axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [
|
||||||
this.episode.action,
|
this.episode.action,
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
setVolume(volume) {
|
setVolume(volume: number) {
|
||||||
audio.volume = volume
|
audio.volume = volume
|
||||||
},
|
},
|
||||||
setRate(rate) {
|
setRate(rate: number) {
|
||||||
audio.playbackRate = rate
|
audio.playbackRate = rate
|
||||||
},
|
},
|
||||||
},
|
},
|
@ -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
36
src/store/settings.ts
Normal 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)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
import { getCookie, setCookie } from '../utils/cookies.js'
|
import { getCookie, setCookie } from '../utils/cookies'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
@ -4,7 +4,7 @@
|
|||||||
* @param {string} name Nom du cookie à récupérer
|
* @param {string} name Nom du cookie à récupérer
|
||||||
* @return {string|null}
|
* @return {string|null}
|
||||||
*/
|
*/
|
||||||
export const getCookie = (name) => {
|
export const getCookie = (name: string): string | null => {
|
||||||
const cookies = document.cookie.split('; ')
|
const cookies = document.cookie.split('; ')
|
||||||
const value = cookies.find((c) => c.startsWith(name + '='))?.split('=')[1]
|
const value = cookies.find((c) => c.startsWith(name + '='))?.split('=')[1]
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@ -19,7 +19,7 @@ export const getCookie = (name) => {
|
|||||||
* @param {string} value Value du cookie
|
* @param {string} value Value du cookie
|
||||||
* @param {number} days Durée de vie du cookie (en jours)
|
* @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()
|
const date = new Date()
|
||||||
date.setDate(date.getDate() + days)
|
date.setDate(date.getDate() + days)
|
||||||
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; SameSite=Strict;`
|
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; SameSite=Strict;`
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
// https://stackoverflow.com/a/20732091
|
// 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))
|
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024))
|
||||||
return (
|
return (
|
||||||
(size / Math.pow(1024, i)).toFixed(2) * 1 +
|
(size / Math.pow(1024, i)).toFixed(2) +
|
||||||
' ' +
|
' ' +
|
||||||
['B', 'kB', 'MB', 'GB', 'TB'][i]
|
['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||||
)
|
)
|
@ -1,4 +1,6 @@
|
|||||||
export const hasEnded = (episode) =>
|
import type { EpisodeInterface } from './types'
|
||||||
|
|
||||||
|
export const hasEnded = (episode: EpisodeInterface) =>
|
||||||
episode.action &&
|
episode.action &&
|
||||||
episode.action.action &&
|
episode.action.action &&
|
||||||
(episode.action.action.toLowerCase() === 'delete' ||
|
(episode.action.action.toLowerCase() === 'delete' ||
|
||||||
@ -6,7 +8,7 @@ export const hasEnded = (episode) =>
|
|||||||
episode.action.total > 0 &&
|
episode.action.total > 0 &&
|
||||||
episode.action.position >= episode.action.total))
|
episode.action.position >= episode.action.total))
|
||||||
|
|
||||||
export const isListening = (episode) =>
|
export const isListening = (episode: EpisodeInterface) =>
|
||||||
episode.action &&
|
episode.action &&
|
||||||
episode.action.action &&
|
episode.action.action &&
|
||||||
episode.action.action.toLowerCase() === 'play' &&
|
episode.action.action.toLowerCase() === 'play' &&
|
@ -3,9 +3,9 @@
|
|||||||
* @param {Date} date The date
|
* @param {Date} date The date
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export const formatTimer = (date) => {
|
export const formatTimer = (date: Date): string => {
|
||||||
const minutes = date.getUTCMinutes().toString().padStart(2, 0)
|
const minutes = date.getUTCMinutes().toString().padStart(2, '0')
|
||||||
const seconds = date.getUTCSeconds().toString().padStart(2, 0)
|
const seconds = date.getUTCSeconds().toString().padStart(2, '0')
|
||||||
let timer = `${minutes}:${seconds}`
|
let timer = `${minutes}:${seconds}`
|
||||||
|
|
||||||
if (date.getUTCHours()) {
|
if (date.getUTCHours()) {
|
||||||
@ -20,7 +20,7 @@ export const formatTimer = (date) => {
|
|||||||
* @param {Date} date The date
|
* @param {Date} date The date
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export const formatEpisodeTimestamp = (date) => {
|
export const formatEpisodeTimestamp = (date: Date): string => {
|
||||||
const year = date.getFullYear()
|
const year = date.getFullYear()
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||||
const day = date.getDate().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
|
* @param {Date} date The date
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export const formatLocaleDate = (date) =>
|
export const formatLocaleDate = (date: Date): string =>
|
||||||
date.toLocaleDateString(undefined, { dateStyle: 'medium' })
|
date.toLocaleDateString(undefined, { dateStyle: 'medium' })
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +44,7 @@ export const formatLocaleDate = (date) =>
|
|||||||
* @param {string} duration The duration feed's entry
|
* @param {string} duration The duration feed's entry
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
export const durationToSeconds = (duration) => {
|
export const durationToSeconds = (duration: string): number => {
|
||||||
const splitDuration = duration.split(':').reverse()
|
const splitDuration = duration.split(':').reverse()
|
||||||
let seconds = parseInt(splitDuration[0])
|
let seconds = parseInt(splitDuration[0])
|
||||||
seconds += splitDuration.length > 1 ? parseInt(splitDuration[1]) * 60 : 0
|
seconds += splitDuration.length > 1 ? parseInt(splitDuration[1]) * 60 : 0
|
@ -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
14
src/utils/toast.ts
Normal 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
33
src/utils/types.ts
Normal 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
|
||||||
|
}
|
@ -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
5
src/utils/url.ts
Normal 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()
|
@ -25,7 +25,7 @@ import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
|||||||
import Episodes from '../components/Feed/Episodes.vue'
|
import Episodes from '../components/Feed/Episodes.vue'
|
||||||
import Loading from '../components/Atoms/Loading.vue'
|
import Loading from '../components/Atoms/Loading.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { decodeUrl } from '../utils/url.js'
|
import { decodeUrl } from '../utils/url.ts'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -24,7 +24,7 @@ import EmptyContent from '../components/Atoms/EmptyContent.vue'
|
|||||||
import Favorite from '../components/Feed/Favorite.vue'
|
import Favorite from '../components/Feed/Favorite.vue'
|
||||||
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
|
import StarOffIcon from 'vue-material-design-icons/StarOff.vue'
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { useSubscriptions } from '../store/subscriptions.js'
|
import { useSubscriptions } from '../store/subscriptions.ts'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal 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,
|
||||||
|
},
|
||||||
|
}
|
@ -19,7 +19,7 @@ const config = defineConfig(({ mode }) => ({
|
|||||||
|
|
||||||
export default createAppConfig(
|
export default createAppConfig(
|
||||||
{
|
{
|
||||||
main: 'src/main.js',
|
main: 'src/main.ts',
|
||||||
},
|
},
|
||||||
{ config, inlineCSS: true },
|
{ config, inlineCSS: true },
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user