Migrate to vite
This commit is contained in:
parent
b2e255d97b
commit
468007dba8
|
@ -20,7 +20,7 @@ $ node dist/server
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd client && npm run serve
|
$ cd client && npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open http://localhost:8080.
|
Then open http://localhost:8080.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
VUE_APP_API_URL=http://localhost:3234
|
VITE_APP_API_URL=http://localhost:3234
|
||||||
#VUE_APP_API_URL=https://search.joinpeertube.org
|
#VITE_APP_API_URL=https://search.joinpeertube.org
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "vue-eslint-parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@typescript-eslint/parser"
|
||||||
|
},
|
||||||
|
"plugins": [ "@typescript-eslint" ],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:vue/vue3-recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/require-default-prop": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
src/locale/**/*~
|
src/locale/**/*~
|
||||||
|
dist/
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"plugins": {
|
|
||||||
"autoprefixer": {}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="<%= BASE_URL %>img/favicon.png">
|
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -15,9 +15,7 @@
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
|
|
||||||
<div id="footer"></div>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -3,34 +3,32 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve --mode development",
|
"dev": "vite",
|
||||||
"build": "vue-cli-service build --mode production",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore --fix src",
|
||||||
"i18n:update": "git fetch weblate && git merge weblate/master && rm -f src/locale/en_US/LC_MESSAGES/app.po && make clean && make makemessages && make translations"
|
"i18n:update": "git fetch weblate && git merge weblate/master && rm -f src/locale/en_US/LC_MESSAGES/app.po && make clean && make makemessages && make translations"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jshmrtn/vue3-gettext": "^1.5.0",
|
|
||||||
"@sipec/vue3-tags-input": "^3.0.4",
|
"@sipec/vue3-tags-input": "^3.0.4",
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
"@types/markdown-it": "^10.0.1",
|
"@types/markdown-it": "^12.2.3",
|
||||||
"@vue/cli-plugin-typescript": "~5.0.0-beta.2",
|
"@typescript-eslint/eslint-plugin": "^5.12.1",
|
||||||
"@vue/cli-service": "~5.0.0-beta.2",
|
"@typescript-eslint/parser": "^5.12.1",
|
||||||
"@vue/compiler-sfc": "^3.1.0",
|
"@vitejs/plugin-vue": "^2.2.2",
|
||||||
"axios": "^0.20.0",
|
"@vue/eslint-config-typescript": "^10.0.0",
|
||||||
"markdown-it": "^11.0.0",
|
"axios": "^0.26.0",
|
||||||
|
"eslint": "^8.9.0",
|
||||||
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
|
"markdown-it": "^12.3.2",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"register-service-worker": "^1.0.0",
|
"sass": "^1.49.8",
|
||||||
"sass": "^1.35.1",
|
"typescript": "~4.5.5",
|
||||||
"sass-loader": "^12.1.0",
|
"vite": "^2.8.4",
|
||||||
"typescript": "~4.3.4",
|
"vue": "^3.2.31",
|
||||||
"vue": "^3.1.0",
|
"vue-matomo": "^4.1.0",
|
||||||
"vue-matomo": "^4.0.1",
|
"vue-router": "^4.0.12",
|
||||||
"vue-router": "^4.0.0"
|
"vue-tsc": "^0.31.4",
|
||||||
},
|
"vue3-gettext": "^2.1.0"
|
||||||
"browserslist": [
|
}
|
||||||
"> 1%",
|
|
||||||
"last 2 versions",
|
|
||||||
"not dead"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<router-view id="main-container" class="container"/>
|
<router-view
|
||||||
|
id="main-container"
|
||||||
|
class="container"
|
||||||
|
/>
|
||||||
|
|
||||||
<my-footer></my-footer>
|
<my-footer />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -18,7 +21,7 @@
|
||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
title: process.env.VUE_APP_TITLE
|
title: import.meta.env.VITE_APP_TITLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<a v-bind:href="actor.url" rel="nofollow noreferrer noopener" target="_blank" class="actor" v-bind:title="linkTitle">
|
<a
|
||||||
<img v-if="actor.avatar && !avatarError" v-bind:src="actor.avatar.url" alt="" :class="{ account: isAccount }" @error="setAvatarError()">
|
:href="actor.url"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
class="actor"
|
||||||
|
:title="linkTitle"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="actor.avatar && !avatarError"
|
||||||
|
:src="actor.avatar.url"
|
||||||
|
alt=""
|
||||||
|
:class="{ account: isAccount }"
|
||||||
|
@error="setAvatarError()"
|
||||||
|
>
|
||||||
|
|
||||||
<strong>{{ actor.displayName }}</strong>
|
<strong>{{ actor.displayName }}</strong>
|
||||||
|
|
||||||
|
@ -10,6 +22,42 @@
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue'
|
||||||
|
import { AccountSummary, VideoChannelSummary } from '../../../PeerTube/shared/models'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
actor: Object as PropType<AccountSummary | VideoChannelSummary>,
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
avatarError: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
linkTitle (): string {
|
||||||
|
if (this.type === 'channel') return this.$gettext('Go on this channel page')
|
||||||
|
|
||||||
|
return this.$gettext('Go on this account page')
|
||||||
|
},
|
||||||
|
|
||||||
|
isAccount (): boolean {
|
||||||
|
return this.type === 'account'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
setAvatarError () {
|
||||||
|
this.avatarError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -56,39 +104,3 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from 'vue'
|
|
||||||
import { AccountSummary, VideoChannelSummary } from '../../../PeerTube/shared/models'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
actor: Object as PropType<AccountSummary | VideoChannelSummary>,
|
|
||||||
type: String
|
|
||||||
},
|
|
||||||
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
avatarError: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
setAvatarError () {
|
|
||||||
this.avatarError = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
linkTitle (): string {
|
|
||||||
if (this.type === 'channel') return this.$gettext('Go on this channel page')
|
|
||||||
|
|
||||||
return this.$gettext('Go on this account page')
|
|
||||||
},
|
|
||||||
|
|
||||||
isAccount (): boolean {
|
|
||||||
return this.type === 'account'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,15 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="channel root-result">
|
<div class="channel root-result">
|
||||||
|
<a
|
||||||
<a target="_blank" rel="nofollow noreferrer noopener" v-bind:href="channel.url" :title="discoverChannelMessage" class="avatar">
|
target="_blank"
|
||||||
<img v-if="channel.avatar" v-bind:src="channel.avatar.url" alt="">
|
rel="nofollow noreferrer noopener"
|
||||||
<img v-else src="/img/default-avatar.png" alt="">
|
:href="channel.url"
|
||||||
|
:title="discoverChannelMessage"
|
||||||
|
class="avatar"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="channel.avatar"
|
||||||
|
:src="channel.avatar.url"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-else
|
||||||
|
src="/img/default-avatar.png"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="information">
|
<div class="information">
|
||||||
<div class="title-block">
|
<div class="title-block">
|
||||||
<h5 class="title">
|
<h5 class="title">
|
||||||
<a target="_blank" rel="nofollow noreferrer noopener" v-bind:href="channel.url" :title="discoverChannelMessage">
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="channel.url"
|
||||||
|
:title="discoverChannelMessage"
|
||||||
|
>
|
||||||
{{ channel.displayName }}
|
{{ channel.displayName }}
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -23,40 +41,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="description">{{ channel.description }}</div>
|
<div class="description">
|
||||||
|
{{ channel.description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<a class="button-link" rel="nofollow noreferrer noopener" target="_blank" v-bind:href="channel.url">
|
<a
|
||||||
|
class="button-link"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
:href="channel.url"
|
||||||
|
>
|
||||||
{{ discoverChannelMessage }}
|
{{ discoverChannelMessage }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../scss/_variables';
|
|
||||||
|
|
||||||
.channel {
|
|
||||||
.avatar {
|
|
||||||
width: $thumbnail-width;
|
|
||||||
min-width: $thumbnail-width;
|
|
||||||
margin-right: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit: cover;
|
|
||||||
width: 110px;
|
|
||||||
height: 110px;
|
|
||||||
min-width: 110px;
|
|
||||||
min-height: 110px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { defineComponent, PropType } from 'vue'
|
||||||
import { VideoChannel } from '../../../PeerTube/shared/models'
|
import { VideoChannel } from '../../../PeerTube/shared/models'
|
||||||
|
@ -85,3 +87,25 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
.channel {
|
||||||
|
.avatar {
|
||||||
|
width: $thumbnail-width;
|
||||||
|
min-width: $thumbnail-width;
|
||||||
|
margin-right: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
width: 110px;
|
||||||
|
height: 110px;
|
||||||
|
min-width: 110px;
|
||||||
|
min-height: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,42 +1,107 @@
|
||||||
<template>
|
<template>
|
||||||
<footer id="main-footer">
|
<footer id="main-footer">
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img src="/img/bottom-peertube-logo-v3.svg" alt="">
|
<img
|
||||||
|
src="/img/bottom-peertube-logo-v3.svg"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
|
||||||
<div v-translate class="description">A free software to take back control of your videos</div>
|
<div
|
||||||
|
v-translate
|
||||||
|
class="description"
|
||||||
|
>
|
||||||
|
A free software to take back control of your videos
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div>
|
<div>
|
||||||
<div v-translate class="subtitle">Open your own videos website with PeerTube!</div>
|
<div
|
||||||
|
v-translate
|
||||||
|
class="subtitle"
|
||||||
|
>
|
||||||
|
Open your own videos website with PeerTube!
|
||||||
|
</div>
|
||||||
|
|
||||||
<a target="_blank" v-translate href="https://docs.joinpeertube.org/#/install-any-os">Install PeerTube</a>
|
<a
|
||||||
|
v-translate
|
||||||
|
target="_blank"
|
||||||
|
href="https://docs.joinpeertube.org/#/install-any-os"
|
||||||
|
>Install PeerTube</a>
|
||||||
|
|
||||||
<a target="_blank" v-translate href="https://joinpeertube.org#what-is-peertube">Why should I have my own PeerTube website?</a>
|
<a
|
||||||
|
v-translate
|
||||||
|
target="_blank"
|
||||||
|
href="https://joinpeertube.org#what-is-peertube"
|
||||||
|
>Why should I have my own PeerTube website?</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div v-translate class="subtitle">Create an account to take back control of your videos</div>
|
<div
|
||||||
|
v-translate
|
||||||
|
class="subtitle"
|
||||||
|
>
|
||||||
|
Create an account to take back control of your videos
|
||||||
|
</div>
|
||||||
|
|
||||||
<a target="_blank" v-translate href="https://joinpeertube.org/instances">Open an account on a PeerTube website</a>
|
<a
|
||||||
|
v-translate
|
||||||
|
target="_blank"
|
||||||
|
href="https://joinpeertube.org/instances"
|
||||||
|
>Open an account on a PeerTube website</a>
|
||||||
|
|
||||||
<a target="_blank" v-translate href="https://docs.joinpeertube.org/#/use-library?id=playlist">Create playlists</a>
|
<a
|
||||||
|
v-translate
|
||||||
|
target="_blank"
|
||||||
|
href="https://docs.joinpeertube.org/#/use-library?id=playlist"
|
||||||
|
>Create playlists</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="big-link">
|
<div class="big-link">
|
||||||
<a href="https://joinpeertube.org" target="_blank" v-translate> >> Check all guides on joinpeertube.org << </a>
|
<a
|
||||||
|
v-translate
|
||||||
|
href="https://joinpeertube.org"
|
||||||
|
target="_blank"
|
||||||
|
> >> Check all guides on joinpeertube.org << </a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a v-translate href="https://framagit.org/framasoft/peertube/search-index/" target="_blank">Source code</a>
|
<a
|
||||||
|
v-translate
|
||||||
|
href="https://framagit.org/framasoft/peertube/search-index/"
|
||||||
|
target="_blank"
|
||||||
|
>Source code</a>
|
||||||
|
|
||||||
<a v-if="legalNoticesUrl" v-translate :href="legalNoticesUrl" target="_blank">Legal notices</a>
|
<a
|
||||||
|
v-if="legalNoticesUrl"
|
||||||
|
v-translate
|
||||||
|
:href="legalNoticesUrl"
|
||||||
|
target="_blank"
|
||||||
|
>Legal notices</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { getConfig } from '../shared/config'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
legalNoticesUrl: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async mounted () {
|
||||||
|
const config = await getConfig()
|
||||||
|
|
||||||
|
this.legalNoticesUrl = config.legalNoticesUrl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -152,22 +217,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { getConfig } from '../shared/config'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
legalNoticesUrl: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async mounted () {
|
|
||||||
const config = await getConfig()
|
|
||||||
|
|
||||||
this.legalNoticesUrl = config.legalNoticesUrl
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,29 +1,87 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<header>
|
<header>
|
||||||
<interface-language-dropdown class="interface-language-dropdown"></interface-language-dropdown>
|
<interface-language-dropdown class="interface-language-dropdown" />
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
<span v-if="configLoaded && !titleImageUrl">{{ indexName }}</span>
|
<span v-if="configLoaded && !titleImageUrl">{{ indexName }}</span>
|
||||||
|
|
||||||
<img class="title-image" :src="titleImageUrl" :alt="indexName" />
|
<img
|
||||||
|
class="title-image"
|
||||||
|
:src="titleImageUrl"
|
||||||
|
:alt="indexName"
|
||||||
|
>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<template v-if="!smallFormat">
|
<template v-if="!smallFormat">
|
||||||
<h4>
|
<h4>
|
||||||
<div v-translate>A search engine of <a href="https://joinpeertube.org" target="_blank">PeerTube</a> videos, channels and playlists</div>
|
<div v-translate>
|
||||||
|
A search engine of <a
|
||||||
|
href="https://joinpeertube.org"
|
||||||
|
target="_blank"
|
||||||
|
>PeerTube</a> videos, channels and playlists
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-translate>Developed by <a href="https://framasoft.org" target="_blank">Framasoft</a></div>
|
<div v-translate>
|
||||||
|
Developed by <a
|
||||||
|
href="https://framasoft.org"
|
||||||
|
target="_blank"
|
||||||
|
>Framasoft</a>
|
||||||
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class="search-home">
|
<div class="search-home">
|
||||||
<img v-if="searchImageUrl" :src="searchImageUrl" alt="">
|
<img
|
||||||
|
v-if="searchImageUrl"
|
||||||
|
:src="searchImageUrl"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { getConfig } from '../shared/config'
|
||||||
|
import { buildApiUrl } from '../shared/utils'
|
||||||
|
import InterfaceLanguageDropdown from './InterfaceLanguageDropdown.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
'interface-language-dropdown': InterfaceLanguageDropdown
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
indexName: String,
|
||||||
|
smallFormat: Boolean,
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
configLoaded: false,
|
||||||
|
titleImageUrl: '',
|
||||||
|
searchImageUrl: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async mounted () {
|
||||||
|
const config = await getConfig()
|
||||||
|
|
||||||
|
this.titleImageUrl = config.searchInstanceNameImage
|
||||||
|
? buildApiUrl(config.searchInstanceNameImage)
|
||||||
|
: ''
|
||||||
|
|
||||||
|
this.searchImageUrl = config.searchInstanceSearchImage
|
||||||
|
? buildApiUrl(config.searchInstanceSearchImage)
|
||||||
|
: buildApiUrl('/img/search-home.png')
|
||||||
|
|
||||||
|
this.configLoaded = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -108,43 +166,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { getConfig } from '../shared/config'
|
|
||||||
import { buildApiUrl } from '../shared/utils'
|
|
||||||
import InterfaceLanguageDropdown from './InterfaceLanguageDropdown.vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
'interface-language-dropdown': InterfaceLanguageDropdown
|
|
||||||
},
|
|
||||||
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
configLoaded: false,
|
|
||||||
titleImageUrl: '',
|
|
||||||
searchImageUrl: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
indexName: String,
|
|
||||||
smallFormat: Boolean,
|
|
||||||
},
|
|
||||||
|
|
||||||
async mounted () {
|
|
||||||
const config = await getConfig()
|
|
||||||
|
|
||||||
this.titleImageUrl = config.searchInstanceNameImage
|
|
||||||
? buildApiUrl(config.searchInstanceNameImage)
|
|
||||||
: ''
|
|
||||||
|
|
||||||
this.searchImageUrl = config.searchInstanceSearchImage
|
|
||||||
? buildApiUrl(config.searchInstanceSearchImage)
|
|
||||||
: buildApiUrl('/img/search-home.png')
|
|
||||||
|
|
||||||
this.configLoaded = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,21 +1,80 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="interface-language-dropdown">
|
<div class="interface-language-dropdown">
|
||||||
<img :title="imgTitle" v-on:click="toggleShow()" class="interface-language" src="/img/interface-languages.svg" alt="Change interface language">
|
<img
|
||||||
|
:title="imgTitle"
|
||||||
|
class="interface-language"
|
||||||
|
src="/img/interface-languages.svg"
|
||||||
|
alt="Change interface language"
|
||||||
|
@click="toggleShow()"
|
||||||
|
>
|
||||||
|
|
||||||
<div v-if="showMenu" class="menu">
|
<div
|
||||||
<a v-for="(lang, locale) in availableLanguages" :key="locale" :href="buildLanguageRoute(locale)" class="menu-item">
|
v-if="showMenu"
|
||||||
|
class="menu"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
v-for="(lang, locale) in availableLanguages"
|
||||||
|
:key="locale"
|
||||||
|
:href="buildLanguageRoute(locale)"
|
||||||
|
class="menu-item"
|
||||||
|
>
|
||||||
{{ lang }}
|
{{ lang }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<hr />
|
<hr>
|
||||||
|
|
||||||
<a class="menu-item add-your-language" target="_blank" href="https://weblate.framasoft.org/projects/peertube-search-index/client/">
|
<a
|
||||||
|
class="menu-item add-your-language"
|
||||||
|
target="_blank"
|
||||||
|
href="https://weblate.framasoft.org/projects/peertube-search-index/client/"
|
||||||
|
>
|
||||||
Translate
|
Translate
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
showMenu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
imgTitle () {
|
||||||
|
return this.$gettext('Change interface language')
|
||||||
|
},
|
||||||
|
|
||||||
|
availableLanguages (): { [id: string]: string } {
|
||||||
|
const { available } = useGettext()
|
||||||
|
|
||||||
|
return available
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleShow () {
|
||||||
|
this.showMenu = !this.showMenu;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildLanguageRoute(locale: string | number) {
|
||||||
|
const paths = this.$route.fullPath.split('/')
|
||||||
|
|
||||||
|
if (paths.length > 0 && Object.prototype.hasOwnProperty.call(this.availableLanguages, paths[0])) {
|
||||||
|
return '/' + locale + '/' + paths.slice(1).join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/' + locale + this.$route.fullPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -67,44 +126,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { useGettext } from '@jshmrtn/vue3-gettext'
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
showMenu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
imgTitle () {
|
|
||||||
return this.$gettext('Change interface language')
|
|
||||||
},
|
|
||||||
|
|
||||||
availableLanguages (): { [id: string]: string } {
|
|
||||||
const { available } = useGettext()
|
|
||||||
|
|
||||||
return available
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
toggleShow () {
|
|
||||||
this.showMenu = !this.showMenu;
|
|
||||||
},
|
|
||||||
|
|
||||||
buildLanguageRoute(locale: string) {
|
|
||||||
const paths = this.$route.fullPath.split('/')
|
|
||||||
|
|
||||||
if (paths.length > 0 && this.availableLanguages.hasOwnProperty(paths[0])) {
|
|
||||||
return "/" + locale + "/" + paths.slice(1).join("/")
|
|
||||||
} else {
|
|
||||||
return "/" + locale + this.$route.fullPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,29 +1,69 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div
|
||||||
<div class="pagination" v-if="searchDone">
|
v-if="searchDone"
|
||||||
<router-link class="previous" v-bind:class="{ 'none-opacity': modelValue === 1 }" :to="{ query: buildPageUrlQuery(modelValue - 1) }">
|
class="pagination"
|
||||||
<translate>Previous page</translate>
|
>
|
||||||
|
<router-link
|
||||||
|
class="previous"
|
||||||
|
:class="{ 'none-opacity': modelValue === 1 }"
|
||||||
|
:to="{ query: buildPageUrlQuery(modelValue - 1) }"
|
||||||
|
>
|
||||||
|
{{ $gettext('Previous page') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div class="pages">
|
<div class="pages">
|
||||||
<template v-for="page in pages">
|
<template
|
||||||
|
v-for="page in pages"
|
||||||
<router-link v-if="page !== modelValue" class="go-to-page" :to="{ query: buildPageUrlQuery(page) }" :key="page">
|
:key="page"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
v-if="page !== modelValue"
|
||||||
|
class="go-to-page"
|
||||||
|
:to="{ query: buildPageUrlQuery(page) }"
|
||||||
|
>
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div v-else class="current">{{ page }}</div>
|
<div
|
||||||
|
v-else
|
||||||
|
class="current"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-link class="next" v-bind:class="{ 'none-opacity': modelValue >= maxPage }" :to="{ query: buildPageUrlQuery(+modelValue + 1) }">
|
<router-link
|
||||||
<translate>Next page</translate>
|
class="next"
|
||||||
|
:class="{ 'none-opacity': modelValue >= maxPage }"
|
||||||
|
:to="{ query: buildPageUrlQuery(+modelValue + 1) }"
|
||||||
|
>
|
||||||
|
{{ $gettext('Next page') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
maxPage: Number,
|
||||||
|
searchDone: Boolean,
|
||||||
|
modelValue: Number,
|
||||||
|
pages: Array as () => number[]
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
buildPageUrlQuery (page: number) {
|
||||||
|
const query = this.$route.query
|
||||||
|
|
||||||
|
return Object.assign({}, query, { page })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -61,26 +101,4 @@
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
maxPage: Number,
|
|
||||||
searchDone: Boolean,
|
|
||||||
modelValue: Number,
|
|
||||||
pages: Array as () => number[]
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
buildPageUrlQuery (page: number) {
|
|
||||||
const query = this.$route.query
|
|
||||||
|
|
||||||
return Object.assign({}, query, { page })
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="playlist root-result">
|
<div class="playlist root-result">
|
||||||
|
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<a class="img" :title="watchMessage" target="_blank" rel="nofollow noreferrer noopener" v-bind:href="playlist.url">
|
<a
|
||||||
<img v-bind:src="playlist.thumbnailUrl" alt="">
|
class="img"
|
||||||
|
:title="watchMessage"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="playlist.url"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="playlist.thumbnailUrl"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
|
||||||
<span class="videos-length">{{ videosLengthLabel }}</span>
|
<span class="videos-length">{{ videosLengthLabel }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -11,41 +19,105 @@
|
||||||
|
|
||||||
<div class="information">
|
<div class="information">
|
||||||
<h5 class="title">
|
<h5 class="title">
|
||||||
<a :title="watchMessage" target="_blank" rel="nofollow noreferrer noopener" v-bind:href="playlist.url">{{ playlist.displayName }}</a>
|
<a
|
||||||
|
:title="watchMessage"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="playlist.url"
|
||||||
|
>{{ playlist.displayName }}</a>
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="description" v-html="renderMarkdown(playlist.description)"></div>
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<div
|
||||||
|
class="description"
|
||||||
|
v-html="renderMarkdown(playlist.description)"
|
||||||
|
/>
|
||||||
|
<!-- eslint-enable -->
|
||||||
|
|
||||||
<div class="metadatas">
|
<div class="metadata">
|
||||||
<div class="by-account">
|
<div class="by-account">
|
||||||
<label v-translate>Created by</label>
|
<label v-translate>Created by</label>
|
||||||
<actor-miniature type="account" v-bind:actor="playlist.ownerAccount"></actor-miniature>
|
<actor-miniature
|
||||||
|
type="account"
|
||||||
|
:actor="playlist.ownerAccount"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="by-channel">
|
<div class="by-channel">
|
||||||
<label v-translate>In</label>
|
<label v-translate>In</label>
|
||||||
|
|
||||||
<actor-miniature type="channel" v-bind:actor="playlist.videoChannel"></actor-miniature>
|
<actor-miniature
|
||||||
|
type="channel"
|
||||||
|
:actor="playlist.videoChannel"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="publishedAt">
|
<div class="publishedAt">
|
||||||
<label v-translate>Updated on</label>
|
<label v-translate>Updated on</label>
|
||||||
|
|
||||||
<div class="value">{{ updateDate }}</div>
|
<div class="value">
|
||||||
|
{{ updateDate }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<a class="button-link" target="_blank" rel="nofollow noreferrer noopener" v-bind:href="playlist.url">
|
<a
|
||||||
|
class="button-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="playlist.url"
|
||||||
|
>
|
||||||
{{ watchMessage }}
|
{{ watchMessage }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue'
|
||||||
|
import ActorMiniature from './ActorMiniature.vue'
|
||||||
|
import { VideoPlaylist } from '../../../PeerTube/shared/models'
|
||||||
|
import { renderMarkdown } from '../shared/markdown-render'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
'actor-miniature': ActorMiniature
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
playlist: Object as PropType<VideoPlaylist>
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
host (): string {
|
||||||
|
const url = this.playlist.url
|
||||||
|
|
||||||
|
return new URL(url as string).host
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDate (): string {
|
||||||
|
return new Date(this.playlist.updatedAt).toLocaleDateString()
|
||||||
|
},
|
||||||
|
|
||||||
|
watchMessage (): string {
|
||||||
|
return this.$gettextInterpolate(this.$gettext('Watch the playlist on %{host}'), { host: this.host })
|
||||||
|
},
|
||||||
|
|
||||||
|
videosLengthLabel (): string {
|
||||||
|
return this.$gettextInterpolate(this.$gettext('%{videosLength} videos'), { videosLength: this.playlist.videosLength })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
renderMarkdown(markdown: string) {
|
||||||
|
return renderMarkdown(markdown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -100,47 +172,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
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 defineComponent({
|
|
||||||
components: {
|
|
||||||
'actor-miniature': ActorMiniature
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
playlist: Object as PropType<VideoPlaylist>
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
host (): string {
|
|
||||||
const url = this.playlist.url
|
|
||||||
|
|
||||||
return new URL(url as string).host
|
|
||||||
},
|
|
||||||
|
|
||||||
updateDate (): string {
|
|
||||||
return new Date(this.playlist.updatedAt).toLocaleDateString()
|
|
||||||
},
|
|
||||||
|
|
||||||
watchMessage (): string {
|
|
||||||
return this.$gettextInterpolate(this.$gettext('Watch the playlist on %{host}'), { host: this.host })
|
|
||||||
},
|
|
||||||
|
|
||||||
videosLengthLabel (): string {
|
|
||||||
return this.$gettextInterpolate(this.$gettext('%{videosLength} videos'), { videosLength: this.playlist.videosLength })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
renderMarkdown(markdown: string) {
|
|
||||||
return renderMarkdown(markdown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="block-warning" v-bind:class="{ highlight: highlight }" >
|
<div
|
||||||
<img src="/img/sepia-warning.svg" alt="">
|
class="block-warning"
|
||||||
|
:class="{ highlight: highlight }"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/img/sepia-warning.svg"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
|
||||||
<div v-translate>
|
<div v-translate>
|
||||||
<strong>%{indexName}</strong> displays videos and channels that match your search but is not the publisher, nor the owner.
|
<strong>%{indexName}</strong> displays videos and channels that match your search but is not the publisher, nor the owner.
|
||||||
|
@ -9,6 +15,17 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
indexName: String,
|
||||||
|
highlight: Boolean
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../scss/_variables';
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
@ -44,17 +61,4 @@
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
indexName: String,
|
|
||||||
highlight: Boolean
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -1,124 +1,109 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="video root-result">
|
<div class="video root-result">
|
||||||
|
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<a class="img" :title="watchVideoMessage" target="_blank" rel="nofollow noreferrer noopener" v-bind:href="video.url">
|
<a
|
||||||
<img v-bind:src="getVideoThumbnailUrl()" alt="" @error="setThumbnailError()" :class="{ error: thumbnailError }">
|
class="img"
|
||||||
|
:title="watchVideoMessage"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="video.url"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="getVideoThumbnailUrl()"
|
||||||
|
alt=""
|
||||||
|
:class="{ error: thumbnailError }"
|
||||||
|
@error="setThumbnailError()"
|
||||||
|
>
|
||||||
|
|
||||||
<span v-if="video.isLive" class="live-info" v-translate>LIVE</span>
|
<span
|
||||||
<span v-else class="duration">{{ formattedDuration }}</span>
|
v-if="video.isLive"
|
||||||
|
v-translate
|
||||||
|
class="live-info"
|
||||||
|
>LIVE</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="duration"
|
||||||
|
>{{ formattedDuration }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="information">
|
<div class="information">
|
||||||
<h5 class="title">
|
<h5 class="title">
|
||||||
<a :title="watchVideoMessage" target="_blank" rel="nofollow noreferrer noopener" v-bind:href="video.url">{{ video.name }}</a>
|
<a
|
||||||
|
:title="watchVideoMessage"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="video.url"
|
||||||
|
>{{ video.name }}</a>
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="description" v-html="renderMarkdown(video.description)"></div>
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
|
<div
|
||||||
|
class="description"
|
||||||
|
v-html="renderMarkdown(video.description)"
|
||||||
|
/>
|
||||||
|
<!-- eslint-enable -->
|
||||||
|
|
||||||
<div class="metadatas">
|
<div class="metadata">
|
||||||
<div class="by-account">
|
<div class="by-account">
|
||||||
<label v-translate>Created by</label>
|
<label v-translate>Created by</label>
|
||||||
<actor-miniature type="account" v-bind:actor="video.account"></actor-miniature>
|
<actor-miniature
|
||||||
|
type="account"
|
||||||
|
:actor="video.account"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="by-channel">
|
<div class="by-channel">
|
||||||
<label v-translate>In</label>
|
<label v-translate>In</label>
|
||||||
|
|
||||||
<actor-miniature type="channel" v-bind:actor="video.channel"></actor-miniature>
|
<actor-miniature
|
||||||
|
type="channel"
|
||||||
|
:actor="video.channel"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="publishedAt">
|
<div class="publishedAt">
|
||||||
<label v-translate>On</label>
|
<label v-translate>On</label>
|
||||||
|
|
||||||
<div class="value">{{ publicationDate }}</div>
|
<div class="value">
|
||||||
|
{{ publicationDate }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="language">
|
<div class="language">
|
||||||
<label v-translate>Language</label>
|
<label v-translate>Language</label>
|
||||||
|
|
||||||
<div class="value">{{ video.language.label }}</div>
|
<div class="value">
|
||||||
|
{{ video.language.label }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tags" v-if="video.tags && video.tags.length !== 0">
|
<div
|
||||||
|
v-if="video.tags && video.tags.length !== 0"
|
||||||
|
class="tags"
|
||||||
|
>
|
||||||
<label v-translate>Tags</label>
|
<label v-translate>Tags</label>
|
||||||
|
|
||||||
<div class="value">{{ video.tags.join(', ') }}</div>
|
<div class="value">
|
||||||
|
{{ video.tags.join(', ') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<a class="button-link" target="_blank" rel="nofollow noreferrer noopener" v-bind:href="video.url">
|
<a
|
||||||
|
class="button-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer noopener"
|
||||||
|
:href="video.url"
|
||||||
|
>
|
||||||
{{ watchVideoMessage }}
|
{{ watchVideoMessage }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
@import '../scss/_variables';
|
|
||||||
|
|
||||||
.video {
|
|
||||||
|
|
||||||
.thumbnail {
|
|
||||||
margin-right: 20px;
|
|
||||||
|
|
||||||
--thumbnail-width: #{$thumbnail-width};
|
|
||||||
--thumbnail-height: #{$thumbnail-height};
|
|
||||||
|
|
||||||
// For the duration overlay
|
|
||||||
.img {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
background-color: #E5E5E5;
|
|
||||||
width: var(--thumbnail-width);
|
|
||||||
height: var(--thumbnail-height);
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
border: 1px solid #E5E5E5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration,
|
|
||||||
.live-info {
|
|
||||||
position: absolute;
|
|
||||||
padding: 2px 5px;
|
|
||||||
right: 5px;
|
|
||||||
bottom: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 11px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration {
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.live-info {
|
|
||||||
background-color: rgba(224, 8, 8 ,.8);
|
|
||||||
font-weight: $font-semibold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $small-view) {
|
|
||||||
--thumbnail-width: calc(100% + 10px);
|
|
||||||
--thumbnail-height: auto;
|
|
||||||
|
|
||||||
img {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { defineComponent, PropType } from 'vue'
|
||||||
import ActorMiniature from './ActorMiniature.vue'
|
import ActorMiniature from './ActorMiniature.vue'
|
||||||
|
@ -184,3 +169,63 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../scss/_variables';
|
||||||
|
|
||||||
|
.video {
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
margin-right: 20px;
|
||||||
|
|
||||||
|
--thumbnail-width: #{$thumbnail-width};
|
||||||
|
--thumbnail-height: #{$thumbnail-height};
|
||||||
|
|
||||||
|
// For the duration overlay
|
||||||
|
.img {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
background-color: #E5E5E5;
|
||||||
|
width: var(--thumbnail-width);
|
||||||
|
height: var(--thumbnail-height);
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
border: 1px solid #E5E5E5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration,
|
||||||
|
.live-info {
|
||||||
|
position: absolute;
|
||||||
|
padding: 2px 5px;
|
||||||
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration {
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.live-info {
|
||||||
|
background-color: rgba(224, 8, 8 ,.8);
|
||||||
|
font-weight: $font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $small-view) {
|
||||||
|
--thumbnail-width: calc(100% + 10px);
|
||||||
|
--thumbnail-height: auto;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import './scss/main.scss'
|
import './scss/main.scss'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import VueMatomo from 'vue-matomo'
|
import VueMatomo from 'vue-matomo'
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { createGettext } from 'vue3-gettext'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import Search from './views/Search.vue'
|
import Search from './views/Search.vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import { createGettext, useGettext } from '@jshmrtn/vue3-gettext'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ const allLocales = Object.keys(availableLanguages).concat(Object.keys(aliasesLan
|
||||||
const defaultLanguage = 'en_US'
|
const defaultLanguage = 'en_US'
|
||||||
let currentLanguage = defaultLanguage
|
let currentLanguage = defaultLanguage
|
||||||
|
|
||||||
const basePath = process.env.BASE_URL
|
const basePath = import.meta.env.BASE_URL
|
||||||
const startRegexp = new RegExp('^' + basePath)
|
const startRegexp = new RegExp('^' + basePath)
|
||||||
|
|
||||||
const paths = window.location.pathname
|
const paths = window.location.pathname
|
||||||
|
@ -111,10 +111,7 @@ buildTranslationsPromise(defaultLanguage, currentLanguage)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Stats Matomo
|
// Stats Matomo
|
||||||
if (!(navigator.doNotTrack === 'yes' ||
|
if (!(navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1')) {
|
||||||
navigator.doNotTrack === '1' ||
|
|
||||||
window.doNotTrack === '1')
|
|
||||||
) {
|
|
||||||
app.use(VueMatomo, {
|
app.use(VueMatomo, {
|
||||||
// Configure your matomo server and site
|
// Configure your matomo server and site
|
||||||
host: 'https://stats.framasoft.org/',
|
host: 'https://stats.framasoft.org/',
|
||||||
|
@ -137,26 +134,6 @@ buildTranslationsPromise(defaultLanguage, currentLanguage)
|
||||||
|
|
||||||
enableLinkTracking: true
|
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.use(router)
|
||||||
|
|
|
@ -188,7 +188,7 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadatas {
|
.metadata {
|
||||||
> div {
|
> div {
|
||||||
min-height: 27px;
|
min-height: 27px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -228,7 +228,7 @@ body {
|
||||||
margin: 0 0 20px -10px !important;
|
margin: 0 0 20px -10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadatas {
|
.metadata {
|
||||||
label {
|
label {
|
||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
|
|
||||||
const TEXT_RULES = [
|
const TEXT_RULES = [
|
||||||
'linkify',
|
'linkify',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
function buildApiUrl (path: string) {
|
function buildApiUrl (path: string) {
|
||||||
const normalizedPath = path.startsWith('/') ? path : '/' + path
|
const normalizedPath = path.startsWith('/') ? path : '/' + path
|
||||||
|
|
||||||
const base = process.env.VUE_APP_API_URL || ''
|
const base = import.meta.env.VITE_APP_API_URL || ''
|
||||||
return base + normalizedPath
|
return base + normalizedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,11 +83,11 @@ function publishedDateRangeToAPIParams (publishedDateRange: string) {
|
||||||
return { startDate: date.toISOString(), endDate: undefined }
|
return { startDate: date.toISOString(), endDate: undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractTagsFromQuery (value: any | any[]) {
|
function extractTagsFromQuery <T> (value: T | T[]) {
|
||||||
if (!value) return []
|
if (!value) return []
|
||||||
|
|
||||||
if (Array.isArray(value) === true) {
|
if (Array.isArray(value)) {
|
||||||
return (value as any[]).map(v => ({ text: v }))
|
return value.map(v => ({ text: v }))
|
||||||
}
|
}
|
||||||
|
|
||||||
return [ { text: value } ]
|
return [ { text: value } ]
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
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']
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,35 +1,23 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"moduleResolution": "node",
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": ".",
|
"resolveJsonModule": true,
|
||||||
"lib": [
|
"esModuleInterop": true,
|
||||||
"dom",
|
"lib": ["esnext", "dom"],
|
||||||
"es2015",
|
|
||||||
"es2016",
|
|
||||||
"es2017"
|
|
||||||
],
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@shared/*": [ "../PeerTube/shared/*" ],
|
"@shared/*": [ "../PeerTube/shared/*" ],
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"src/*"
|
"./src/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
"src/**/*.ts",
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
"src/**/*.vue",
|
|
||||||
"tests/**/*.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
server: {
|
||||||
|
port: 8080
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
publicPath: '/'
|
|
||||||
}
|
|
6741
client/yarn.lock
6741
client/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -36,9 +36,8 @@ const apiRoute = '/api/' + API_VERSION
|
||||||
app.use(apiRoute, apiRouter)
|
app.use(apiRoute, apiRouter)
|
||||||
|
|
||||||
// Static client files
|
// Static client files
|
||||||
app.use('/js/', express.static(join(__dirname, '../client/dist/js'), { maxAge: '30d' }))
|
|
||||||
app.use('/css/', express.static(join(__dirname, '../client/dist/css'), { maxAge: '30d' }))
|
|
||||||
app.use('/img/', express.static(join(__dirname, '../client/dist/img'), { maxAge: '30d' }))
|
app.use('/img/', express.static(join(__dirname, '../client/dist/img'), { maxAge: '30d' }))
|
||||||
|
app.use('/assets/', express.static(join(__dirname, '../client/dist/assets'), { maxAge: '30d' }))
|
||||||
app.use('/theme/', express.static(join(__dirname, './themes'), { maxAge: '30d' }))
|
app.use('/theme/', express.static(join(__dirname, './themes'), { maxAge: '30d' }))
|
||||||
|
|
||||||
app.use('/opensearch.xml', async function (req, res) {
|
app.use('/opensearch.xml', async function (req, res) {
|
||||||
|
|
Loading…
Reference in New Issue