Update to vuejs 3

This commit is contained in:
Chocobozzz 2021-06-25 11:44:32 +02:00
parent ed455a4d36
commit afc6710112
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
20 changed files with 2304 additions and 3625 deletions

View File

View File

@ -10,24 +10,23 @@
},
"dependencies": {},
"devDependencies": {
"@johmun/vue-tags-input": "^2.1.0",
"@jshmrtn/vue3-gettext": "^1.0.4",
"@sipec/vue3-tags-input": "^3.0.4",
"@types/axios": "^0.14.0",
"@vue/cli-plugin-typescript": "^4.5.4",
"@vue/cli-service": "^4.0.5",
"@types/markdown-it": "^10.0.1",
"@vue/cli-plugin-typescript": "~5.0.0-beta.2",
"@vue/cli-service": "~5.0.0-beta.2",
"@vue/compiler-sfc": "^3.1.0",
"axios": "^0.20.0",
"markdown-it": "^11.0.0",
"node-sass": "^4.13.0",
"nprogress": "^0.2.0",
"register-service-worker": "^1.0.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-vue": "^6.0.0-beta.10",
"sass-loader": "^10.0.1",
"vue": "^2.6.3",
"vue-gettext": "^2.1.10",
"vue-matomo": "^3.13.5-0",
"vue-router": "^3.1.3",
"vue-template-compiler": "^2.6.3"
"sass": "^1.35.1",
"sass-loader": "^12.1.0",
"typescript": "~4.3.4",
"vue": "^3.1.0",
"vue-matomo": "^4.0.1",
"vue-router": "^4.0.0"
},
"browserslist": [
"> 1%",

View File

@ -7,9 +7,11 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import Footer from './components/Footer.vue'
export default {
export default defineComponent({
components: {
'my-footer': Footer
},
@ -19,7 +21,7 @@
title: process.env.VUE_APP_TITLE
}
}
}
})
</script>
<style lang="scss">

View File

@ -53,17 +53,17 @@
</style>
<script lang="ts">
import Vue, { PropType } from 'vue'
import { defineComponent, PropType } from 'vue'
import { AccountSummary, VideoChannelSummary } from '../../../PeerTube/shared/models'
export default Vue.extend({
export default defineComponent({
props: {
actor: Object as PropType<AccountSummary | VideoChannelSummary>,
type: String
},
computed: {
linkTitle () {
linkTitle (): string {
if (this.type === 'channel') return this.$gettext('Go on this channel page')
return this.$gettext('Go on this account page')

View File

@ -59,22 +59,23 @@
</style>
<script lang="ts">
import Vue, { PropType } from 'vue'
import { Video, VideoChannel } from '../../../PeerTube/shared/models'
import { useGettext } from '@jshmrtn/vue3-gettext'
import { defineComponent, PropType } from 'vue'
import { VideoChannel } from '../../../PeerTube/shared/models'
export default Vue.extend({
export default defineComponent({
props: {
channel: Object as PropType<VideoChannel>
},
computed: {
host () {
host (): string {
const url = this.channel.url
return new URL(url as string).host
},
discoverChannelMessage () {
discoverChannelMessage (): string {
return this.$gettextInterpolate(this.$gettext('Discover this channel on %{host}'), { host: this.host })
}
}

View File

@ -154,10 +154,10 @@
</style>
<script>
import Vue from 'vue'
import { defineComponent } from 'vue'
import { getConfig } from '../shared/config'
export default Vue.extend({
export default defineComponent({
data () {
return {
legalNoticesUrl: ''

View File

@ -8,7 +8,7 @@
<img class="title-image" v-if="configLoaded && titleImageUrl" v-bind:src="titleImageUrl" v-bind:alt="indexName" />
</h1>
<template v-if="!smallFormat">
<h4>
<div v-translate>A search engine of <a href="https://joinpeertube.org" target="_blank">PeerTube</a> videos and channels</div>
@ -102,12 +102,12 @@
</style>
<script lang="ts">
import Vue from 'vue'
import { defineComponent } from 'vue'
import { getConfig } from '../shared/config'
import { buildApiUrl } from '../shared/utils'
import InterfaceLanguageDropdown from './InterfaceLanguageDropdown.vue'
export default Vue.extend({
export default defineComponent({
components: {
'interface-language-dropdown': InterfaceLanguageDropdown
},

View File

@ -3,11 +3,11 @@
<img :title="imgTitle" v-on:click="toggleShow()" class="interface-language" src="/img/interface-languages.svg" alt="Change interface language">
<div v-if="showMenu" class="menu">
<a v-for="(lang, locale) in $language.available" :key="locale" :href="buildLanguageRoute(locale)" class="menu-item">
<a v-for="(lang, locale) in availableLanguages" :key="locale" :href="buildLanguageRoute(locale)" class="menu-item">
{{lang}}
</a>
<hr></hr>
<hr />
<a class="menu-item add-your-language" target="_blank" href="https://weblate.framasoft.org/projects/peertube-search-index/client/">
Translate
@ -69,9 +69,10 @@
</style>
<script lang="ts">
import Vue, { PropType } from 'vue'
import { useGettext } from '@jshmrtn/vue3-gettext'
import { defineComponent } from 'vue'
export default Vue.extend({
export default defineComponent({
data () {
return {
showMenu: false
@ -81,6 +82,12 @@
computed: {
imgTitle () {
return this.$gettext('Change interface language')
},
availableLanguages (): { [id: string]: string } {
const { available } = useGettext()
return available
}
},
@ -88,10 +95,11 @@
toggleShow () {
this.showMenu = !this.showMenu;
},
buildLanguageRoute(locale: string) {
const paths = this.$route.fullPath.split('/')
if (paths.length > 0 && this.$language.available.hasOwnProperty(paths[0])) {
if (paths.length > 0 && this.availableLanguages.hasOwnProperty(paths[0])) {
return "/" + locale + "/" + paths.slice(1).join("/")
} else {
return "/" + locale + this.$route.fullPath

View File

@ -1,23 +1,23 @@
<template>
<div class="pagination" v-if="searchDone">
<router-link class="previous" v-bind:class="{ 'none-opacity': value === 1 }" :to="{ query: buildPageUrlQuery(value - 1) }">
<router-link class="previous" v-bind:class="{ 'none-opacity': modelValue === 1 }" :to="{ query: buildPageUrlQuery(modelValue - 1) }">
<translate>Previous page</translate>
</router-link>
<div class="pages">
<template v-for="page in pages">
<router-link v-if="page !== value" class="go-to-page" :to="{ query: buildPageUrlQuery(page) }" :key="page">
<router-link v-if="page !== modelValue" class="go-to-page" :to="{ query: buildPageUrlQuery(page) }" :key="page">
{{ page }}
</router-link>
<div v-else class="current" :key="page">{{ page }}</div>
<div v-else class="current">{{ page }}</div>
</template>
</div>
<router-link class="next" v-bind:class="{ 'none-opacity': value >= maxPage }" :to="{ query: buildPageUrlQuery(+value + 1) }">
<router-link class="next" v-bind:class="{ 'none-opacity': modelValue >= maxPage }" :to="{ query: buildPageUrlQuery(+modelValue + 1) }">
<translate>Next page</translate>
</router-link>
</div>
@ -65,13 +65,13 @@
</style>
<script lang="ts">
import Vue from 'vue'
import { defineComponent } from 'vue'
export default Vue.extend({
export default defineComponent({
props: {
maxPage: Number,
searchDone: Boolean,
value: Number,
modelValue: Number,
pages: Array as () => number[]
},

View File

@ -102,12 +102,13 @@
</style>
<script lang="ts">
import Vue, { PropType } from 'vue'
import { defineComponent, PropType } from 'vue'
import ActorMiniature from './ActorMiniature.vue'
import { VideoPlaylist } from '../../../PeerTube/shared/models'
import { renderMarkdown } from '../shared/markdown-render'
import { useGettext } from '@jshmrtn/vue3-gettext'
export default Vue.extend({
export default defineComponent({
components: {
'actor-miniature': ActorMiniature
},
@ -117,21 +118,21 @@
},
computed: {
host () {
host (): string {
const url = this.playlist.url
return new URL(url as string).host
},
updateDate () {
updateDate (): string {
return new Date(this.playlist.updatedAt).toLocaleDateString()
},
watchMessage () {
watchMessage (): string {
return this.$gettextInterpolate(this.$gettext('Watch the playlist on %{host}'), { host: this.host })
},
videosLengthLabel () {
videosLengthLabel (): string {
return this.$gettextInterpolate(this.$gettext('%{videosLength} videos'), { videosLength: this.playlist.videosLength })
}
},

View File

@ -2,7 +2,7 @@
<div class="block-warning" v-bind:class="{ highlight: highlight }" >
<img src="/img/sepia-warning.svg" alt="">
<div v-translate="{ indexName: indexName }">
<div v-translate>
<strong>%{indexName}</strong> displays videos and channels that match your search but is not the publisher, nor the owner.
If you notice any problems with a video, report it to the administrators on the PeerTube website where the video is published.
</div>
@ -49,9 +49,9 @@
</style>
<script lang="ts">
import Vue from 'vue'
import { defineComponent } from 'vue'
export default Vue.extend({
export default defineComponent({
props: {
indexName: String,
highlight: Boolean

View File

@ -115,41 +115,42 @@
</style>
<script lang="ts">
import Vue, { PropType } from 'vue'
import { defineComponent, PropType } from 'vue'
import ActorMiniature from './ActorMiniature.vue'
import { Video } from '../../../PeerTube/shared/models'
import { durationToString } from '../shared/utils'
import { renderMarkdown } from '../shared/markdown-render'
import { useGettext } from '@jshmrtn/vue3-gettext'
import { EnhancedVideo } from '../../../server/types/video.model'
export default Vue.extend({
export default defineComponent({
components: {
'actor-miniature': ActorMiniature
},
props: {
video: Object as PropType<Video>
video: Object as PropType<EnhancedVideo>
},
computed: {
host () {
host (): string {
const url = this.video.url
return new URL(url as string).host
},
formattedDuration () {
formattedDuration (): string {
return durationToString(this.video.duration)
},
windowWidth () {
windowWidth (): number {
return window.innerWidth
},
publicationDate () {
publicationDate (): string {
return new Date(this.video.publishedAt).toLocaleDateString()
},
watchVideoMessage () {
watchVideoMessage (): string {
return this.$gettextInterpolate(this.$gettext('Watch the video on %{host}'), { host: this.host })
}
},

View File

@ -1,42 +1,39 @@
import './scss/main.scss'
import Vue from 'vue'
import GetTextPlugin from 'vue-gettext'
import { createApp } from 'vue'
import VueMatomo from 'vue-matomo'
import App from './App.vue'
import Router from 'vue-router'
import Search from './views/Search.vue'
import VueRouter from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import { createGettext, useGettext } from '@jshmrtn/vue3-gettext'
Vue.use(VueRouter)
Vue.config.productionTip = process.env.NODE_ENV === 'production'
const app = createApp(App)
// ############# I18N ##############
const availableLanguages = {
'en_US': 'English',
'fr_FR': 'Français',
'de': 'Deutsch',
'el': 'ελληνικά',
'es': 'Español',
'gd': 'Gàidhlig',
'gl': 'galego',
'ru': 'русский',
'it': 'Italiano',
'ja': '日本語',
'sv': 'svenska',
'nl': 'Nederlands',
'oc': 'Occitan',
'sq': 'Shqip',
'pt_BR': 'Português (Brasil)',
'bn': 'বাংলা',
'pl': 'Polski'
en_US: 'English',
fr_FR: 'Français',
de: 'Deutsch',
el: 'ελληνικά',
es: 'Español',
gd: 'Gàidhlig',
gl: 'galego',
ru: 'русский',
it: 'Italiano',
ja: '日本語',
sv: 'svenska',
nl: 'Nederlands',
oc: 'Occitan',
sq: 'Shqip',
pt_BR: 'Português (Brasil)',
bn: 'বাংলা',
pl: 'Polski'
}
const aliasesLanguages = {
'en': 'en_US',
'fr': 'fr_FR',
'br': 'pt_BR',
'pt': 'pt_BR'
en: 'en_US',
fr: 'fr_FR',
br: 'pt_BR',
pt: 'pt_BR'
}
const allLocales = Object.keys(availableLanguages).concat(Object.keys(aliasesLanguages))
@ -64,108 +61,107 @@ if (allLocales.includes(localePath)) {
currentLanguage = aliasesLanguages[snakeCaseLanguage] ? aliasesLanguages[snakeCaseLanguage] : snakeCaseLanguage
}
Vue.filter('translate', value => {
return value ? Vue.prototype.$gettext(value.toString()) : ''
})
buildTranslationsPromise(defaultLanguage, currentLanguage)
.catch(err => {
console.error('Cannot load translations.', err)
const p = buildTranslationsPromise(defaultLanguage, currentLanguage)
p.catch(err => {
console.error('Cannot load translations.', err)
return { default: {} }
}).then(translations => {
Vue.use(GetTextPlugin, {
translations,
availableLanguages,
defaultLanguage: 'en_US',
silent: currentLanguage === 'en_US'
return { default: {} }
})
Vue.config.language = currentLanguage
// Routes
let routes = []
for (const locale of [ '' ].concat(allLocales)) {
const base = locale
? '/' + locale + '/'
: '/'
routes = routes.concat([
{
path: base,
component: Search
},
{
path: base + 'search',
component: Search
}
])
}
routes.push({
path: '/*',
// Don't want to create a 404 page
component: Search
})
const router = new Router({
mode: 'history',
routes
})
// Stats Matomo
if (!(navigator.doNotTrack === 'yes' ||
navigator.doNotTrack === '1' ||
window.doNotTrack === '1')
) {
Vue.use(VueMatomo, {
// Configure your matomo server and site
host: 'https://stats.framasoft.org/',
siteId: 77,
// Enables automatically registering pageviews on the router
router,
// Require consent before sending tracking information to matomo
// Default: false
requireConsent: false,
// Whether to track the initial page view
// Default: true
trackInitialView: true,
// Changes the default .js and .php endpoint's filename
// Default: 'piwik'
trackerFileName: 'p',
enableLinkTracking: true
.then(translations => {
const gettext = createGettext({
translations,
availableLanguages,
defaultLanguage: currentLanguage,
mutedLanguages: [ 'en_US' ]
})
const _paq = []
app.use(gettext)
// CNIL conformity
_paq.push([ function piwikCNIL () {
const self = this
// Routes
let routes = []
function getOriginalVisitorCookieTimeout () {
const now = new Date()
const nowTs = Math.round(now.getTime() / 1000)
const visitorInfo = self.getVisitorInfo()
const createTs = parseInt(visitorInfo[2], 10)
const cookieTimeout = 33696000 // 13 months in seconds
return (createTs + cookieTimeout) - nowTs
}
for (const locale of [ '' ].concat(allLocales)) {
const base = locale
? '/' + locale + '/'
: '/'
this.setVisitorCookieTimeout(getOriginalVisitorCookieTimeout())
} ])
}
routes = routes.concat([
{
path: base,
component: Search
},
{
path: base + 'search',
component: Search
}
])
}
new Vue({
router,
render: h => h(App)
}).$mount('#app')
})
routes.push({
path: '/*',
// Don't want to create a 404 page
component: Search
})
const router = createRouter({
history: createWebHistory(),
routes
})
// Stats Matomo
if (!(navigator.doNotTrack === 'yes' ||
navigator.doNotTrack === '1' ||
window.doNotTrack === '1')
) {
app.use(VueMatomo, {
// Configure your matomo server and site
host: 'https://stats.framasoft.org/',
siteId: 77,
// Enables automatically registering pageviews on the router
router,
// Require consent before sending tracking information to matomo
// Default: false
requireConsent: false,
// Whether to track the initial page view
// Default: true
trackInitialView: true,
// Changes the default .js and .php endpoint's filename
// Default: 'piwik'
trackerFileName: 'p',
enableLinkTracking: true
})
const _paq = []
// CNIL conformity
_paq.push([ function piwikCNIL () {
// @ts-expect-error
const self = this
function getOriginalVisitorCookieTimeout () {
const now = new Date()
const nowTs = Math.round(now.getTime() / 1000)
const visitorInfo = self.getVisitorInfo()
const createTs = parseInt(visitorInfo[2], 10)
const cookieTimeout = 33696000 // 13 months in seconds
return (createTs + cookieTimeout) - nowTs
}
// @ts-expect-error
this.setVisitorCookieTimeout(getOriginalVisitorCookieTimeout())
} ])
}
app.use(router)
app.mount('#app')
})
.catch(err => console.error(err))
function buildTranslationsPromise (defaultLanguage, currentLanguage) {
const translations = {}

View File

@ -1,6 +1,4 @@
export interface SearchUrl {
[id: string]: string | undefined | number[] | string[]
search?: string
nsfw?: string
publishedDateRange?: string
@ -11,4 +9,9 @@ export interface SearchUrl {
tagsAllOf?: string[]
tagsOneOf?: string[]
isLive?: boolean | string
sort?: string
page?: number | string
}

View File

@ -1,10 +1,8 @@
import Vue from 'vue'
function buildApiUrl (path: string) {
const normalizedPath = path.startsWith('/') ? path : '/' + path
if (Vue.config.productionTip) return normalizedPath
return process.env.VUE_APP_API_URL + normalizedPath
const base = process.env.VUE_APP_API_URL || ''
return base + normalizedPath
}
function durationToString (duration: number) {

View File

@ -1,6 +0,0 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
declare var $language

11
client/src/typings/vue.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { Language } from '@jshmrtn/vue3-gettext'
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$gettext: Language['$gettext']
$pgettext: Language['$pgettext']
$ngettext: Language['$ngettext']
$npgettext: Language['$npgettext']
$gettextInterpolate: Language['interpolate']
}
}

View File

@ -1,13 +1,13 @@
<template>
<div>
<my-header v-bind:indexName="indexName" v-bind:smallFormat="searchDone"></my-header>
<router-link v-if="searchDone" class="header-left-logo" to="/" @click.native="simpleFormReset(false)" :title="homeTitleMessage">
<router-link v-if="searchDone" class="header-left-logo" to="/" @click="simpleFormReset(false)" :title="homeTitleMessage">
<img src="/img/peertube-logo.svg" alt="">
</router-link>
<main>
<form id="search-anchor" class="search-container" v-bind:class="{ 'search-container-small': !searchDone }" role="search" onsubmit="return false;">
<input v-bind:placeholder="inputPlaceholder" autofocus v-on:keyup.enter="doNewSearch()" type="text" v-model="formSearch" name="search" autocapitalize="off" autocomplete="off" autocorrect="off" maxlength="1024" />
<input v-bind:placeholder="inputPlaceholder" autofocus type="text" v-model="formSearch" name="search" autocapitalize="off" autocomplete="off" autocorrect="off" maxlength="1024" />
<button v-on:click="doNewSearch()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@ -225,6 +225,8 @@
</template>
<style lang="scss">
@use 'sass:math';
@import '../scss/_variables';
main {
@ -405,7 +407,7 @@
position: absolute;
right: 0;
top: 50%;
margin-top: -$size/2;
margin-top: math.div(-$size, 2);
margin-right: 10px;
width: 0;
height: 0;
@ -439,7 +441,7 @@
</style>
<script lang="ts">
import Vue from 'vue'
import { defineComponent } from 'vue'
import Header from '../components/Header.vue'
import SearchWarning from '../components/SearchWarning.vue'
import VideoResult from '../components/VideoResult.vue'
@ -451,14 +453,14 @@
import { SearchUrl } from '../models'
import { EnhancedVideo } from '../../../server/types/video.model'
import { EnhancedVideoChannel } from '../../../server/types/channel.model'
import VueTagsInput from '@johmun/vue-tags-input'
import VueTagsInput from '@sipec/vue3-tags-input'
import Pagination from '../components/Pagination.vue'
import { VideoChannelsSearchQuery, ResultList, VideosSearchQuery } from '../../../PeerTube/shared/models'
import Nprogress from 'nprogress'
import { EnhancedPlaylist } from '../../../server/types/playlist.model'
import { PlaylistsSearchQuery } from '../../../server/types/search-query/playlist-search.model'
export default Vue.extend({
export default defineComponent({
components: {
'my-header': Header,
'search-warning': SearchWarning,
@ -534,7 +536,7 @@
watch: {
// For pagination change
$route(to, from) {
const urlPage = this.$route.query.page
const urlPage = this.$route.query.page as string
const scrollToResults = urlPage && parseInt(urlPage) !== this.currentPage
@ -545,6 +547,8 @@
return
}
console.log('do search')
this.doSearch()
if (scrollToResults) this.scrollToResults()
},
@ -555,20 +559,20 @@
},
computed: {
applyFiltersLabel () {
applyFiltersLabel (): string {
return this.$gettext('Apply filters')
},
inputPlaceholder () {
inputPlaceholder (): string {
return this.$gettext('Keyword, channel, video, playlist, etc.')
},
tagsPlaceholder () {
tagsPlaceholder (): string {
return this.$gettext('Add tag')
},
publishedDateRanges () {
return [
publishedDateRanges (): { id: string, label: string }[] {
return [
{
id: 'any_published_date',
label: this.$gettext('Any')
@ -592,7 +596,7 @@
]
},
durationRanges () {
durationRanges (): { id: string, label: string }[] {
return [
{
id: 'any_duration',
@ -613,7 +617,7 @@
]
},
videoCategories () {
videoCategories (): { id: string, label: string }[] {
return [
{ id: '1', label: this.$gettext('Music') },
{ id: '2', label: this.$gettext('Films') },
@ -636,7 +640,7 @@
]
},
videoLicences () {
videoLicences (): { id: string, label: string }[] {
return [
{ id: '1', label: this.$gettext('Attribution') },
{ id: '2', label: this.$gettext('Attribution - Share Alike') },
@ -648,7 +652,7 @@
]
},
videoLanguages () {
videoLanguages (): { id: string, label: string }[] {
return [
{ id: 'en', label: this.$gettext('English') },
{ id: 'fr', label: this.$gettext('Français') },
@ -673,7 +677,7 @@
]
},
boostLanguagesQuery () {
boostLanguagesQuery (): string[] {
const languages = new Set<string>()
for (const completeLanguage of navigator.languages) {
@ -687,7 +691,7 @@
return Array.from(languages)
},
homeTitleMessage () {
homeTitleMessage (): string {
return this.$gettext('Display homepage')
}
},
@ -695,6 +699,8 @@
methods: {
doNewSearch () {
console.log('do new search')
this.currentPage = 1
this.channelsCount = null
this.videosCount = null
@ -728,8 +734,10 @@
this.playlistsCount = playlistsResult.total
this.resultsCount = videosResult.total + channelsResult.total + playlistsResult.total
this.results = channelsResult.data.concat(playlistsResult.data)
.concat(videosResult.data)
this.results = channelsResult.data
this.results = this.results.concat(playlistsResult.data)
this.results = this.results.concat(videosResult.data)
if (this.formSort === '-match') {
this.results.sort((r1, r2) => {
@ -792,11 +800,11 @@
page: this.currentPage
}
this.$router.push({ path: '/search', query })
this.$router.push({ path: '/search', query: query as any })
},
loadUrl () {
const query = this.$route.query as SearchUrl & { page: number }
const query = this.$route.query as SearchUrl
if (query.search) this.formSearch = query.search
else this.formSearch = undefined

View File

@ -1,8 +1,9 @@
{
"compilerOptions": {
"target": "es5",
"module": "es2020",
"target": "esnext",
"module": "esnext",
"strict": false,
"noImplicitThis": true,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,

File diff suppressed because it is too large Load Diff