mirror of
https://github.com/TeamPiped/Piped.git
synced 2024-11-14 04:08:21 +00:00
Merge pull request #683 from TeamPiped/windicss
Replace UIKit with Windicss
This commit is contained in:
commit
46ad4d1d27
@ -15,9 +15,9 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2.5.1
|
uses: actions/setup-node@v2.5.1
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: "yarn"
|
||||||
- run: yarn install --prefer-offline
|
- run: yarn install --prefer-offline
|
||||||
- run: yarn build && sed -i 's/fonts.gstatic.com/fonts.kavin.rocks/g' dist/css/*.css
|
- run: yarn build && sed -i 's/fonts.gstatic.com/fonts.kavin.rocks/g' dist/assets/*.css
|
||||||
- uses: aquiladev/ipfs-action@v0.1.6
|
- uses: aquiladev/ipfs-action@v0.1.6
|
||||||
id: ipfs-add
|
id: ipfs-add
|
||||||
with:
|
with:
|
@ -8,7 +8,7 @@ RUN yarn install --prefer-offline
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN yarn build && sed -i 's/fonts.gstatic.com/fonts.kavin.rocks/g' dist/css/*.css
|
RUN yarn build && sed -i 's/fonts.gstatic.com/fonts.kavin.rocks/g' dist/assets/*.css
|
||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
28
index.html
Normal file
28
index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html style="background: #0f0f0f" lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<link title="Piped" type="application/opensearchdescription+xml" rel="search" href="/opensearch.xml" />
|
||||||
|
<title>Piped</title>
|
||||||
|
<meta property="og:title" content="Piped" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:image" content="/img/icons/favicon-32x32.png" />
|
||||||
|
<meta property="og:url" content="/" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="An alternative privacy-friendly YouTube frontend which is efficient by design."
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<strong style="color: #fff"
|
||||||
|
>We're sorry but Piped doesn't work properly without JavaScript enabled. Please enable it to
|
||||||
|
continue.</strong
|
||||||
|
>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</html>
|
26
package.json
26
package.json
@ -3,42 +3,42 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vite",
|
||||||
"build": "vue-cli-service build",
|
"build": "vite build",
|
||||||
"lint": "vue-cli-service lint"
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "^3.0.0-5",
|
"@fortawesome/vue-fontawesome": "^3.0.0-5",
|
||||||
"core-js": "3.20.1",
|
"buffer": "^6.0.3",
|
||||||
"css-loader": "^6.5.1",
|
|
||||||
"dompurify": "^2.3.4",
|
"dompurify": "^2.3.4",
|
||||||
"hotkeys-js": "^3.8.7",
|
"hotkeys-js": "^3.8.7",
|
||||||
"javascript-time-ago": "^2.3.10",
|
"javascript-time-ago": "^2.3.10",
|
||||||
"mux.js": "^6.0.1",
|
"mux.js": "^6.0.1",
|
||||||
"register-service-worker": "^1.7.1",
|
|
||||||
"shaka-player": "3.3.0",
|
"shaka-player": "3.3.0",
|
||||||
"uikit": "3.9.4",
|
"stream": "^0.0.2",
|
||||||
"vue": "^3.2.26",
|
"vue": "^3.2.26",
|
||||||
"vue-i18n": "^9.2.0-beta.25",
|
"vue-i18n": "^9.2.0-beta.25",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.12",
|
||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@intlify/vue-i18n-loader": "^4.1.0",
|
"@intlify/vite-plugin-vue-i18n": "^3.2.1",
|
||||||
"@vue/cli-plugin-babel": "^4.5.15",
|
"@vitejs/plugin-vue": "^2.0.1",
|
||||||
"@vue/cli-plugin-eslint": "^4.5.15",
|
|
||||||
"@vue/cli-plugin-pwa": "^4.5.15",
|
|
||||||
"@vue/cli-service": "^4.5.15",
|
|
||||||
"@vue/compiler-sfc": "3.2.26",
|
"@vue/compiler-sfc": "3.2.26",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^7.20.0",
|
"eslint-plugin-vue": "^7.20.0",
|
||||||
"vue-cli-plugin-i18n": "^2.3.1"
|
"prettier": "^2.5.1",
|
||||||
|
"vite": "^2.7.9",
|
||||||
|
"vite-plugin-eslint": "^1.3.0",
|
||||||
|
"vite-plugin-pwa": "^0.11.12",
|
||||||
|
"vite-plugin-windicss": "^1.6.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html style="background: #0b0e0f" lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
|
||||||
<link
|
|
||||||
title="Piped"
|
|
||||||
type="application/opensearchdescription+xml"
|
|
||||||
rel="search"
|
|
||||||
href="<%= BASE_URL %>opensearch.xml"
|
|
||||||
/>
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
||||||
<meta property="og:title" content="Piped" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:image" content="<%= BASE_URL %>img/icons/favicon-32x32.png" />
|
|
||||||
<meta property="og:url" content="<%= BASE_URL %>" />
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="An alternative privacy-friendly YouTube frontend which is efficient by design."
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong style="color: #fff"
|
|
||||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript
|
|
||||||
enabled. Please enable it to continue.</strong
|
|
||||||
>
|
|
||||||
</noscript>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
139
src/App.vue
139
src/App.vue
@ -1,9 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="w-full min-h-screen px-1vw reset" :class="[theme]">
|
||||||
class="uk-container uk-container-expand uk-height-viewport"
|
|
||||||
:style="[{ background: backgroundColor, colour: foregroundColor }]"
|
|
||||||
:class="{ 'uk-light': darkMode }"
|
|
||||||
>
|
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive :max="5">
|
<keep-alive :max="5">
|
||||||
@ -11,21 +7,20 @@
|
|||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
|
|
||||||
<div style="text-align: center">
|
<footer class="text-center">
|
||||||
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped">
|
<a aria-label="GitHub" href="https://github.com/TeamPiped/Piped">
|
||||||
<font-awesome-icon :icon="['fab', 'github']"></font-awesome-icon>
|
<font-awesome-icon :icon="['fab', 'github']" />
|
||||||
</a>
|
</a>
|
||||||
|
<a class="ml-2" href="https://github.com/TeamPiped/Piped#donations">
|
||||||
<a href="https://github.com/TeamPiped/Piped#donations">
|
<font-awesome-icon :icon="['fab', 'bitcoin']" />
|
||||||
<font-awesome-icon :icon="['fab', 'bitcoin']"></font-awesome-icon>
|
<span v-text="$t('actions.donations')" />
|
||||||
{{ $t("actions.donations") }}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Navigation from "@/components/Navigation";
|
import Navigation from "@/components/Navigation.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Navigation,
|
Navigation,
|
||||||
@ -64,15 +59,15 @@ export default {
|
|||||||
(async function () {
|
(async function () {
|
||||||
const locale = App.getPreferenceString("hl", App.defaultLangage);
|
const locale = App.getPreferenceString("hl", App.defaultLangage);
|
||||||
if (locale !== App.TimeAgoConfig.locale) {
|
if (locale !== App.TimeAgoConfig.locale) {
|
||||||
const localeTime = await import("javascript-time-ago/locale/" + locale + ".json").then(
|
const localeTime = await import(
|
||||||
module => module.default,
|
"./../node_modules/javascript-time-ago/locale/" + locale + ".json"
|
||||||
);
|
).then(module => module.default);
|
||||||
App.TimeAgo.addLocale(localeTime);
|
App.TimeAgo.addLocale(localeTime);
|
||||||
App.TimeAgoConfig.locale = locale;
|
App.TimeAgoConfig.locale = locale;
|
||||||
}
|
}
|
||||||
if (window.i18n.global.locale.value !== locale) {
|
if (window.i18n.global.locale.value !== locale) {
|
||||||
if (!window.i18n.global.availableLocales.includes(locale)) {
|
if (!window.i18n.global.availableLocales.includes(locale)) {
|
||||||
const messages = await import("@/locales/" + locale + ".json").then(module => module.default);
|
const messages = await import(`./locales/${locale}.json`).then(module => module.default);
|
||||||
window.i18n.global.setLocaleMessage(locale, messages);
|
window.i18n.global.setLocaleMessage(locale, messages);
|
||||||
}
|
}
|
||||||
window.i18n.global.locale.value = locale;
|
window.i18n.global.locale.value = locale;
|
||||||
@ -93,7 +88,6 @@ b {
|
|||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
background-color: #15191a;
|
background-color: #15191a;
|
||||||
color: #c5bcae;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
@ -114,13 +108,114 @@ b {
|
|||||||
|
|
||||||
* {
|
* {
|
||||||
scrollbar-color: #15191a #444a4e;
|
scrollbar-color: #15191a #444a4e;
|
||||||
|
@apply font-sans;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uk-grid > div {
|
.video-grid {
|
||||||
padding-bottom: 1vh;
|
@apply grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 col-auto <md:gap-x-2.5 md:gap-x-1vw gap-y-1.5 mx-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uk-button {
|
.btn {
|
||||||
background: #222;
|
@apply py-2 px-4 rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset {
|
||||||
|
@apply text-black bg-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto {
|
||||||
|
@apply dark:(text-white bg-dark-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
@apply text-white bg-dark-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.select,
|
||||||
|
.btn {
|
||||||
|
@apply w-auto text-gray-600 bg-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.select {
|
||||||
|
@apply h-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
@apply h-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
@apply h-4 w-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .input,
|
||||||
|
.dark .select,
|
||||||
|
.dark .btn {
|
||||||
|
@apply text-gray-400 bg-dark-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto .input,
|
||||||
|
.auto .select,
|
||||||
|
.auto .btn {
|
||||||
|
@apply dark:(text-gray-400 bg-dark-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
@apply pl-2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
@apply !mt-2 !mb-3 border-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark hr {
|
||||||
|
@apply border-dark-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto hr {
|
||||||
|
@apply dark:border-dark-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
@apply m-0 font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply !text-5xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@apply !text-3xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
@apply w-full text-lg text-left font-light border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
@apply hover:(text-dark-300 underline underline-dark-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-secondary {
|
||||||
|
@apply hover:(text-dark-400 underline underline-dark-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .link {
|
||||||
|
@apply hover:(text-gray-300 underline underline-gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto .link {
|
||||||
|
@apply dark:hover:(text-gray-300 underline underline-gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .link-secondary {
|
||||||
|
@apply text-gray-300 hover:(text-gray-400 underline underline-gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto .link-secondary {
|
||||||
|
@apply dark:(text-gray-300 hover:(text-gray-400 underline underline-gray-400));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -2,27 +2,34 @@
|
|||||||
<ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
|
<ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
|
||||||
|
|
||||||
<div v-if="channel" v-show="!channel.error">
|
<div v-if="channel" v-show="!channel.error">
|
||||||
<h1 class="uk-text-center">
|
<div class="flex justify-center place-items-center">
|
||||||
<img height="48" width="48" class="uk-border-circle" :src="channel.avatarUrl" />{{ channel.name }}
|
<img height="48" width="48" class="rounded-full m-1" :src="channel.avatarUrl" />
|
||||||
</h1>
|
<h1 v-text="channel.name" />
|
||||||
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" style="width: 100%" loading="lazy" />
|
</div>
|
||||||
|
<img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<p style="white-space: pre-wrap"><span v-html="purifyHTML(urlify(channel.description))"></span></p>
|
<p class="whitespace-pre-wrap">
|
||||||
|
<span v-html="purifyHTML(urlify(channel.description))" />
|
||||||
|
</p>
|
||||||
|
|
||||||
<button v-if="authenticated" class="uk-button uk-button-small" type="button" @click="subscribeHandler">
|
<button
|
||||||
{{ subscribed ? $t("actions.unsubscribe") : $t("actions.subscribe") }}
|
v-if="authenticated"
|
||||||
</button>
|
class="btn"
|
||||||
|
@click="subscribeHandler"
|
||||||
|
v-text="$t(`actions.${subscribed ? 'unsubscribe' : 'subscribe'}`)"
|
||||||
|
/>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="uk-grid uk-grid-xl">
|
<div class="video-grid">
|
||||||
<div
|
<VideoItem
|
||||||
v-for="video in channel.relatedStreams"
|
v-for="video in channel.relatedStreams"
|
||||||
:key="video.url"
|
:key="video.url"
|
||||||
class="uk-width-1-2 uk-width-1-3@m uk-width-1-4@l uk-width-1-5@xl"
|
:video="video"
|
||||||
>
|
height="94"
|
||||||
<VideoItem :video="video" height="94" width="168" hide-channel />
|
width="168"
|
||||||
</div>
|
hide-channel
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,67 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="comment uk-flex">
|
<div class="comment flex mt-1.5">
|
||||||
<img
|
<img
|
||||||
:src="comment.thumbnail"
|
:src="comment.thumbnail"
|
||||||
class="comment-avatar uk-border-circle uk-margin-right"
|
class="comment-avatar rounded-full w-12 h-12"
|
||||||
height="48"
|
height="48"
|
||||||
width="48"
|
width="48"
|
||||||
style="width: 48px; height: 48px"
|
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="comment-content">
|
<div class="comment-content pl-2">
|
||||||
<div class="comment-header">
|
<div class="comment-header">
|
||||||
<div v-if="comment.pinned" class="comment-pinned uk-text-meta">
|
<div v-if="comment.pinned" class="comment-pinned">
|
||||||
<font-awesome-icon icon="thumbtack"></font-awesome-icon> {{ $t("comment.pinned_by") }}
|
<font-awesome-icon icon="thumbtack" />
|
||||||
{{ uploader }}
|
<span class="ml-1.5" v-text="$t('comment.pinned_by')" />
|
||||||
|
<span v-text="uploader" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="comment-author">
|
<div class="comment-author">
|
||||||
<router-link class="uk-text-bold uk-text-small" :to="comment.commentorUrl">
|
<router-link class="font-bold link" :to="comment.commentorUrl" v-text="comment.author" />
|
||||||
{{ comment.author }} </router-link
|
<font-awesome-icon class="ml-1.5" v-if="comment.verified" icon="check" />
|
||||||
> <font-awesome-icon v-if="comment.verified" icon="check"></font-awesome-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-meta uk-text-meta uk-margin-small-bottom">
|
<div class="comment-meta text-sm mb-1.5" v-text="comment.commentedTime" />
|
||||||
{{ comment.commentedTime }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="whitespace-pre-wrap" v-text="comment.commentText" />
|
||||||
<div class="comment-body" style="white-space: pre-wrap">
|
<div class="comment-footer mt-1">
|
||||||
{{ comment.commentText }}
|
<font-awesome-icon icon="thumbs-up" />
|
||||||
</div>
|
<span class="ml-1" v-text="numberFormat(comment.likeCount)" />
|
||||||
<div class="comment-footer uk-margin-small-top uk-text-meta">
|
<font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
|
||||||
<font-awesome-icon icon="thumbs-up" style="margin-right: 4px"></font-awesome-icon>
|
|
||||||
<span>{{ numberFormat(comment.likeCount) }}</span>
|
|
||||||
|
|
||||||
<font-awesome-icon v-if="comment.hearted" icon="heart"></font-awesome-icon>
|
|
||||||
</div>
|
</div>
|
||||||
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
|
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
|
||||||
<div @click="loadReplies">
|
<div @click="loadReplies">
|
||||||
<a class="uk-link-text" v-t="'actions.show_replies'" />
|
<a v-t="'actions.show_replies'" />
|
||||||
|
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
|
||||||
<font-awesome-icon icon="level-down-alt" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="showingReplies">
|
<template v-if="showingReplies">
|
||||||
<div @click="hideReplies">
|
<div @click="hideReplies">
|
||||||
<a class="uk-link-text" v-t="'actions.hide_replies'" />
|
<a v-t="'actions.hide_replies'" />
|
||||||
|
<font-awesome-icon class="ml-1.5" icon="level-up-alt" />
|
||||||
<font-awesome-icon icon="level-up-alt" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-show="showingReplies" v-if="replies" class="replies uk-width-4-5@xl uk-width-3-4@s uk-width-1">
|
<div v-show="showingReplies" v-if="replies" class="replies">
|
||||||
<div
|
<div v-for="reply in replies" :key="reply.commentId" class="w-full">
|
||||||
v-for="reply in replies"
|
|
||||||
:key="reply.commentId"
|
|
||||||
class="uk-tile-default uk-align-left uk-width-expand"
|
|
||||||
:style="[{ background: backgroundColor }]"
|
|
||||||
>
|
|
||||||
<Comment :comment="reply" :uploader="uploader" :video-id="videoId" />
|
<Comment :comment="reply" :uploader="uploader" :video-id="videoId" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="nextpage" @click="loadReplies">
|
<div v-if="nextpage" @click="loadReplies">
|
||||||
<a class="uk-link-text" v-t="'actions.load_more_replies'" />
|
<a v-t="'actions.load_more_replies'" />
|
||||||
|
<font-awesome-icon class="ml-1.5" icon="level-down-alt" />
|
||||||
<font-awesome-icon icon="level-down-alt" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<p>{{ message }}</p>
|
<p v-text="message" />
|
||||||
<button @click="toggleTrace" class="uk-button uk-button-small" type="button">
|
<button @click="toggleTrace" class="btn" v-text="$t('actions.show_more')" />
|
||||||
{{ $t("actions.show_more") }}
|
<p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
|
||||||
</button>
|
|
||||||
<p ref="stacktrace" style="white-space: pre-wrap" hidden>{{ error }}</p>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,50 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 v-t="'titles.feed'" class="uk-text-bold uk-text-center" />
|
<h1 v-t="'titles.feed'" class="font-bold text-center" />
|
||||||
|
|
||||||
<button
|
<button v-if="authenticated" class="btn mr-2" @click="exportHandler">
|
||||||
v-if="authenticated"
|
|
||||||
class="uk-button uk-button-small"
|
|
||||||
style="margin-right: 0.5rem"
|
|
||||||
type="button"
|
|
||||||
@click="exportHandler"
|
|
||||||
>
|
|
||||||
<router-link to="/subscriptions">Subscriptions</router-link>
|
<router-link to="/subscriptions">Subscriptions</router-link>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<a :href="getRssUrl"><font-awesome-icon icon="rss" style="padding-top: 0.2rem"></font-awesome-icon></a>
|
<a :href="getRssUrl">
|
||||||
|
<font-awesome-icon icon="rss" />
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="uk-align-right@m">
|
<span class="md:float-right">
|
||||||
<label for="ddlSortBy">{{ $t("actions.sort_by") }}</label>
|
<Sorting by-key="uploaded" @apply="order => videos.sort(order)" />
|
||||||
<select id="ddlSortBy" v-model="selectedSort" class="uk-select uk-width-auto" @change="onChange()">
|
|
||||||
<option v-t="'actions.most_recent'" value="descending" />
|
|
||||||
<option v-t="'actions.least_recent'" value="ascending" />
|
|
||||||
<option v-t="'actions.channel_name_asc'" value="channel_ascending" />
|
|
||||||
<option v-t="'actions.channel_name_desc'" value="channel_descending" />
|
|
||||||
</select>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="uk-grid uk-grid-xl">
|
<div class="video-grid">
|
||||||
<div
|
<VideoItem v-for="video in videos" :key="video.url" :video="video" />
|
||||||
v-for="video in videos"
|
|
||||||
:key="video.url"
|
|
||||||
:style="[{ background: backgroundColor }]"
|
|
||||||
class="uk-width-1-1 uk-width-1-3@s uk-width-1-4@m uk-width-1-5@l uk-width-1-6@xl"
|
|
||||||
>
|
|
||||||
<VideoItem :video="video" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VideoItem from "@/components/VideoItem.vue";
|
import VideoItem from "@/components/VideoItem.vue";
|
||||||
|
import Sorting from "@/components/Sorting.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
VideoItem,
|
VideoItem,
|
||||||
|
Sorting,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -52,7 +37,6 @@ export default {
|
|||||||
videoStep: 100,
|
videoStep: 100,
|
||||||
videosStore: [],
|
videosStore: [],
|
||||||
videos: [],
|
videos: [],
|
||||||
selectedSort: "descending",
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -84,22 +68,6 @@ export default {
|
|||||||
authToken: this.getAuthToken(),
|
authToken: this.getAuthToken(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onChange() {
|
|
||||||
switch (this.selectedSort) {
|
|
||||||
case "ascending":
|
|
||||||
this.videos.sort((a, b) => a.uploaded - b.uploaded);
|
|
||||||
break;
|
|
||||||
case "descending":
|
|
||||||
this.videos.sort((a, b) => b.uploaded - a.uploaded);
|
|
||||||
break;
|
|
||||||
case "channel_ascending":
|
|
||||||
this.videos.sort((a, b) => a.uploaderName.localeCompare(b.uploaderName));
|
|
||||||
break;
|
|
||||||
case "channel_descending":
|
|
||||||
this.videos.sort((a, b) => b.uploaderName.localeCompare(a.uploaderName));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadMoreVideos() {
|
loadMoreVideos() {
|
||||||
this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
|
this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
|
||||||
if (this.videos.length != this.videosStore.length)
|
if (this.videos.length != this.videosStore.length)
|
||||||
|
@ -1,31 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 class="uk-text-bold uk-text-center">{{ $t("titles.history") }}</h1>
|
<h1 class="font-bold text-center" v-text="$t('titles.history')" />
|
||||||
|
|
||||||
<div style="text-align: left">
|
<div class="flex">
|
||||||
<button class="uk-button" v-t="'actions.clear_history'" @click="clearHistory"></button>
|
<div>
|
||||||
|
<button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: right">
|
<div class="right-1">
|
||||||
<label for="ddlSortBy">{{ $t("actions.sort_by") }}</label>
|
<Sorting by-key="watchedAt" @apply="order => videos.sort(order)" />
|
||||||
<select id="ddlSortBy" v-model="selectedSort" class="uk-select uk-width-auto" @change="onChange()">
|
</div>
|
||||||
<option v-t="'actions.most_recent'" value="descending" />
|
|
||||||
<option v-t="'actions.least_recent'" value="ascending" />
|
|
||||||
<option v-t="'actions.channel_name_asc'" value="channel_ascending" />
|
|
||||||
<option v-t="'actions.channel_name_desc'" value="channel_descending" />
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="uk-grid uk-grid-xl">
|
<div class="video-grid">
|
||||||
<div
|
<VideoItem v-for="video in videos" :key="video.url" :video="video" />
|
||||||
v-for="video in videos"
|
|
||||||
:key="video.url"
|
|
||||||
:style="[{ background: backgroundColor }]"
|
|
||||||
class="uk-width-1-2 uk-width-1-3@s uk-width-1-4@m uk-width-1-5@l uk-width-1-6@xl"
|
|
||||||
>
|
|
||||||
<VideoItem :video="video" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
@ -33,15 +22,16 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VideoItem from "@/components/VideoItem.vue";
|
import VideoItem from "@/components/VideoItem.vue";
|
||||||
|
import Sorting from "@/components/Sorting.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
VideoItem,
|
VideoItem,
|
||||||
|
Sorting,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
videos: [],
|
videos: [],
|
||||||
selectedSort: "descending",
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -74,22 +64,6 @@ export default {
|
|||||||
document.title = "Watch History - Piped";
|
document.title = "Watch History - Piped";
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange() {
|
|
||||||
switch (this.selectedSort) {
|
|
||||||
case "ascending":
|
|
||||||
this.videos.sort((a, b) => a.watchedAt - b.watchedAt);
|
|
||||||
break;
|
|
||||||
case "descending":
|
|
||||||
this.videos.sort((a, b) => b.watchedAt - a.watchedAt);
|
|
||||||
break;
|
|
||||||
case "channel_ascending":
|
|
||||||
this.videos.sort((a, b) => a.uploaderName.localeCompare(b.uploaderName));
|
|
||||||
break;
|
|
||||||
case "channel_descending":
|
|
||||||
this.videos.sort((a, b) => b.uploaderName.localeCompare(a.uploaderName));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearHistory() {
|
clearHistory() {
|
||||||
if (window.db) {
|
if (window.db) {
|
||||||
var tx = window.db.transaction("watch_history", "readwrite");
|
var tx = window.db.transaction("watch_history", "readwrite");
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="uk-vertical-align uk-text-center uk-height-1-1">
|
<div class="text-center">
|
||||||
<form class="uk-panel uk-panel-box">
|
<form>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<input ref="fileSelector" type="file" @change="fileChange" />
|
<input ref="fileSelector" type="file" @change="fileChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<b>Selected Subscriptions: {{ selectedSubscriptions }}</b>
|
<strong v-text="`Selected Subscriptions: ${selectedSubscriptions}`" />
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<b>Override: <input v-model="override" class="uk-checkbox" type="checkbox"/></b>
|
<strong>Override: <input v-model="override" class="checkbox" type="checkbox" /></strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<a class="uk-width-1-1 uk-button uk-button-large uk-width-auto" @click="handleImport">Import</a>
|
<a class="btn w-auto" @click="handleImport">Import</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<br />
|
<br />
|
||||||
<b>Importing Subscriptions from YouTube</b>
|
<strong>Importing Subscriptions from YouTube</strong>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
Open
|
Open
|
||||||
@ -30,7 +30,7 @@
|
|||||||
Select and import the file above.
|
Select and import the file above.
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<b>Importing Subscriptions from Invidious</b>
|
<strong>Importing Subscriptions from Invidious</strong>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
Open
|
Open
|
||||||
@ -41,7 +41,7 @@
|
|||||||
Select and import the file above.
|
Select and import the file above.
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<b>Importing Subscriptions from NewPipe</b>
|
<strong>Importing Subscriptions from NewPipe</strong>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
Go to the Feed tab.
|
Go to the Feed tab.
|
||||||
|
@ -1,30 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="uk-vertical-align uk-text-center uk-height-1-1">
|
<div class="text-center">
|
||||||
<form class="uk-panel uk-panel-box">
|
<h1 v-t="'titles.login'" />
|
||||||
<div class="uk-form-row">
|
<form class="children:pb-3">
|
||||||
|
<div>
|
||||||
<input
|
<input
|
||||||
v-model="username"
|
v-model="username"
|
||||||
class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
:placeholder="$t('login.username')"
|
:placeholder="$t('login.username')"
|
||||||
:aria-label="$t('login.username')"
|
:aria-label="$t('login.username')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<input
|
<input
|
||||||
v-model="password"
|
v-model="password"
|
||||||
class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
|
class="input"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="password"
|
autocomplete="password"
|
||||||
:placeholder="$t('login.password')"
|
:placeholder="$t('login.password')"
|
||||||
:aria-label="$t('login.password')"
|
:aria-label="$t('login.password')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<a class="uk-width-1-1 uk-button uk-button-large uk-width-auto" @click="login">
|
<a class="btn w-auto" @click="login" v-text="$t('titles.login')" />
|
||||||
{{ $t("titles.login") }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav
|
<nav class="flex flex-wrap items-center justify-center px-2 sm:px-4 py-2.5 w-full relative">
|
||||||
class="uk-navbar uk-navbar-container uk-container-expand uk-position-relative"
|
<div class="flex-1">
|
||||||
:style="[{ background: backgroundColor, colour: foregroundColor }]"
|
<router-link class="flex font-bold text-3xl items-center font-sans font-bold" to="/"
|
||||||
uk-navbar
|
|
||||||
>
|
|
||||||
<div class="uk-navbar-left">
|
|
||||||
<router-link class="uk-navbar-item uk-logo uk-text-bold" :style="[{ colour: foregroundColor }]" to="/"
|
|
||||||
><img
|
><img
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src="/img/icons/logo.svg"
|
src="/img/icons/logo.svg"
|
||||||
height="32"
|
height="32"
|
||||||
width="32"
|
width="32"
|
||||||
style="margin-bottom: 6px; margin-right: -13px"
|
class="w-10 mr-[-0.6rem]"
|
||||||
/>iped</router-link
|
/>iped</router-link
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-center uk-flex uk-visible@m">
|
<div class="<md:hidden">
|
||||||
<input
|
<input
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
class="uk-input uk-width-medium"
|
class="input !w-72 !h-10"
|
||||||
type="text"
|
type="text"
|
||||||
role="search"
|
role="search"
|
||||||
:title="$t('actions.search')"
|
:title="$t('actions.search')"
|
||||||
@ -29,8 +25,8 @@
|
|||||||
@blur="onInputBlur"
|
@blur="onInputBlur"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-navbar-right">
|
<div class="flex-1 flex justify-end">
|
||||||
<ul class="uk-navbar-nav">
|
<ul class="flex text-1xl children:pl-3">
|
||||||
<li>
|
<li>
|
||||||
<router-link v-t="'titles.preferences'" to="/preferences" />
|
<router-link v-t="'titles.preferences'" to="/preferences" />
|
||||||
</li>
|
</li>
|
||||||
@ -49,10 +45,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="uk-container-expand uk-hidden@m">
|
<div class="w-full md:hidden">
|
||||||
<input
|
<input
|
||||||
v-model="searchText"
|
v-model="searchText"
|
||||||
class="uk-input"
|
class="input !h-10 !w-full"
|
||||||
type="text"
|
type="text"
|
||||||
role="search"
|
role="search"
|
||||||
:title="$t('actions.search')"
|
:title="$t('actions.search')"
|
||||||
@ -72,7 +68,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SearchSuggestions from "@/components/SearchSuggestions";
|
import SearchSuggestions from "@/components/SearchSuggestions.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -84,6 +80,10 @@ export default {
|
|||||||
suggestionsVisible: false,
|
suggestionsVisible: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
const query = new URLSearchParams(window.location.search).get("search_query");
|
||||||
|
if (query) this.onSearchTextChange(query);
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
shouldShowLogin(_this) {
|
shouldShowLogin(_this) {
|
||||||
return _this.getAuthToken() == null;
|
return _this.getAuthToken() == null;
|
||||||
@ -121,5 +121,3 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="uk-container-expand">
|
<div class="w-full">
|
||||||
<div
|
<div
|
||||||
ref="container"
|
ref="container"
|
||||||
data-shaka-player-container
|
data-shaka-player-container
|
||||||
style="width: 100%; height: 100%; background: #000"
|
style="width: 100%; height: 100%; background: #000"
|
||||||
:style="!isEmbed ? { 'max-height': '75vh', 'min-height': '250px' } : {}"
|
:style="!isEmbed ? { 'max-height': '75vh', 'min-height': '250px' } : {}"
|
||||||
>
|
>
|
||||||
<video
|
<video ref="videoEl" data-shaka-player class="w-full" :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
|
||||||
ref="videoEl"
|
|
||||||
data-shaka-player
|
|
||||||
class="uk-width-expand"
|
|
||||||
:autoplay="shouldAutoPlay"
|
|
||||||
:loop="selectedAutoLoop"
|
|
||||||
></video>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -81,10 +75,9 @@ export default {
|
|||||||
.then(hotkeys => {
|
.then(hotkeys => {
|
||||||
this.hotkeys = hotkeys;
|
this.hotkeys = hotkeys;
|
||||||
var self = this;
|
var self = this;
|
||||||
hotkeys("f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+,,shift+.", function(
|
hotkeys(
|
||||||
e,
|
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+,,shift+.",
|
||||||
handler,
|
function (e, handler) {
|
||||||
) {
|
|
||||||
const videoEl = self.$refs.videoEl;
|
const videoEl = self.$refs.videoEl;
|
||||||
console.log(handler.key);
|
console.log(handler.key);
|
||||||
switch (handler.key) {
|
switch (handler.key) {
|
||||||
@ -177,7 +170,8 @@ export default {
|
|||||||
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
|
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deactivated() {
|
deactivated() {
|
||||||
@ -194,7 +188,25 @@ export default {
|
|||||||
videoEl.setAttribute("poster", this.video.thumbnailUrl);
|
videoEl.setAttribute("poster", this.video.thumbnailUrl);
|
||||||
|
|
||||||
if (this.$route.query.t) {
|
if (this.$route.query.t) {
|
||||||
videoEl.currentTime = this.$route.query.t;
|
const time = this.$route.query.t;
|
||||||
|
let start = 0;
|
||||||
|
if (/^[\d]*$/g.test(time)) {
|
||||||
|
start = time;
|
||||||
|
} else {
|
||||||
|
const hours = /([\d]*)h/gi.exec(time)?.[1];
|
||||||
|
const minutes = /([\d]*)m/gi.exec(time)?.[1];
|
||||||
|
const seconds = /([\d]*)s/gi.exec(time)?.[1];
|
||||||
|
if (hours) {
|
||||||
|
start += parseInt(hours) * 60 * 60;
|
||||||
|
}
|
||||||
|
if (minutes) {
|
||||||
|
start += parseInt(minutes) * 60;
|
||||||
|
}
|
||||||
|
if (seconds) {
|
||||||
|
start += parseInt(seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
videoEl.currentTime = start;
|
||||||
} else if (window.db) {
|
} else if (window.db) {
|
||||||
var tx = window.db.transaction("watch_history", "readonly");
|
var tx = window.db.transaction("watch_history", "readonly");
|
||||||
var store = tx.objectStore("watch_history");
|
var store = tx.objectStore("watch_history");
|
||||||
@ -228,10 +240,9 @@ export default {
|
|||||||
mime = "application/x-mpegURL";
|
mime = "application/x-mpegURL";
|
||||||
} else if (this.video.audioStreams.length > 0 && !lbry && MseSupport) {
|
} else if (this.video.audioStreams.length > 0 && !lbry && MseSupport) {
|
||||||
if (!this.video.dash) {
|
if (!this.video.dash) {
|
||||||
const dash = require("@/utils/DashUtils.js").default.generate_dash_file_from_formats(
|
const dash = (
|
||||||
streams,
|
await import("@/utils/DashUtils.js").then(mod => mod.default)
|
||||||
this.video.duration,
|
).generate_dash_file_from_formats(streams, this.video.duration);
|
||||||
);
|
|
||||||
|
|
||||||
uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
|
uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
|
||||||
} else uri = this.video.dash;
|
} else uri = this.video.dash;
|
||||||
|
@ -2,34 +2,36 @@
|
|||||||
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
|
<ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
|
||||||
|
|
||||||
<div v-if="playlist" v-show="!playlist.error">
|
<div v-if="playlist" v-show="!playlist.error">
|
||||||
<h1 class="uk-text-center">
|
<h1 class="text-center" v-text="playlist.name" />
|
||||||
<img :src="playlist.avatarUrl" height="48" width="48" loading="lazy" />
|
|
||||||
{{ playlist.name }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<b
|
<div class="grid grid-cols-2">
|
||||||
><router-link class="uk-text-justify" :to="playlist.uploaderUrl || '/'">
|
<div>
|
||||||
<img :src="playlist.uploaderAvatar" loading="lazy" class="uk-border-circle" />
|
<router-link class="link" :to="playlist.uploaderUrl || '/'">
|
||||||
{{ playlist.uploader }}</router-link
|
<img :src="playlist.uploaderAvatar" loading="lazy" class="rounded-full" />
|
||||||
></b
|
<strong v-text="playlist.uploader" />
|
||||||
>
|
</router-link>
|
||||||
|
</div>
|
||||||
<div class="uk-align-right">
|
<div>
|
||||||
<b>{{ playlist.videos }} {{ $t("video.videos") }}</b>
|
<div class="right-2vw absolute">
|
||||||
|
<strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
|
||||||
<br />
|
<br />
|
||||||
<a :href="getRssUrl"><font-awesome-icon icon="rss"></font-awesome-icon></a>
|
<a :href="getRssUrl">
|
||||||
|
<font-awesome-icon icon="rss" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="uk-grid uk-grid-xl">
|
<div class="video-grid">
|
||||||
<div
|
<VideoItem
|
||||||
v-for="video in playlist.relatedStreams"
|
v-for="video in playlist.relatedStreams"
|
||||||
:key="video.url"
|
:key="video.url"
|
||||||
class="uk-width-1-2 uk-width-1-3@m uk-width-1-4@l uk-width-1-5@xl"
|
:video="video"
|
||||||
>
|
height="94"
|
||||||
<VideoItem :video="video" height="94" width="168" />
|
width="168"
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,205 +1,169 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="uk-flex uk-flex-between uk-flex-middle">
|
<div class="flex">
|
||||||
<button class="uk-button uk-button-text" @click="$router.go(-1) || $router.push('/')">
|
<button @click="$router.go(-1) || $router.push('/')">
|
||||||
<font-awesome-icon icon="chevron-left" /> {{ $t("actions.back") }}
|
<font-awesome-icon icon="chevron-left" /><span class="ml-1.5" v-text="$t('actions.back')" />
|
||||||
</button>
|
</button>
|
||||||
<span><h1 v-t="'titles.preferences'" class="uk-text-bold uk-text-center"/></span>
|
|
||||||
<span />
|
|
||||||
</div>
|
</div>
|
||||||
|
<h1 v-t="'titles.preferences'" class="font-bold text-center" />
|
||||||
<hr />
|
<hr />
|
||||||
<h2>SponsorBlock</h2>
|
<h2>SponsorBlock</h2>
|
||||||
<p>{{ $t("actions.uses_api_from") }}<a href="https://sponsor.ajay.app/">sponsor.ajay.app</a></p>
|
<p>
|
||||||
<label for="chkEnableSponsorblock"><b v-t="'actions.enable_sponsorblock'"/></label>
|
<span v-text="$t('actions.uses_api_from')" /><a class="link" href="https://sponsor.ajay.app/"
|
||||||
|
>sponsor.ajay.app</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<label for="chkEnableSponsorblock"><strong v-t="'actions.enable_sponsorblock'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
id="chkEnableSponsorblock"
|
id="chkEnableSponsorblock"
|
||||||
v-model="sponsorBlock"
|
v-model="sponsorBlock"
|
||||||
class="uk-checkbox"
|
class="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipSponsors"><b v-t="'actions.skip_sponsors'"/></label>
|
<label for="chkSkipSponsors"><strong v-t="'actions.skip_sponsors'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkSkipSponsors" v-model="skipSponsor" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkSkipSponsors" v-model="skipSponsor" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipIntro"><b v-t="'actions.skip_intro'"/></label>
|
<label for="chkSkipIntro"><strong v-t="'actions.skip_intro'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkSkipIntro" v-model="skipIntro" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkSkipIntro" v-model="skipIntro" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipOutro"><b v-t="'actions.skip_outro'"/></label>
|
<label for="chkSkipOutro"><strong v-t="'actions.skip_outro'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkSkipOutro" v-model="skipOutro" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkSkipOutro" v-model="skipOutro" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipPreview"><b v-t="'actions.skip_preview'"/></label>
|
<label for="chkSkipPreview"><strong v-t="'actions.skip_preview'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkSkipPreview" v-model="skipPreview" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkSkipPreview" v-model="skipPreview" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipInteraction"><b v-t="'actions.skip_interaction'"/></label>
|
<label for="chkSkipInteraction"><strong v-t="'actions.skip_interaction'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
id="chkSkipInteraction"
|
id="chkSkipInteraction"
|
||||||
v-model="skipInteraction"
|
v-model="skipInteraction"
|
||||||
class="uk-checkbox"
|
class="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipSelfPromo"><b v-t="'actions.skip_self_promo'"/></label>
|
<label for="chkSkipSelfPromo"><strong v-t="'actions.skip_self_promo'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input id="chkSkipSelfPromo" v-model="skipSelfPromo" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
id="chkSkipSelfPromo"
|
|
||||||
v-model="skipSelfPromo"
|
|
||||||
class="uk-checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
@change="onChange($event)"
|
|
||||||
/>
|
|
||||||
<br />
|
<br />
|
||||||
<label for="chkSkipNonMusic"><b v-t="'actions.skip_non_music'"/></label>
|
<label for="chkSkipNonMusic"><strong v-t="'actions.skip_non_music'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
id="chkSkipNonMusic"
|
id="chkSkipNonMusic"
|
||||||
v-model="skipMusicOffTopic"
|
v-model="skipMusicOffTopic"
|
||||||
class="uk-checkbox"
|
class="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="ddlTheme"><b v-t="'actions.theme'"/></label>
|
<label for="ddlTheme"><strong v-t="'actions.theme'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<select id="ddlTheme" v-model="selectedTheme" class="uk-select uk-width-auto" @change="onChange($event)">
|
<select id="ddlTheme" v-model="selectedTheme" class="select w-auto" @change="onChange($event)">
|
||||||
<option v-t="'actions.auto'" value="auto" />
|
<option v-t="'actions.auto'" value="auto" />
|
||||||
<option v-t="'actions.dark'" value="dark" />
|
<option v-t="'actions.dark'" value="dark" />
|
||||||
<option v-t="'actions.light'" value="light" />
|
<option v-t="'actions.light'" value="light" />
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<label for="chkAutoPlayVideo"><b v-t="'actions.autoplay_video'"/></label>
|
<label for="chkAutoPlayVideo"><strong v-t="'actions.autoplay_video'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input id="chkAutoPlayVideo" v-model="autoPlayVideo" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
id="chkAutoPlayVideo"
|
|
||||||
v-model="autoPlayVideo"
|
|
||||||
class="uk-checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
@change="onChange($event)"
|
|
||||||
/>
|
|
||||||
<br />
|
<br />
|
||||||
<label for="chkAudioOnly"><b v-t="'actions.audio_only'"/></label>
|
<label for="chkAudioOnly"><strong v-t="'actions.audio_only'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkAudioOnly" v-model="listen" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="ddlDefaultQuality"><b v-t="'actions.default_quality'"/></label>
|
<label for="ddlDefaultQuality"><strong v-t="'actions.default_quality'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<select id="ddlDefaultQuality" v-model="defaultQuality" class="uk-select uk-width-auto" @change="onChange($event)">
|
<select id="ddlDefaultQuality" v-model="defaultQuality" class="select w-auto" @change="onChange($event)">
|
||||||
<option v-t="'actions.auto'" value="0" />
|
<option v-t="'actions.auto'" value="0" />
|
||||||
<option v-for="resolution in resolutions" :key="resolution" :value="resolution">{{ resolution }}p</option>
|
<option v-for="resolution in resolutions" :key="resolution" :value="resolution" v-text="`${resolution}p`" />
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<label for="txtBufferingGoal"><b v-t="'actions.buffering_goal'"/></label>
|
<label for="txtBufferingGoal"><strong v-t="'actions.buffering_goal'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input id="txtBufferingGoal" v-model="bufferingGoal" class="input w-auto" type="text" @change="onChange($event)" />
|
||||||
id="txtBufferingGoal"
|
|
||||||
v-model="bufferingGoal"
|
|
||||||
class="uk-input uk-width-auto"
|
|
||||||
type="text"
|
|
||||||
@change="onChange($event)"
|
|
||||||
/>
|
|
||||||
<br />
|
<br />
|
||||||
<label for="ddlCountrySelection"><b v-t="'actions.country_selection'"/></label>
|
<label for="ddlCountrySelection"><strong v-t="'actions.country_selection'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<select
|
<select id="ddlCountrySelection" v-model="countrySelected" class="select w-auto" @change="onChange($event)">
|
||||||
id="ddlCountrySelection"
|
<option v-for="country in countryMap" :key="country.code" :value="country.code" v-text="country.name" />
|
||||||
v-model="countrySelected"
|
|
||||||
class="uk-select uk-width-auto"
|
|
||||||
@change="onChange($event)"
|
|
||||||
>
|
|
||||||
<option v-for="country in countryMap" :key="country.code" :value="country.code">{{ country.name }}</option>
|
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<label for="ddlDefaultHomepage"><b v-t="'actions.default_homepage'"/></label>
|
<label for="ddlDefaultHomepage"><strong v-t="'actions.default_homepage'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<select
|
<select id="ddlDefaultHomepage" v-model="defaultHomepage" class="select w-auto" @change="onChange($event)">
|
||||||
id="ddlDefaultHomepage"
|
|
||||||
v-model="defaultHomepage"
|
|
||||||
class="uk-select uk-width-auto"
|
|
||||||
@change="onChange($event)"
|
|
||||||
>
|
|
||||||
<option v-t="'titles.trending'" value="trending" />
|
<option v-t="'titles.trending'" value="trending" />
|
||||||
<option v-t="'titles.feed'" value="feed" />
|
<option v-t="'titles.feed'" value="feed" />
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<label for="chkShowComments"><b v-t="'actions.show_comments'"/></label>
|
<label for="chkShowComments"><strong v-t="'actions.show_comments'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkShowComments" v-model="showComments" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkShowComments" v-model="showComments" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="chkMinimizeDescription"><b v-t="'actions.minimize_description_default'"/></label>
|
<label for="chkMinimizeDescription"><strong v-t="'actions.minimize_description_default'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
id="chkMinimizeDescription"
|
id="chkMinimizeDescription"
|
||||||
v-model="minimizeDescription"
|
v-model="minimizeDescription"
|
||||||
class="uk-checkbox"
|
class="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="chkStoreWatchHistory"><b v-t="'actions.store_watch_history'"/></label>
|
<label for="chkStoreWatchHistory"><strong v-t="'actions.store_watch_history'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
id="chkStoreWatchHistory"
|
id="chkStoreWatchHistory"
|
||||||
v-model="watchHistory"
|
v-model="watchHistory"
|
||||||
class="uk-checkbox"
|
class="checkbox"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<label for="ddlLanguageSelection"><b v-t="'actions.language_selection'"/></label>
|
<label for="ddlLanguageSelection"><strong v-t="'actions.language_selection'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<select
|
<select id="ddlLanguageSelection" v-model="selectedLanguage" class="select w-auto" @change="onChange($event)">
|
||||||
id="ddlLanguageSelection"
|
<option v-for="language in languages" :key="language.code" :value="language.code" v-text="language.name" />
|
||||||
v-model="selectedLanguage"
|
|
||||||
class="uk-select uk-width-auto"
|
|
||||||
@change="onChange($event)"
|
|
||||||
>
|
|
||||||
<option v-for="language in languages" :key="language.code" :value="language.code">{{ language.name }}</option>
|
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<label for="ddlEnabledCodecs"><b v-t="'actions.enabled_codecs'"/></label>
|
<label for="ddlEnabledCodecs"><strong v-t="'actions.enabled_codecs'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<select
|
<select id="ddlEnabledCodecs" v-model="enabledCodecs" class="select w-auto" multiple @change="onChange($event)">
|
||||||
id="ddlEnabledCodecs"
|
|
||||||
v-model="enabledCodecs"
|
|
||||||
class="uk-select uk-width-auto"
|
|
||||||
multiple
|
|
||||||
@change="onChange($event)"
|
|
||||||
>
|
|
||||||
<option value="av1">AV1</option>
|
<option value="av1">AV1</option>
|
||||||
<option value="vp9">VP9</option>
|
<option value="vp9">VP9</option>
|
||||||
<option value="avc">AVC (h.264)</option>
|
<option value="avc">AVC (h.264)</option>
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<label for="chkDisableLBRY"><b v-t="'actions.disable_lbry'"/></label>
|
<label for="chkDisableLBRY"><strong v-t="'actions.disable_lbry'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkDisableLBRY" v-model="disableLBRY" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkDisableLBRY" v-model="disableLBRY" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<br />
|
<br />
|
||||||
<label for="chkEnableLBRYProxy"><b v-t="'actions.enable_lbry_proxy'"/></label>
|
<label for="chkEnableLBRYProxy"><strong v-t="'actions.enable_lbry_proxy'" /></label>
|
||||||
<br />
|
<br />
|
||||||
<input id="chkEnableLBRYProxy" v-model="proxyLBRY" class="uk-checkbox" type="checkbox" @change="onChange($event)" />
|
<input id="chkEnableLBRYProxy" v-model="proxyLBRY" class="checkbox" type="checkbox" @change="onChange($event)" />
|
||||||
<h2 v-t="'actions.instances_list'" />
|
<h2 v-t="'actions.instances_list'" />
|
||||||
<table class="uk-table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t("preferences.instance_name") }}</th>
|
<th v-text="$t('preferences.instance_name')" />
|
||||||
<th>{{ $t("preferences.instance_locations") }}</th>
|
<th v-text="$t('preferences.instance_locations')" />
|
||||||
<th>{{ $t("preferences.has_cdn") }}</th>
|
<th v-text="$t('preferences.has_cdn')" />
|
||||||
<th>{{ $t("preferences.ssl_score") }}</th>
|
<th v-text="$t('preferences.ssl_score')" />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody v-for="instance in instances" :key="instance.name">
|
<tbody v-for="instance in instances" :key="instance.name">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ instance.name }}</td>
|
<td v-text="instance.name" />
|
||||||
<td>{{ instance.locations }}</td>
|
<td v-text="instance.locations" />
|
||||||
<td>{{ instance.cdn == "Yes" ? $t("actions.yes") : $t("actions.no") }}</td>
|
<td v-text="$t(`actions.${instance.cdn === 'Yes' ? 'yes' : 'no'}`)" />
|
||||||
<td>
|
<td>
|
||||||
<a :href="sslScore(instance.apiurl)" target="_blank"> {{ $t("actions.view_ssl_score") }}</a>
|
<a :href="sslScore(instance.apiurl)" target="_blank" v-text="$t('actions.view_ssl_score')" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -207,19 +171,10 @@
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<label for="ddlInstanceSelection"
|
<label for="ddlInstanceSelection"><strong v-text="`${$t('actions.instance_selection')}:`" /></label>
|
||||||
><b>{{ $t("actions.instance_selection") }}:</b></label
|
|
||||||
>
|
|
||||||
<br />
|
<br />
|
||||||
<select
|
<select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
|
||||||
id="ddlInstanceSelection"
|
<option v-for="instance in instances" :key="instance.name" :value="instance.apiurl" v-text="instance.name" />
|
||||||
v-model="selectedInstance"
|
|
||||||
class="uk-select uk-width-auto"
|
|
||||||
@change="onChange($event)"
|
|
||||||
>
|
|
||||||
<option v-for="instance in instances" :key="instance.name" :value="instance.apiurl">
|
|
||||||
{{ instance.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -326,7 +281,14 @@ export default {
|
|||||||
this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true);
|
this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true);
|
||||||
if (localStorage.getItem("selectedSkip") !== null) {
|
if (localStorage.getItem("selectedSkip") !== null) {
|
||||||
var skipList = localStorage.getItem("selectedSkip").split(",");
|
var skipList = localStorage.getItem("selectedSkip").split(",");
|
||||||
this.skipSponsor = this.skipIntro = this.skipOutro = this.skipPreview = this.skipInteraction = this.skipSelfPromo = this.skipMusicOffTopic = false;
|
this.skipSponsor =
|
||||||
|
this.skipIntro =
|
||||||
|
this.skipOutro =
|
||||||
|
this.skipPreview =
|
||||||
|
this.skipInteraction =
|
||||||
|
this.skipSelfPromo =
|
||||||
|
this.skipMusicOffTopic =
|
||||||
|
false;
|
||||||
skipList.forEach(skip => {
|
skipList.forEach(skip => {
|
||||||
switch (skip) {
|
switch (skip) {
|
||||||
case "sponsor":
|
case "sponsor":
|
||||||
@ -373,10 +335,8 @@ export default {
|
|||||||
this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
|
this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
|
||||||
if (this.selectedLanguage != "en") {
|
if (this.selectedLanguage != "en") {
|
||||||
try {
|
try {
|
||||||
this.CountryMap = await import("@/utils/CountryMaps/" + this.selectedLanguage + ".json").then(
|
this.CountryMap = await import(`../utils/CountryMaps/${this.selectedLanguage}.json`).then(
|
||||||
val => {
|
val => val.default,
|
||||||
this.countryMap = val;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Countries not translated into " + this.selectedLanguage);
|
console.error("Countries not translated into " + this.selectedLanguage);
|
||||||
|
@ -1,30 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="uk-vertical-align uk-text-center uk-height-1-1">
|
<div class="text-center">
|
||||||
<form class="uk-panel uk-panel-box">
|
<form class="children:pb-3">
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<input
|
<input
|
||||||
v-model="username"
|
v-model="username"
|
||||||
class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
:placeholder="$t('login.username')"
|
:placeholder="$t('login.username')"
|
||||||
:aria-label="$t('login.username')"
|
:aria-label="$t('login.username')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<input
|
<input
|
||||||
v-model="password"
|
v-model="password"
|
||||||
class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
|
class="input"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="password"
|
autocomplete="password"
|
||||||
:placeholder="$t('login.password')"
|
:placeholder="$t('login.password')"
|
||||||
:aria-label="$t('login.password')"
|
:aria-label="$t('login.password')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-form-row">
|
<div>
|
||||||
<a class="uk-width-1-1 uk-button uk-button-large uk-width-auto" @click="register">
|
<a class="btn w-auto" @click="register" v-text="$t('titles.register')" />
|
||||||
{{ $t("titles.register") }}</a
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,69 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 class="uk-text-center">
|
<h1 class="text-center" v-text="$route.query.search_query" />
|
||||||
{{ $route.query.search_query }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<label for="ddlSearchFilters"
|
<label for="ddlSearchFilters">
|
||||||
><b>{{ $t("actions.filter") }}: </b></label
|
<strong v-text="`${$t('actions.filter')}:`" />
|
||||||
>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="ddlSearchFilters"
|
id="ddlSearchFilters"
|
||||||
v-model="selectedFilter"
|
v-model="selectedFilter"
|
||||||
default="all"
|
default="all"
|
||||||
class="uk-select uk-width-auto"
|
class="select w-auto"
|
||||||
style="height: 100%"
|
|
||||||
@change="updateResults()"
|
@change="updateResults()"
|
||||||
>
|
>
|
||||||
<option v-for="filter in availableFilters" :key="filter" :value="filter">
|
<option v-for="filter in availableFilters" :key="filter" :value="filter" v-text="filter.replace('_', ' ')" />
|
||||||
{{ filter.replace("_", " ") }}
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div v-if="results && results.corrected" style="height: 7vh">
|
<div v-if="results && results.corrected" style="height: 7vh">
|
||||||
{{ $t("search.did_you_mean") }}
|
<span v-text="$t('search.did_you_mean')" />
|
||||||
<i>
|
|
||||||
<router-link :to="{ name: 'SearchResults', query: { search_query: results.suggestion } }">
|
<router-link :to="{ name: 'SearchResults', query: { search_query: results.suggestion } }">
|
||||||
{{ results.suggestion }}
|
<em v-text="results.suggestion" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</i>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="results" class="uk-grid uk-grid-xl">
|
<div v-if="results" class="video-grid">
|
||||||
<div
|
<div v-for="result in results.items" :key="result.url">
|
||||||
v-for="result in results.items"
|
|
||||||
:key="result.url"
|
|
||||||
:style="[{ background: backgroundColor }]"
|
|
||||||
class="uk-width-1-2 uk-width-1-3@s uk-width-1-4@m uk-width-1-5@l uk-width-1-6@xl"
|
|
||||||
>
|
|
||||||
<VideoItem v-if="shouldUseVideoItem(result)" :video="result" height="94" width="168" />
|
<VideoItem v-if="shouldUseVideoItem(result)" :video="result" height="94" width="168" />
|
||||||
<div v-if="!shouldUseVideoItem(result)" class="uk-text-secondary">
|
<div v-if="!shouldUseVideoItem(result)">
|
||||||
<router-link class="uk-text-emphasis" :to="result.url">
|
<router-link :to="result.url">
|
||||||
<div class="uk-position-relative">
|
<div class="relative">
|
||||||
<img style="width: 100%" :src="result.thumbnail" loading="lazy" />
|
<img class="w-full" :src="result.thumbnail" loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
{{ result.name }} <font-awesome-icon
|
<span v-text="result.name" />
|
||||||
v-if="result.verified"
|
<font-awesome-icon class="ml-1.5" v-if="result.verified" icon="check" />
|
||||||
icon="check"
|
|
||||||
></font-awesome-icon>
|
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
<p v-if="result.description">{{ result.description }}</p>
|
<p v-if="result.description" v-text="result.description" />
|
||||||
<router-link v-if="result.uploaderUrl" class="uk-link-muted" :to="result.uploaderUrl">
|
<router-link v-if="result.uploaderUrl" class="link" :to="result.uploaderUrl">
|
||||||
<p>
|
<p>
|
||||||
{{ result.uploader }} <font-awesome-icon
|
<span v-text="result.uploader" />
|
||||||
v-if="result.uploaderVerified"
|
<font-awesome-icon class="ml-1.5" v-if="result.uploaderVerified" icon="check" />
|
||||||
icon="check"
|
|
||||||
></font-awesome-icon>
|
|
||||||
</p>
|
</p>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<a v-if="result.uploaderName" class="uk-text-muted">{{ result.uploaderName }}</a>
|
<a v-if="result.uploaderName" class="link" v-text="result.uploaderName" />
|
||||||
<b v-if="result.videos >= 0"
|
<template v-if="result.videos >= 0">
|
||||||
><br v-if="result.uploaderName" />{{ result.videos }} {{ $t("video.videos") }}</b
|
<br v-if="result.uploaderName" />
|
||||||
>
|
<strong v-text="`${result.videos} ${$t('video.videos')}`" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="absolute suggestions-container">
|
||||||
class="uk-position-absolute uk-panel uk-box-shadow-large suggestions-container"
|
<ul>
|
||||||
:style="[{ background: secondaryBackgroundColor }]"
|
|
||||||
>
|
|
||||||
<ul class="uk-list uk-margin-remove uk-text-secondary">
|
|
||||||
<li
|
<li
|
||||||
v-for="(suggestion, i) in searchSuggestions"
|
v-for="(suggestion, i) in searchSuggestions"
|
||||||
:key="i"
|
:key="i"
|
||||||
:style="[selected === i ? { background: secondaryForegroundColor } : {}]"
|
class="suggestion"
|
||||||
class="uk-margin-remove suggestion"
|
:class="{ 'suggestion-selected': selected === i }"
|
||||||
@mouseover="onMouseOver(i)"
|
@mouseover="onMouseOver(i)"
|
||||||
@mousedown.stop="onClick(i)"
|
@mousedown.stop="onClick(i)"
|
||||||
>
|
v-text="suggestion"
|
||||||
{{ suggestion }}
|
/>
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -79,25 +75,30 @@ export default {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.suggestions-container {
|
.suggestions-container {
|
||||||
left: 50%;
|
@apply left-1/2 translate-x-[-50%] transform-gpu max-w-3xl w-full box-border p-y-1.25 z-10 <md:max-w-[calc(100%-0.5rem)] bg-gray-300;
|
||||||
transform: translateX(-50%);
|
|
||||||
max-width: 640px;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 5px 0;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .suggestions-container {
|
||||||
|
@apply bg-dark-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto .suggestions-container {
|
||||||
|
@apply dark:bg-dark-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-selected {
|
||||||
|
@apply bg-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .suggestion-selected {
|
||||||
|
@apply bg-dark-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto .suggestion-selected {
|
||||||
|
@apply dark:bg-dark-100;
|
||||||
|
}
|
||||||
|
|
||||||
.suggestion {
|
.suggestion {
|
||||||
padding: 4px 15px;
|
@apply p-y-1;
|
||||||
}
|
|
||||||
@media screen and (max-width: 959px) {
|
|
||||||
.suggestions-container {
|
|
||||||
max-width: calc(100% - 60px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 639px) {
|
|
||||||
.suggestions-container {
|
|
||||||
max-width: calc(100% - 30px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
44
src/components/Sorting.vue
Normal file
44
src/components/Sorting.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<label for="ddlSortBy" v-text="$t('actions.sort_by')" />
|
||||||
|
<select id="ddlSortBy" v-model="selectedSort" class="select w-auto">
|
||||||
|
<option v-for="(value, key) in options" v-t="`actions.${key}`" :key="key" :value="value" />
|
||||||
|
</select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { defineEmits, defineProps, ref, watch } from "vue";
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
most_recent: "descending",
|
||||||
|
least_recent: "ascending",
|
||||||
|
channel_name_asc: "channel_ascending",
|
||||||
|
channel_name_desc: "channel_descending",
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedSort = ref("descending");
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
byKey: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["apply"]);
|
||||||
|
|
||||||
|
watch(selectedSort, value => {
|
||||||
|
switch (value) {
|
||||||
|
case "ascending":
|
||||||
|
emit("apply", (a, b) => a[props.byKey] - b[props.byKey]);
|
||||||
|
break;
|
||||||
|
case "descending":
|
||||||
|
emit("apply", (a, b) => b[props.byKey] - a[props.byKey]);
|
||||||
|
break;
|
||||||
|
case "channel_ascending":
|
||||||
|
emit("apply", (a, b) => a.uploaderName.localeCompare(b.uploaderName));
|
||||||
|
break;
|
||||||
|
case "channel_descending":
|
||||||
|
emit("apply", (a, b) => b.uploaderName.localeCompare(a.uploaderName));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error("Unexpected sort value");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -1,45 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 class="uk-text-bold uk-text-center">{{ $t("titles.subscriptions") }}</h1>
|
<h1 class="font-bold text-center" v-text="$t('titles.subscriptions')" />
|
||||||
|
|
||||||
<div style="text-align: center">
|
<div v-if="authenticated">
|
||||||
<button v-if="authenticated" class="uk-button uk-button-small" style=" margin-right: 0.5rem" type="button">
|
<button class="btn mr-0.5">
|
||||||
<router-link to="/import">
|
<router-link to="/import" v-text="$t('actions.import_from_json')" />
|
||||||
{{ $t("actions.import_from_json") }}
|
|
||||||
</router-link>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="btn" @click="exportHandler" v-text="$t('actions.export_to_json')" />
|
||||||
v-if="authenticated"
|
|
||||||
class="uk-button uk-button-small"
|
|
||||||
style="color: white"
|
|
||||||
type="button"
|
|
||||||
@click="exportHandler"
|
|
||||||
>
|
|
||||||
{{ $t("actions.export_to_json") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div v-for="subscription in subscriptions" :key="subscription.url" style="text-align: center">
|
<div class="grid">
|
||||||
<div class="uk-text-primary" :style="[{ background: backgroundColor }]">
|
<div class="mb-3" v-for="subscription in subscriptions" :key="subscription.url">
|
||||||
<a :href="subscription.url">
|
<div class="flex justify-center place-items-center">
|
||||||
<img :src="subscription.avatar" class="uk-margin-small-right uk-border-circle" width="96" height="96" />
|
<div class="w-full grid grid-cols-3">
|
||||||
<span
|
<router-link :to="subscription.url" class="col-start-2 block flex text-center font-bold text-4xl">
|
||||||
class="uk-text-large"
|
<img :src="subscription.avatar" class="rounded-full" width="48" height="48" />
|
||||||
style="width: 30rem; display: inline-block; text-align: center; margin-left: 6rem"
|
<span v-text="subscription.name" />
|
||||||
>{{ subscription.name }}</span
|
</router-link>
|
||||||
>
|
|
||||||
</a>
|
|
||||||
<button
|
<button
|
||||||
class="uk-button uk-button-large"
|
class="btn !w-min"
|
||||||
style="background: #222; margin-left: 0.5rem; width: 185px"
|
|
||||||
type="button"
|
|
||||||
@click="handleButton(subscription)"
|
@click="handleButton(subscription)"
|
||||||
>
|
v-text="$t(`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`)"
|
||||||
{{ subscription.subscribed ? $t("actions.unsubscribe") : $t("actions.subscribe") }}
|
/>
|
||||||
</button>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<h1 v-t="'titles.trending'" class="uk-text-bold uk-text-center" />
|
<h1 v-t="'titles.trending'" class="font-bold text-center" />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="uk-grid uk-grid-xl">
|
<div class="video-grid">
|
||||||
<div
|
<VideoItem v-for="video in videos" :key="video.url" :video="video" height="118" width="210" />
|
||||||
v-for="video in videos"
|
|
||||||
:key="video.url"
|
|
||||||
:style="[{ background: backgroundColor }]"
|
|
||||||
class="uk-width-1-2 uk-width-1-3@s uk-width-1-4@m uk-width-1-5@l uk-width-1-6@xl"
|
|
||||||
>
|
|
||||||
<VideoItem :video="video" height="118" width="210" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,92 +1,83 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="uk-text-secondary" :style="[{ background: backgroundColor }]">
|
<div>
|
||||||
<router-link class="uk-text-emphasis" :to="video.url">
|
<router-link :to="video.url">
|
||||||
<img :height="height" :width="width" style="width: 100%" :src="video.thumbnail" alt="" loading="lazy" />
|
<img :height="height" :width="width" class="w-full" :src="video.thumbnail" alt="" loading="lazy" />
|
||||||
<div class="uk-position-relative">
|
<div class="relative text-sm">
|
||||||
<span
|
<span
|
||||||
v-if="video.duration"
|
v-if="video.duration"
|
||||||
class="uk-label uk-border-rounded uk-position-absolute video-duration"
|
class="thumbnail-overlay bottom-5px right-5px px-5px"
|
||||||
style="bottom: 5px; right: 5px; background: rgba(0, 0, 0, 0.75); color: white; padding: 0 5px"
|
v-text="timeFormat(video.duration)"
|
||||||
>{{ timeFormat(video.duration) }}</span
|
/>
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
v-if="video.watched"
|
v-if="video.watched"
|
||||||
class="uk-label uk-border-rounded uk-position-absolute video-duration"
|
class="thumbnail-overlay bottom-5px left-5px px-5px"
|
||||||
style="bottom: 5px; left: 5px; background: rgba(0, 0, 0, 0.75); color: white; padding: 0 5px"
|
v-text="$t('video.watched')"
|
||||||
>{{ $t("video.watched") }}</span
|
/>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p
|
<p
|
||||||
style="
|
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
|
||||||
padding-top: 0.5rem;
|
class="my-2 overflow-hidden flex link"
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
"
|
|
||||||
:title="video.title"
|
:title="video.title"
|
||||||
>
|
v-text="video.title"
|
||||||
{{ video.title }}
|
/>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div class="uk-align-right" style="margin-left: 0; margin-bottom: 0; display: inline-block; width: 10%">
|
<div class="float-right m-0 inline-block">
|
||||||
<router-link
|
<router-link
|
||||||
:to="video.url + '&listen=1'"
|
:to="video.url + '&listen=1'"
|
||||||
:aria-label="'Listen to ' + video.title"
|
:aria-label="'Listen to ' + video.title"
|
||||||
:title="'Listen to ' + video.title"
|
:title="'Listen to ' + video.title"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="headphones"></font-awesome-icon>
|
<font-awesome-icon icon="headphones" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; flex-flow: row; height: 15%">
|
<div class="flex">
|
||||||
<router-link class="uk-link-muted" :to="video.uploaderUrl">
|
<router-link :to="video.uploaderUrl">
|
||||||
<img
|
<img
|
||||||
v-if="video.uploaderAvatar"
|
v-if="video.uploaderAvatar"
|
||||||
:src="video.uploaderAvatar"
|
:src="video.uploaderAvatar"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
:alt="video.uploaderName"
|
:alt="video.uploaderName"
|
||||||
class="uk-border-circle"
|
class="rounded-full mr-0.5 mt-0.5 w-32px h-32px"
|
||||||
style="margin-right: 0.5rem; margin-top: 0.5rem; width: 32px; height: 32px"
|
width="68"
|
||||||
|
height="68"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<div style="width: calc(100% - 32px - 8px)">
|
<div class="w-[calc(100%-32px-1rem)]">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="video.uploaderUrl && video.uploaderName && !hideChannel"
|
v-if="video.uploaderUrl && video.uploaderName && !hideChannel"
|
||||||
class="uk-link-muted uk-overflow-hidden"
|
class="link-secondary overflow-hidden block"
|
||||||
:to="video.uploaderUrl"
|
:to="video.uploaderUrl"
|
||||||
:title="video.uploaderName"
|
:title="video.uploaderName"
|
||||||
style="display: block; width: 90%"
|
|
||||||
>
|
>
|
||||||
{{ video.uploaderName }} <font-awesome-icon
|
<span v-text="video.uploaderName" />
|
||||||
v-if="video.uploaderVerified"
|
<font-awesome-icon class="ml-1.5" v-if="video.uploaderVerified" icon="check" />
|
||||||
icon="check"
|
|
||||||
></font-awesome-icon>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<b v-if="video.views >= 0 || video.uploadedDate" class="uk-text-small">
|
<strong v-if="video.views >= 0 || video.uploadedDate" class="text-sm">
|
||||||
<span v-if="video.views >= 0">
|
<span v-if="video.views >= 0">
|
||||||
<font-awesome-icon icon="eye"></font-awesome-icon>
|
<font-awesome-icon icon="eye" />
|
||||||
{{ numberFormat(video.views) }} •
|
<span class="pl-0.5" v-text="`${numberFormat(video.views)} •`" />
|
||||||
</span>
|
</span>
|
||||||
<span v-if="video.uploadedDate">
|
<span v-if="video.uploadedDate" class="pl-0.5" v-text="video.uploadedDate" />
|
||||||
{{ video.uploadedDate }}
|
<span v-if="video.uploaded" class="pl-0.5" v-text="timeAgo(video.uploaded)" />
|
||||||
</span>
|
</strong>
|
||||||
<span v-if="video.uploaded">
|
|
||||||
{{ timeAgo(video.uploaded) }}
|
|
||||||
</span>
|
|
||||||
</b>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.thumbnail-overlay {
|
||||||
|
@apply rounded-md absolute bg-black bg-opacity-75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>{{ $t("actions.loading") }}</div>
|
<div v-text="$t('actions.loading')" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="video && isEmbed" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 999">
|
<div v-if="video && isEmbed" class="absolute top-0 left-0 h-full w-full z-50">
|
||||||
<Player
|
<Player
|
||||||
ref="videoPlayer"
|
ref="videoPlayer"
|
||||||
:video="video"
|
:video="video"
|
||||||
@ -10,7 +10,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="video && !isEmbed" class="uk-container uk-container-expand">
|
<div v-if="video && !isEmbed" class="w-full">
|
||||||
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
|
<ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
|
||||||
|
|
||||||
<div v-show="!video.error">
|
<div v-show="!video.error">
|
||||||
@ -21,125 +21,118 @@
|
|||||||
:selected-auto-play="selectedAutoPlay"
|
:selected-auto-play="selectedAutoPlay"
|
||||||
:selected-auto-loop="selectedAutoLoop"
|
:selected-auto-loop="selectedAutoLoop"
|
||||||
/>
|
/>
|
||||||
<div class="uk-text-bold uk-margin-small-top uk-text-large uk-text-emphasis uk-text-break">
|
<div class="font-bold mt-2 text-2xl break-words" v-text="video.title" />
|
||||||
{{ video.title }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="uk-flex uk-flex-middle">
|
<div class="flex mb-1.5">
|
||||||
<div class="uk-margin-small-right">{{ addCommas(video.views) }} views</div>
|
<span v-text="`${addCommas(video.views)} views`" />
|
||||||
<div class="uk-margin-small-right">{{ uploadDate }}</div>
|
<span class="ml-2" v-text="uploadDate" />
|
||||||
<div class="uk-flex-1"></div>
|
|
||||||
|
<div class="flex items-center relative ml-auto children:ml-2">
|
||||||
<template v-if="video.likes >= 0">
|
<template v-if="video.likes >= 0">
|
||||||
<div class="uk-margin-small-left">
|
<div>
|
||||||
<font-awesome-icon class="uk-margin-small-right" icon="thumbs-up"></font-awesome-icon>
|
<font-awesome-icon icon="thumbs-up" />
|
||||||
<b>{{ addCommas(video.likes) }}</b>
|
<strong class="ml-2" v-text="addCommas(video.likes)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-margin-small-left">
|
<div>
|
||||||
<font-awesome-icon class="uk-margin-small-right" icon="thumbs-down"></font-awesome-icon>
|
<font-awesome-icon icon="thumbs-down" />
|
||||||
<b>{{ video.dislikes >= 0 ? addCommas(video.dislikes) : "?" }}</b>
|
<strong class="ml-2" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="video.likes < 0">
|
<template v-if="video.likes < 0">
|
||||||
<div class="uk-margin-small-left">
|
<div>
|
||||||
<b v-t="'video.ratings_disabled'" />
|
<strong v-t="'video.ratings_disabled'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a :href="'https://youtu.be/' + getVideoId()" class="uk-margin-small-left uk-button uk-button-small">
|
<a :href="`https://youtu.be/${getVideoId()}`" class="btn">
|
||||||
<b>{{ $t("player.watch_on") }} </b>
|
<strong v-text="$t('player.watch_on')" />
|
||||||
<font-awesome-icon class="uk-margin-small-right" :icon="['fab', 'youtube']"></font-awesome-icon>
|
<font-awesome-icon class="ml-1.5" :icon="['fab', 'youtube']" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a v-if="video.lbryId" :href="'https://odysee.com/' + video.lbryId" class="btn">
|
||||||
v-if="video.lbryId"
|
<strong v-text="`${$t('player.watch_on')} LBRY`" />
|
||||||
:href="'https://odysee.com/' + video.lbryId"
|
|
||||||
class="uk-margin-small-left uk-button uk-button-small"
|
|
||||||
>
|
|
||||||
<b>{{ $t("player.watch_on") }} LBRY</b>
|
|
||||||
</a>
|
</a>
|
||||||
<router-link
|
<router-link
|
||||||
:to="toggleListenUrl"
|
:to="toggleListenUrl"
|
||||||
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
|
:aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
|
||||||
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
|
:title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
|
||||||
class="uk-margin-small-left uk-button uk-button-small"
|
class="btn"
|
||||||
>
|
>
|
||||||
<font-awesome-icon :icon="isListening ? 'tv' : 'headphones'"></font-awesome-icon>
|
<font-awesome-icon :icon="isListening ? 'tv' : 'headphones'" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="uk-flex uk-flex-middle uk-margin-small-top">
|
<div class="flex">
|
||||||
<img :src="video.uploaderAvatar" alt="" loading="lazy" class="uk-border-circle" />
|
<div class="flex items-center">
|
||||||
<router-link v-if="video.uploaderUrl" class="uk-link uk-margin-small-left" :to="video.uploaderUrl">
|
<img :src="video.uploaderAvatar" alt="" loading="lazy" class="rounded-full" />
|
||||||
{{ video.uploader }} </router-link
|
<router-link
|
||||||
> <font-awesome-icon v-if="video.uploaderVerified" icon="check"></font-awesome-icon>
|
v-if="video.uploaderUrl"
|
||||||
<div class="uk-flex-1"></div>
|
class="link ml-1.5"
|
||||||
<button v-if="authenticated" class="uk-button uk-button-small" type="button" @click="subscribeHandler">
|
:to="video.uploaderUrl"
|
||||||
{{ subscribed ? $t("actions.unsubscribe") : $t("actions.subscribe") }}
|
v-text="video.uploader"
|
||||||
</button>
|
/>
|
||||||
|
<font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="authenticated"
|
||||||
|
class="btn relative ml-auto"
|
||||||
|
@click="subscribeHandler"
|
||||||
|
v-text="$t(`actions.${subscribed ? 'unsubscribe' : 'subscribe'}`)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<a class="uk-button uk-button-small" @click="showDesc = !showDesc">
|
<button
|
||||||
{{ showDesc ? $t("actions.minimize_description") : $t("actions.show_description") }}
|
class="btn mb-2"
|
||||||
</a>
|
@click="showDesc = !showDesc"
|
||||||
|
v-text="$t(`actions.${showDesc ? 'minimize_description' : 'show_description'}`)"
|
||||||
|
/>
|
||||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<p v-show="showDesc" :style="[{ colour: foregroundColor }]" v-html="purifyHTML(video.description)"></p>
|
<p v-show="showDesc" class="break-words" v-html="purifyHTML(video.description)" />
|
||||||
<div v-if="showDesc && sponsors && sponsors.segments">
|
|
||||||
{{ $t("video.sponsor_segments") }}: {{ sponsors.segments.length }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<label for="chkAutoLoop"
|
|
||||||
><b>{{ $t("actions.loop_this_video") }}:</b></label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="chkAutoLoop"
|
|
||||||
v-model="selectedAutoLoop"
|
|
||||||
class="uk-checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
@change="onChange($event)"
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<label for="chkAutoPlay"
|
|
||||||
><b>{{ $t("actions.auto_play_next_video") }}:</b></label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="chkAutoPlay"
|
|
||||||
v-model="selectedAutoPlay"
|
|
||||||
class="uk-checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
@change="onChange($event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="uk-grid">
|
|
||||||
<div v-if="comments" ref="comments" class="uk-width-4-5@xl uk-width-3-4@s uk-width-1">
|
|
||||||
<div
|
<div
|
||||||
|
v-if="showDesc && sponsors && sponsors.segments"
|
||||||
|
v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<label for="chkAutoLoop"><strong v-text="`${$t('actions.loop_this_video')}:`" /></label>
|
||||||
|
<input id="chkAutoLoop" v-model="selectedAutoLoop" class="ml-1.5" type="checkbox" @change="onChange($event)" />
|
||||||
|
<br />
|
||||||
|
<label for="chkAutoPlay"><strong v-text="`${$t('actions.auto_play_next_video')}:`" /></label>
|
||||||
|
<input id="chkAutoPlay" v-model="selectedAutoPlay" class="ml-1.5" type="checkbox" @change="onChange($event)" />
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="grid xl:grid-cols-5 sm:grid-cols-4 grid-cols-1">
|
||||||
|
<div v-if="comments" ref="comments" class="xl:col-span-4 sm:col-span-3">
|
||||||
|
<Comment
|
||||||
v-for="comment in comments.comments"
|
v-for="comment in comments.comments"
|
||||||
:key="comment.commentId"
|
:key="comment.commentId"
|
||||||
class="uk-tile-default uk-align-left uk-width-expand"
|
:comment="comment"
|
||||||
:style="[{ background: backgroundColor }]"
|
:uploader="video.uploader"
|
||||||
>
|
:video-id="getVideoId()"
|
||||||
<Comment :comment="comment" :uploader="video.uploader" :video-id="getVideoId()" />
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="video" class="uk-width-1-5@xl uk-width-1-4@s uk-width-1 uk-flex-last@s uk-flex-first">
|
<div v-if="video" class="order-first sm:order-last">
|
||||||
<a class="uk-button uk-button-small uk-margin-small-bottom uk-hidden@s" @click="showRecs = !showRecs">
|
<a
|
||||||
{{ showRecs ? $t("actions.minimize_recommendations") : $t("actions.show_recommendations") }}
|
class="btn mb-2 sm:hidden"
|
||||||
</a>
|
@click="showRecs = !showRecs"
|
||||||
<div
|
v-text="$t(`actions.${showRecs ? 'minimize_recommendations' : 'show_recommendations'}`)"
|
||||||
|
/>
|
||||||
|
<hr v-show="showRecs" />
|
||||||
|
<div v-show="showRecs || !smallView">
|
||||||
|
<VideoItem
|
||||||
v-for="related in video.relatedStreams"
|
v-for="related in video.relatedStreams"
|
||||||
v-show="showRecs || !smallView"
|
|
||||||
:key="related.url"
|
:key="related.url"
|
||||||
class="uk-tile-default uk-width-auto"
|
:video="related"
|
||||||
:style="[{ background: backgroundColor }]"
|
height="94"
|
||||||
>
|
width="168"
|
||||||
<VideoItem :video="related" height="94" width="168" />
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr class="uk-hidden@s" />
|
<hr class="sm:hidden" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
30
src/main.js
30
src/main.js
@ -34,9 +34,7 @@ library.add(
|
|||||||
faTv,
|
faTv,
|
||||||
);
|
);
|
||||||
|
|
||||||
import("uikit/dist/css/uikit-core.css");
|
import router from "@/router/router.js";
|
||||||
|
|
||||||
import router from "@/router/router";
|
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
@ -49,6 +47,7 @@ TimeAgo.addDefaultLocale(en);
|
|||||||
|
|
||||||
import { createI18n } from "vue-i18n";
|
import { createI18n } from "vue-i18n";
|
||||||
import enLocale from "@/locales/en.json";
|
import enLocale from "@/locales/en.json";
|
||||||
|
import "windi.css";
|
||||||
|
|
||||||
const timeAgo = new TimeAgo("en-US");
|
const timeAgo = new TimeAgo("en-US");
|
||||||
|
|
||||||
@ -147,13 +146,6 @@ const mixin = {
|
|||||||
apiUrl() {
|
apiUrl() {
|
||||||
return this.getPreferenceString("instance", "https://pipedapi.kavin.rocks");
|
return this.getPreferenceString("instance", "https://pipedapi.kavin.rocks");
|
||||||
},
|
},
|
||||||
getEffectiveTheme() {
|
|
||||||
var theme = this.getPreferenceString("theme", "dark");
|
|
||||||
if (theme === "auto")
|
|
||||||
theme =
|
|
||||||
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
||||||
return theme;
|
|
||||||
},
|
|
||||||
getAuthToken() {
|
getAuthToken() {
|
||||||
return this.getPreferenceString("authToken" + this.hashCode(this.apiUrl()));
|
return this.getPreferenceString("authToken" + this.hashCode(this.apiUrl()));
|
||||||
},
|
},
|
||||||
@ -170,7 +162,7 @@ const mixin = {
|
|||||||
const regex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
|
const regex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
|
||||||
if (!string) return "";
|
if (!string) return "";
|
||||||
return string.replace(regex, url => {
|
return string.replace(regex, url => {
|
||||||
return `<a class="uk-button uk-button-text" href="${url}" target="_blank">${url}</a>`;
|
return `<a href="${url}" target="_blank">${url}</a>`;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async updateWatched(videos) {
|
async updateWatched(videos) {
|
||||||
@ -189,20 +181,8 @@ const mixin = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
backgroundColor() {
|
theme() {
|
||||||
return this.getEffectiveTheme() === "light" ? "#fff" : "#0b0e0f";
|
return this.getPreferenceString("theme", "dark");
|
||||||
},
|
|
||||||
secondaryBackgroundColor() {
|
|
||||||
return this.getEffectiveTheme() === "light" ? "#e5e5e5" : "#242727";
|
|
||||||
},
|
|
||||||
foregroundColor() {
|
|
||||||
return this.getEffectiveTheme() === "light" ? "#15191a" : "#0b0e0f";
|
|
||||||
},
|
|
||||||
secondaryForegroundColor() {
|
|
||||||
return this.getEffectiveTheme() === "light" ? "#666" : "#393d3d";
|
|
||||||
},
|
|
||||||
darkMode() {
|
|
||||||
return this.getEffectiveTheme() !== "light";
|
|
||||||
},
|
},
|
||||||
authenticated(_this) {
|
authenticated(_this) {
|
||||||
return _this.getAuthToken() !== undefined;
|
return _this.getAuthToken() !== undefined;
|
||||||
|
@ -1,33 +1,7 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import { register } from "register-service-worker";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
register(`/service-worker.js`, {
|
registerSW();
|
||||||
ready() {
|
|
||||||
console.log(
|
|
||||||
"App is being served from cache by a service worker.\n" +
|
|
||||||
"For more details, visit https://goo.gl/AFskqB",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
registered() {
|
|
||||||
console.log("Service worker has been registered.");
|
|
||||||
},
|
|
||||||
cached() {
|
|
||||||
console.log("Content has been cached for offline use.");
|
|
||||||
},
|
|
||||||
updatefound() {
|
|
||||||
console.log("New content is downloading.");
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
console.log("New content is available; please refresh.");
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
offline() {
|
|
||||||
console.log("No internet connection found. App is running in offline mode.");
|
|
||||||
},
|
|
||||||
error(error) {
|
|
||||||
console.error("Error during service worker registration:", error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
// Based of https://github.com/GilgusMaximus/yt-dash-manifest-generator/blob/master/src/DashGenerator.js
|
// Based of https://github.com/GilgusMaximus/yt-dash-manifest-generator/blob/master/src/DashGenerator.js
|
||||||
|
|
||||||
const xml = require("xml-js");
|
import { Buffer } from "buffer";
|
||||||
|
window.Buffer = Buffer;
|
||||||
|
import { json2xml } from "xml-js";
|
||||||
|
|
||||||
const DashUtils = {
|
const DashUtils = {
|
||||||
generate_dash_file_from_formats(VideoFormats, VideoLength) {
|
generate_dash_file_from_formats(VideoFormats, VideoLength) {
|
||||||
const generatedJSON = this.generate_xmljs_json_from_data(VideoFormats, VideoLength);
|
const generatedJSON = this.generate_xmljs_json_from_data(VideoFormats, VideoLength);
|
||||||
return xml.json2xml(generatedJSON);
|
return json2xml(generatedJSON);
|
||||||
},
|
},
|
||||||
generate_xmljs_json_from_data(VideoFormatArray, VideoLength) {
|
generate_xmljs_json_from_data(VideoFormatArray, VideoLength) {
|
||||||
const convertJSON = {
|
const convertJSON = {
|
||||||
|
55
vite.config.js
Normal file
55
vite.config.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import WindiCSS from "vite-plugin-windicss";
|
||||||
|
import vueI18n from "@intlify/vite-plugin-vue-i18n";
|
||||||
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
import path from "path";
|
||||||
|
import eslintPlugin from "vite-plugin-eslint";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
WindiCSS(),
|
||||||
|
vueI18n({
|
||||||
|
include: path.resolve(__dirname, "./src/locales/**"),
|
||||||
|
}),
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
workbox: {
|
||||||
|
globPatterns: ["**/*.{js,css,html,ico,svg,png}", "manifest.webmanifest"],
|
||||||
|
},
|
||||||
|
manifest: {
|
||||||
|
name: "Piped",
|
||||||
|
short_name: "Piped",
|
||||||
|
background_color: "#000000",
|
||||||
|
theme_color: "#fa4b4b",
|
||||||
|
icons: [
|
||||||
|
{ src: "./img/icons/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
|
||||||
|
{ src: "./img/icons/android-chrome-512x512.png", sizes: "512x512", type: "image/png" },
|
||||||
|
{
|
||||||
|
src: "./img/icons/android-chrome-maskable-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "maskable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "./img/icons/android-chrome-maskable-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "maskable",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
eslintPlugin(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
@ -1,37 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
pwa: {
|
|
||||||
name: "Piped",
|
|
||||||
themeColor: "#fa4b4b",
|
|
||||||
msTileColor: "#000000",
|
|
||||||
appleMobileWebAppCapable: "yes",
|
|
||||||
appleMobileWebAppStatusBarStyle: "black",
|
|
||||||
workboxPluginMode: "GenerateSW",
|
|
||||||
workboxOptions: {
|
|
||||||
navigateFallback: "index.html",
|
|
||||||
skipWaiting: true,
|
|
||||||
importWorkboxFrom: "local",
|
|
||||||
runtimeCaching: [
|
|
||||||
{
|
|
||||||
urlPattern: /\.(?:png|svg|ico)$/,
|
|
||||||
handler: "CacheFirst",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
configureWebpack: {
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
"vue-i18n": "vue-i18n/dist/vue-i18n.runtime.esm-bundler.js",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pluginOptions: {
|
|
||||||
i18n: {
|
|
||||||
locale: "en",
|
|
||||||
fallbackLocale: "en",
|
|
||||||
localeDir: "locales",
|
|
||||||
fullInstall: true,
|
|
||||||
enableLegacy: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
23
windi.config.js
Normal file
23
windi.config.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = {
|
||||||
|
darkMode: "media",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: [
|
||||||
|
"-apple-system",
|
||||||
|
"BlinkMacSystemFont",
|
||||||
|
"Segoe UI",
|
||||||
|
"Roboto",
|
||||||
|
"Helvetica Neue",
|
||||||
|
"Arial",
|
||||||
|
"Noto Sans",
|
||||||
|
"sans-serif",
|
||||||
|
"Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji",
|
||||||
|
"Segoe UI Symbol",
|
||||||
|
"Noto Color Emoji",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user