mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2025-11-04 06:31:55 +00:00 
			
		
		
		
	Merge pull request #683 from TeamPiped/windicss
Replace UIKit with Windicss
This commit is contained in:
		
							
								
								
									
										26
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,18 +1,18 @@
 | 
			
		||||
name: Build and Lint
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
  push:
 | 
			
		||||
    pull_request:
 | 
			
		||||
    push:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2.4.0
 | 
			
		||||
      - name: Setup Node.js
 | 
			
		||||
        uses: actions/setup-node@v2.5.1
 | 
			
		||||
        with:
 | 
			
		||||
          cache: "yarn"
 | 
			
		||||
      - run: yarn install --prefer-offline
 | 
			
		||||
      - run: yarn build
 | 
			
		||||
      - run: yarn lint --no-fix
 | 
			
		||||
    build:
 | 
			
		||||
        runs-on: ubuntu-latest
 | 
			
		||||
        steps:
 | 
			
		||||
            - uses: actions/checkout@v2.4.0
 | 
			
		||||
            - name: Setup Node.js
 | 
			
		||||
              uses: actions/setup-node@v2.5.1
 | 
			
		||||
              with:
 | 
			
		||||
                  cache: "yarn"
 | 
			
		||||
            - run: yarn install --prefer-offline
 | 
			
		||||
            - run: yarn build
 | 
			
		||||
            - run: yarn lint --no-fix
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,38 +1,38 @@
 | 
			
		||||
name: Docker Multi-Architecture Build
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - "**.md"
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
    push:
 | 
			
		||||
        paths-ignore:
 | 
			
		||||
            - "**.md"
 | 
			
		||||
        branches:
 | 
			
		||||
            - master
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build-docker-image:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2.4.0
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        uses: docker/setup-qemu-action@v1.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          platforms: all
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        id: buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v1.6.0
 | 
			
		||||
        with:
 | 
			
		||||
          version: latest
 | 
			
		||||
      - name: Login to DockerHub
 | 
			
		||||
        uses: docker/login-action@v1.12.0
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
      - name: Build and push
 | 
			
		||||
        uses: docker/build-push-action@v2.7.0
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          file: ./Dockerfile
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: 1337kavin/piped-frontend:latest
 | 
			
		||||
    build-docker-image:
 | 
			
		||||
        runs-on: ubuntu-latest
 | 
			
		||||
        steps:
 | 
			
		||||
            - uses: actions/checkout@v2.4.0
 | 
			
		||||
              with:
 | 
			
		||||
                  fetch-depth: 0
 | 
			
		||||
            - name: Set up QEMU
 | 
			
		||||
              uses: docker/setup-qemu-action@v1.2.0
 | 
			
		||||
              with:
 | 
			
		||||
                  platforms: all
 | 
			
		||||
            - name: Set up Docker Buildx
 | 
			
		||||
              id: buildx
 | 
			
		||||
              uses: docker/setup-buildx-action@v1.6.0
 | 
			
		||||
              with:
 | 
			
		||||
                  version: latest
 | 
			
		||||
            - name: Login to DockerHub
 | 
			
		||||
              uses: docker/login-action@v1.12.0
 | 
			
		||||
              with:
 | 
			
		||||
                  username: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
                  password: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
            - name: Build and push
 | 
			
		||||
              uses: docker/build-push-action@v2.7.0
 | 
			
		||||
              with:
 | 
			
		||||
                  context: .
 | 
			
		||||
                  file: ./Dockerfile
 | 
			
		||||
                  platforms: linux/amd64,linux/arm64
 | 
			
		||||
                  push: true
 | 
			
		||||
                  tags: 1337kavin/piped-frontend:latest
 | 
			
		||||
 
 | 
			
		||||
@@ -15,9 +15,9 @@ jobs:
 | 
			
		||||
      - name: Setup Node.js
 | 
			
		||||
        uses: actions/setup-node@v2.5.1
 | 
			
		||||
        with:
 | 
			
		||||
            cache: 'yarn'
 | 
			
		||||
          cache: "yarn"
 | 
			
		||||
      - 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
 | 
			
		||||
        id: ipfs-add
 | 
			
		||||
        with:
 | 
			
		||||
@@ -8,7 +8,7 @@ RUN yarn install --prefer-offline
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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",
 | 
			
		||||
    "private": true,
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "serve": "vue-cli-service serve",
 | 
			
		||||
        "build": "vue-cli-service build",
 | 
			
		||||
        "lint": "vue-cli-service lint"
 | 
			
		||||
        "serve": "vite",
 | 
			
		||||
        "build": "vite build",
 | 
			
		||||
        "preview": "vite preview",
 | 
			
		||||
        "lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
 | 
			
		||||
    },
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@fortawesome/fontawesome-svg-core": "^1.2.36",
 | 
			
		||||
        "@fortawesome/free-brands-svg-icons": "^5.15.4",
 | 
			
		||||
        "@fortawesome/free-solid-svg-icons": "^5.15.4",
 | 
			
		||||
        "@fortawesome/vue-fontawesome": "^3.0.0-5",
 | 
			
		||||
        "core-js": "3.20.1",
 | 
			
		||||
        "css-loader": "^6.5.1",
 | 
			
		||||
        "buffer": "^6.0.3",
 | 
			
		||||
        "dompurify": "^2.3.4",
 | 
			
		||||
        "hotkeys-js": "^3.8.7",
 | 
			
		||||
        "javascript-time-ago": "^2.3.10",
 | 
			
		||||
        "mux.js": "^6.0.1",
 | 
			
		||||
        "register-service-worker": "^1.7.1",
 | 
			
		||||
        "shaka-player": "3.3.0",
 | 
			
		||||
        "uikit": "3.9.4",
 | 
			
		||||
        "stream": "^0.0.2",
 | 
			
		||||
        "vue": "^3.2.26",
 | 
			
		||||
        "vue-i18n": "^9.2.0-beta.25",
 | 
			
		||||
        "vue-router": "^4.0.12",
 | 
			
		||||
        "xml-js": "^1.6.11"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@intlify/vue-i18n-loader": "^4.1.0",
 | 
			
		||||
        "@vue/cli-plugin-babel": "^4.5.15",
 | 
			
		||||
        "@vue/cli-plugin-eslint": "^4.5.15",
 | 
			
		||||
        "@vue/cli-plugin-pwa": "^4.5.15",
 | 
			
		||||
        "@vue/cli-service": "^4.5.15",
 | 
			
		||||
        "@intlify/vite-plugin-vue-i18n": "^3.2.1",
 | 
			
		||||
        "@vitejs/plugin-vue": "^2.0.1",
 | 
			
		||||
        "@vue/compiler-sfc": "3.2.26",
 | 
			
		||||
        "babel-eslint": "^10.1.0",
 | 
			
		||||
        "eslint": "^7.32.0",
 | 
			
		||||
        "eslint-config-prettier": "^8.3.0",
 | 
			
		||||
        "eslint-plugin-prettier": "^4.0.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": {
 | 
			
		||||
        "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>
 | 
			
		||||
							
								
								
									
										143
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								src/App.vue
									
									
									
									
									
								
							@@ -1,9 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div
 | 
			
		||||
        class="uk-container uk-container-expand uk-height-viewport"
 | 
			
		||||
        :style="[{ background: backgroundColor, colour: foregroundColor }]"
 | 
			
		||||
        :class="{ 'uk-light': darkMode }"
 | 
			
		||||
    >
 | 
			
		||||
    <div class="w-full min-h-screen px-1vw reset" :class="[theme]">
 | 
			
		||||
        <Navigation />
 | 
			
		||||
        <router-view v-slot="{ Component }">
 | 
			
		||||
            <keep-alive :max="5">
 | 
			
		||||
@@ -11,21 +7,20 @@
 | 
			
		||||
            </keep-alive>
 | 
			
		||||
        </router-view>
 | 
			
		||||
 | 
			
		||||
        <div style="text-align: center">
 | 
			
		||||
        <footer class="text-center">
 | 
			
		||||
            <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 href="https://github.com/TeamPiped/Piped#donations">
 | 
			
		||||
                <font-awesome-icon :icon="['fab', 'bitcoin']"></font-awesome-icon>
 | 
			
		||||
                {{ $t("actions.donations") }}
 | 
			
		||||
            <a class="ml-2" href="https://github.com/TeamPiped/Piped#donations">
 | 
			
		||||
                <font-awesome-icon :icon="['fab', 'bitcoin']" />
 | 
			
		||||
                <span v-text="$t('actions.donations')" />
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
        </footer>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Navigation from "@/components/Navigation";
 | 
			
		||||
import Navigation from "@/components/Navigation.vue";
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        Navigation,
 | 
			
		||||
@@ -45,7 +40,7 @@ export default {
 | 
			
		||||
        if (this.getPreferenceBoolean("watchHistory", false))
 | 
			
		||||
            if ("indexedDB" in window) {
 | 
			
		||||
                const request = indexedDB.open("piped-db", 1);
 | 
			
		||||
                request.onupgradeneeded = function() {
 | 
			
		||||
                request.onupgradeneeded = function () {
 | 
			
		||||
                    const db = request.result;
 | 
			
		||||
                    console.log("Upgrading object store.");
 | 
			
		||||
                    if (!db.objectStoreNames.contains("watch_history")) {
 | 
			
		||||
@@ -61,18 +56,18 @@ export default {
 | 
			
		||||
 | 
			
		||||
        const App = this;
 | 
			
		||||
 | 
			
		||||
        (async function() {
 | 
			
		||||
        (async function () {
 | 
			
		||||
            const locale = App.getPreferenceString("hl", App.defaultLangage);
 | 
			
		||||
            if (locale !== App.TimeAgoConfig.locale) {
 | 
			
		||||
                const localeTime = await import("javascript-time-ago/locale/" + locale + ".json").then(
 | 
			
		||||
                    module => module.default,
 | 
			
		||||
                );
 | 
			
		||||
                const localeTime = await import(
 | 
			
		||||
                    "./../node_modules/javascript-time-ago/locale/" + locale + ".json"
 | 
			
		||||
                ).then(module => module.default);
 | 
			
		||||
                App.TimeAgo.addLocale(localeTime);
 | 
			
		||||
                App.TimeAgoConfig.locale = locale;
 | 
			
		||||
            }
 | 
			
		||||
            if (window.i18n.global.locale.value !== 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.locale.value = locale;
 | 
			
		||||
@@ -93,7 +88,6 @@ b {
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
    background-color: #15191a;
 | 
			
		||||
    color: #c5bcae;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
@@ -114,13 +108,114 @@ b {
 | 
			
		||||
 | 
			
		||||
* {
 | 
			
		||||
    scrollbar-color: #15191a #444a4e;
 | 
			
		||||
    @apply font-sans;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.uk-grid > div {
 | 
			
		||||
    padding-bottom: 1vh;
 | 
			
		||||
.video-grid {
 | 
			
		||||
    @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 {
 | 
			
		||||
    background: #222;
 | 
			
		||||
.btn {
 | 
			
		||||
    @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>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,34 @@
 | 
			
		||||
    <ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
 | 
			
		||||
 | 
			
		||||
    <div v-if="channel" v-show="!channel.error">
 | 
			
		||||
        <h1 class="uk-text-center">
 | 
			
		||||
            <img height="48" width="48" class="uk-border-circle" :src="channel.avatarUrl" />{{ channel.name }}
 | 
			
		||||
        </h1>
 | 
			
		||||
        <img v-if="channel.bannerUrl" :src="channel.bannerUrl" style="width: 100%" loading="lazy" />
 | 
			
		||||
        <div class="flex justify-center place-items-center">
 | 
			
		||||
            <img height="48" width="48" class="rounded-full m-1" :src="channel.avatarUrl" />
 | 
			
		||||
            <h1 v-text="channel.name" />
 | 
			
		||||
        </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 -->
 | 
			
		||||
        <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">
 | 
			
		||||
            {{ subscribed ? $t("actions.unsubscribe") : $t("actions.subscribe") }}
 | 
			
		||||
        </button>
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="authenticated"
 | 
			
		||||
            class="btn"
 | 
			
		||||
            @click="subscribeHandler"
 | 
			
		||||
            v-text="$t(`actions.${subscribed ? 'unsubscribe' : 'subscribe'}`)"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <hr />
 | 
			
		||||
 | 
			
		||||
        <div class="uk-grid uk-grid-xl">
 | 
			
		||||
            <div
 | 
			
		||||
        <div class="video-grid">
 | 
			
		||||
            <VideoItem
 | 
			
		||||
                v-for="video in channel.relatedStreams"
 | 
			
		||||
                :key="video.url"
 | 
			
		||||
                class="uk-width-1-2 uk-width-1-3@m uk-width-1-4@l uk-width-1-5@xl"
 | 
			
		||||
            >
 | 
			
		||||
                <VideoItem :video="video" height="94" width="168" hide-channel />
 | 
			
		||||
            </div>
 | 
			
		||||
                :video="video"
 | 
			
		||||
                height="94"
 | 
			
		||||
                width="168"
 | 
			
		||||
                hide-channel
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,67 +1,53 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="comment uk-flex">
 | 
			
		||||
    <div class="comment flex mt-1.5">
 | 
			
		||||
        <img
 | 
			
		||||
            :src="comment.thumbnail"
 | 
			
		||||
            class="comment-avatar uk-border-circle uk-margin-right"
 | 
			
		||||
            class="comment-avatar rounded-full w-12 h-12"
 | 
			
		||||
            height="48"
 | 
			
		||||
            width="48"
 | 
			
		||||
            style="width: 48px; height: 48px"
 | 
			
		||||
            loading="lazy"
 | 
			
		||||
            alt="Avatar"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <div class="comment-content">
 | 
			
		||||
        <div class="comment-content pl-2">
 | 
			
		||||
            <div class="comment-header">
 | 
			
		||||
                <div v-if="comment.pinned" class="comment-pinned uk-text-meta">
 | 
			
		||||
                    <font-awesome-icon icon="thumbtack"></font-awesome-icon>  {{ $t("comment.pinned_by") }}
 | 
			
		||||
                    {{ uploader }}
 | 
			
		||||
                <div v-if="comment.pinned" class="comment-pinned">
 | 
			
		||||
                    <font-awesome-icon icon="thumbtack" />
 | 
			
		||||
                    <span class="ml-1.5" v-text="$t('comment.pinned_by')" />
 | 
			
		||||
                    <span v-text="uploader" />
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="comment-author">
 | 
			
		||||
                    <router-link class="uk-text-bold uk-text-small" :to="comment.commentorUrl">
 | 
			
		||||
                        {{ comment.author }} </router-link
 | 
			
		||||
                    > <font-awesome-icon v-if="comment.verified" icon="check"></font-awesome-icon>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="comment-meta uk-text-meta uk-margin-small-bottom">
 | 
			
		||||
                    {{ comment.commentedTime }}
 | 
			
		||||
                    <router-link class="font-bold link" :to="comment.commentorUrl" v-text="comment.author" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" v-if="comment.verified" icon="check" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="comment-meta text-sm mb-1.5" v-text="comment.commentedTime" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="comment-body" style="white-space: pre-wrap">
 | 
			
		||||
                {{ comment.commentText }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="comment-footer uk-margin-small-top uk-text-meta">
 | 
			
		||||
                <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 class="whitespace-pre-wrap" v-text="comment.commentText" />
 | 
			
		||||
            <div class="comment-footer mt-1">
 | 
			
		||||
                <font-awesome-icon icon="thumbs-up" />
 | 
			
		||||
                <span class="ml-1" v-text="numberFormat(comment.likeCount)" />
 | 
			
		||||
                <font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
 | 
			
		||||
                <div @click="loadReplies">
 | 
			
		||||
                    <a class="uk-link-text" v-t="'actions.show_replies'" />
 | 
			
		||||
                     
 | 
			
		||||
                    <font-awesome-icon icon="level-down-alt" />
 | 
			
		||||
                    <a v-t="'actions.show_replies'" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-if="showingReplies">
 | 
			
		||||
                <div @click="hideReplies">
 | 
			
		||||
                    <a class="uk-link-text" v-t="'actions.hide_replies'" />
 | 
			
		||||
                     
 | 
			
		||||
                    <font-awesome-icon icon="level-up-alt" />
 | 
			
		||||
                    <a v-t="'actions.hide_replies'" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" icon="level-up-alt" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </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-for="reply in replies"
 | 
			
		||||
                    :key="reply.commentId"
 | 
			
		||||
                    class="uk-tile-default uk-align-left uk-width-expand"
 | 
			
		||||
                    :style="[{ background: backgroundColor }]"
 | 
			
		||||
                >
 | 
			
		||||
            <div v-show="showingReplies" v-if="replies" class="replies">
 | 
			
		||||
                <div v-for="reply in replies" :key="reply.commentId" class="w-full">
 | 
			
		||||
                    <Comment :comment="reply" :uploader="uploader" :video-id="videoId" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-if="nextpage" @click="loadReplies">
 | 
			
		||||
                    <a class="uk-link-text" v-t="'actions.load_more_replies'" />
 | 
			
		||||
                     
 | 
			
		||||
                    <font-awesome-icon icon="level-down-alt" />
 | 
			
		||||
                    <a v-t="'actions.load_more_replies'" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <p>{{ message }}</p>
 | 
			
		||||
    <button @click="toggleTrace" class="uk-button uk-button-small" type="button">
 | 
			
		||||
        {{ $t("actions.show_more") }}
 | 
			
		||||
    </button>
 | 
			
		||||
    <p ref="stacktrace" style="white-space: pre-wrap" hidden>{{ error }}</p>
 | 
			
		||||
    <p v-text="message" />
 | 
			
		||||
    <button @click="toggleTrace" class="btn" v-text="$t('actions.show_more')" />
 | 
			
		||||
    <p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +1,35 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <h1 v-t="'titles.feed'" class="uk-text-bold uk-text-center" />
 | 
			
		||||
    <h1 v-t="'titles.feed'" class="font-bold text-center" />
 | 
			
		||||
 | 
			
		||||
    <button
 | 
			
		||||
        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>
 | 
			
		||||
    <button v-if="authenticated" class="btn mr-2" @click="exportHandler">
 | 
			
		||||
        <router-link to="/subscriptions">Subscriptions</router-link>
 | 
			
		||||
    </button>
 | 
			
		||||
 | 
			
		||||
    <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 class="uk-align-right@m">
 | 
			
		||||
        <label for="ddlSortBy">{{ $t("actions.sort_by") }}</label>
 | 
			
		||||
        <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 class="md:float-right">
 | 
			
		||||
        <Sorting by-key="uploaded" @apply="order => videos.sort(order)" />
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div class="uk-grid uk-grid-xl">
 | 
			
		||||
        <div
 | 
			
		||||
            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 class="video-grid">
 | 
			
		||||
        <VideoItem v-for="video in videos" :key="video.url" :video="video" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VideoItem from "@/components/VideoItem.vue";
 | 
			
		||||
import Sorting from "@/components/Sorting.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        VideoItem,
 | 
			
		||||
        Sorting,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
@@ -52,7 +37,6 @@ export default {
 | 
			
		||||
            videoStep: 100,
 | 
			
		||||
            videosStore: [],
 | 
			
		||||
            videos: [],
 | 
			
		||||
            selectedSort: "descending",
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -84,22 +68,6 @@ export default {
 | 
			
		||||
                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() {
 | 
			
		||||
            this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
 | 
			
		||||
            if (this.videos.length != this.videosStore.length)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,20 @@
 | 
			
		||||
<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">
 | 
			
		||||
        <button class="uk-button" v-t="'actions.clear_history'" @click="clearHistory"></button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="flex">
 | 
			
		||||
        <div>
 | 
			
		||||
            <button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
    <div style="text-align: right">
 | 
			
		||||
        <label for="ddlSortBy">{{ $t("actions.sort_by") }}</label>
 | 
			
		||||
        <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>
 | 
			
		||||
        <div class="right-1">
 | 
			
		||||
            <Sorting by-key="watchedAt" @apply="order => videos.sort(order)" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div class="uk-grid uk-grid-xl">
 | 
			
		||||
        <div
 | 
			
		||||
            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 class="video-grid">
 | 
			
		||||
        <VideoItem v-for="video in videos" :key="video.url" :video="video" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <br />
 | 
			
		||||
@@ -33,15 +22,16 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VideoItem from "@/components/VideoItem.vue";
 | 
			
		||||
import Sorting from "@/components/Sorting.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        VideoItem,
 | 
			
		||||
        Sorting,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            videos: [],
 | 
			
		||||
            selectedSort: "descending",
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
@@ -74,22 +64,6 @@ export default {
 | 
			
		||||
        document.title = "Watch History - Piped";
 | 
			
		||||
    },
 | 
			
		||||
    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() {
 | 
			
		||||
            if (window.db) {
 | 
			
		||||
                var tx = window.db.transaction("watch_history", "readwrite");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="uk-vertical-align uk-text-center uk-height-1-1">
 | 
			
		||||
        <form class="uk-panel uk-panel-box">
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
    <div class="text-center">
 | 
			
		||||
        <form>
 | 
			
		||||
            <div>
 | 
			
		||||
                <input ref="fileSelector" type="file" @change="fileChange" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
                <b>Selected Subscriptions: {{ selectedSubscriptions }}</b>
 | 
			
		||||
            <div>
 | 
			
		||||
                <strong v-text="`Selected Subscriptions: ${selectedSubscriptions}`" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
                <b>Override: <input v-model="override" class="uk-checkbox" type="checkbox"/></b>
 | 
			
		||||
            <div>
 | 
			
		||||
                <strong>Override: <input v-model="override" class="checkbox" type="checkbox" /></strong>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
                <a class="uk-width-1-1 uk-button uk-button-large uk-width-auto" @click="handleImport">Import</a>
 | 
			
		||||
            <div>
 | 
			
		||||
                <a class="btn w-auto" @click="handleImport">Import</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
        <br />
 | 
			
		||||
        <b>Importing Subscriptions from YouTube</b>
 | 
			
		||||
        <strong>Importing Subscriptions from YouTube</strong>
 | 
			
		||||
        <br />
 | 
			
		||||
        <div>
 | 
			
		||||
            Open
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
            Select and import the file above.
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
        <b>Importing Subscriptions from Invidious</b>
 | 
			
		||||
        <strong>Importing Subscriptions from Invidious</strong>
 | 
			
		||||
        <br />
 | 
			
		||||
        <div>
 | 
			
		||||
            Open
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
            Select and import the file above.
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
        <b>Importing Subscriptions from NewPipe</b>
 | 
			
		||||
        <strong>Importing Subscriptions from NewPipe</strong>
 | 
			
		||||
        <br />
 | 
			
		||||
        <div>
 | 
			
		||||
            Go to the Feed tab.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,29 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="uk-vertical-align uk-text-center uk-height-1-1">
 | 
			
		||||
        <form class="uk-panel uk-panel-box">
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
    <div class="text-center">
 | 
			
		||||
        <h1 v-t="'titles.login'" />
 | 
			
		||||
        <form class="children:pb-3">
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="username"
 | 
			
		||||
                    class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
 | 
			
		||||
                    class="input"
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    autocomplete="username"
 | 
			
		||||
                    :placeholder="$t('login.username')"
 | 
			
		||||
                    :aria-label="$t('login.username')"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="password"
 | 
			
		||||
                    class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
 | 
			
		||||
                    class="input"
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    autocomplete="password"
 | 
			
		||||
                    :placeholder="$t('login.password')"
 | 
			
		||||
                    :aria-label="$t('login.password')"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
                <a class="uk-width-1-1 uk-button uk-button-large uk-width-auto" @click="login">
 | 
			
		||||
                    {{ $t("titles.login") }}
 | 
			
		||||
                </a>
 | 
			
		||||
            <div>
 | 
			
		||||
                <a class="btn w-auto" @click="login" v-text="$t('titles.login')" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,20 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <nav
 | 
			
		||||
        class="uk-navbar uk-navbar-container uk-container-expand uk-position-relative"
 | 
			
		||||
        :style="[{ background: backgroundColor, colour: foregroundColor }]"
 | 
			
		||||
        uk-navbar
 | 
			
		||||
    >
 | 
			
		||||
        <div class="uk-navbar-left">
 | 
			
		||||
            <router-link class="uk-navbar-item uk-logo uk-text-bold" :style="[{ colour: foregroundColor }]" to="/"
 | 
			
		||||
    <nav class="flex flex-wrap items-center justify-center px-2 sm:px-4 py-2.5 w-full relative">
 | 
			
		||||
        <div class="flex-1">
 | 
			
		||||
            <router-link class="flex font-bold text-3xl items-center font-sans font-bold" to="/"
 | 
			
		||||
                ><img
 | 
			
		||||
                    alt="logo"
 | 
			
		||||
                    src="/img/icons/logo.svg"
 | 
			
		||||
                    height="32"
 | 
			
		||||
                    width="32"
 | 
			
		||||
                    style="margin-bottom: 6px; margin-right: -13px"
 | 
			
		||||
                    class="w-10 mr-[-0.6rem]"
 | 
			
		||||
                />iped</router-link
 | 
			
		||||
            >
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="uk-navbar-center uk-flex uk-visible@m">
 | 
			
		||||
        <div class="<md:hidden">
 | 
			
		||||
            <input
 | 
			
		||||
                v-model="searchText"
 | 
			
		||||
                class="uk-input uk-width-medium"
 | 
			
		||||
                class="input !w-72 !h-10"
 | 
			
		||||
                type="text"
 | 
			
		||||
                role="search"
 | 
			
		||||
                :title="$t('actions.search')"
 | 
			
		||||
@@ -29,8 +25,8 @@
 | 
			
		||||
                @blur="onInputBlur"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="uk-navbar-right">
 | 
			
		||||
            <ul class="uk-navbar-nav">
 | 
			
		||||
        <div class="flex-1 flex justify-end">
 | 
			
		||||
            <ul class="flex text-1xl children:pl-3">
 | 
			
		||||
                <li>
 | 
			
		||||
                    <router-link v-t="'titles.preferences'" to="/preferences" />
 | 
			
		||||
                </li>
 | 
			
		||||
@@ -49,10 +45,10 @@
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
    <div class="uk-container-expand uk-hidden@m">
 | 
			
		||||
    <div class="w-full md:hidden">
 | 
			
		||||
        <input
 | 
			
		||||
            v-model="searchText"
 | 
			
		||||
            class="uk-input"
 | 
			
		||||
            class="input !h-10 !w-full"
 | 
			
		||||
            type="text"
 | 
			
		||||
            role="search"
 | 
			
		||||
            :title="$t('actions.search')"
 | 
			
		||||
@@ -72,7 +68,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import SearchSuggestions from "@/components/SearchSuggestions";
 | 
			
		||||
import SearchSuggestions from "@/components/SearchSuggestions.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
@@ -84,6 +80,10 @@ export default {
 | 
			
		||||
            suggestionsVisible: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        const query = new URLSearchParams(window.location.search).get("search_query");
 | 
			
		||||
        if (query) this.onSearchTextChange(query);
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        shouldShowLogin(_this) {
 | 
			
		||||
            return _this.getAuthToken() == null;
 | 
			
		||||
@@ -121,5 +121,3 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="uk-container-expand">
 | 
			
		||||
    <div class="w-full">
 | 
			
		||||
        <div
 | 
			
		||||
            ref="container"
 | 
			
		||||
            data-shaka-player-container
 | 
			
		||||
            style="width: 100%; height: 100%; background: #000"
 | 
			
		||||
            :style="!isEmbed ? { 'max-height': '75vh', 'min-height': '250px' } : {}"
 | 
			
		||||
        >
 | 
			
		||||
            <video
 | 
			
		||||
                ref="videoEl"
 | 
			
		||||
                data-shaka-player
 | 
			
		||||
                class="uk-width-expand"
 | 
			
		||||
                :autoplay="shouldAutoPlay"
 | 
			
		||||
                :loop="selectedAutoLoop"
 | 
			
		||||
            ></video>
 | 
			
		||||
            <video ref="videoEl" data-shaka-player class="w-full" :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -81,103 +75,103 @@ export default {
 | 
			
		||||
            .then(hotkeys => {
 | 
			
		||||
                this.hotkeys = hotkeys;
 | 
			
		||||
                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(
 | 
			
		||||
                    e,
 | 
			
		||||
                    handler,
 | 
			
		||||
                ) {
 | 
			
		||||
                    const videoEl = self.$refs.videoEl;
 | 
			
		||||
                    console.log(handler.key);
 | 
			
		||||
                    switch (handler.key) {
 | 
			
		||||
                        case "f":
 | 
			
		||||
                            self.$ui.getControls().toggleFullScreen();
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "m":
 | 
			
		||||
                            videoEl.muted = !videoEl.muted;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "j":
 | 
			
		||||
                            videoEl.currentTime = Math.max(videoEl.currentTime - 15, 0);
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "l":
 | 
			
		||||
                            videoEl.currentTime = videoEl.currentTime + 15;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "c":
 | 
			
		||||
                            self.$player.setTextTrackVisibility(!self.$player.isTextTrackVisible());
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "k":
 | 
			
		||||
                        case "space":
 | 
			
		||||
                            if (videoEl.paused) videoEl.play();
 | 
			
		||||
                            else videoEl.pause();
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "up":
 | 
			
		||||
                            videoEl.volume = Math.min(videoEl.volume + 0.05, 1);
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "down":
 | 
			
		||||
                            videoEl.volume = Math.max(videoEl.volume - 0.05, 0);
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "left":
 | 
			
		||||
                            videoEl.currentTime = Math.max(videoEl.currentTime - 5, 0);
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "right":
 | 
			
		||||
                            videoEl.currentTime = videoEl.currentTime + 5;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "0":
 | 
			
		||||
                            videoEl.currentTime = 0;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "1":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.1;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "2":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.2;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "3":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.3;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "4":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.4;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "5":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.5;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "6":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.6;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "7":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.7;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "8":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.8;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "9":
 | 
			
		||||
                            videoEl.currentTime = videoEl.duration * 0.9;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "shift+,":
 | 
			
		||||
                            self.$player.trickPlay(Math.max(videoEl.playbackRate - 0.25, 0.25));
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "shift+.":
 | 
			
		||||
                            self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                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 (e, handler) {
 | 
			
		||||
                        const videoEl = self.$refs.videoEl;
 | 
			
		||||
                        console.log(handler.key);
 | 
			
		||||
                        switch (handler.key) {
 | 
			
		||||
                            case "f":
 | 
			
		||||
                                self.$ui.getControls().toggleFullScreen();
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "m":
 | 
			
		||||
                                videoEl.muted = !videoEl.muted;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "j":
 | 
			
		||||
                                videoEl.currentTime = Math.max(videoEl.currentTime - 15, 0);
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "l":
 | 
			
		||||
                                videoEl.currentTime = videoEl.currentTime + 15;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "c":
 | 
			
		||||
                                self.$player.setTextTrackVisibility(!self.$player.isTextTrackVisible());
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "k":
 | 
			
		||||
                            case "space":
 | 
			
		||||
                                if (videoEl.paused) videoEl.play();
 | 
			
		||||
                                else videoEl.pause();
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "up":
 | 
			
		||||
                                videoEl.volume = Math.min(videoEl.volume + 0.05, 1);
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "down":
 | 
			
		||||
                                videoEl.volume = Math.max(videoEl.volume - 0.05, 0);
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "left":
 | 
			
		||||
                                videoEl.currentTime = Math.max(videoEl.currentTime - 5, 0);
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "right":
 | 
			
		||||
                                videoEl.currentTime = videoEl.currentTime + 5;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "0":
 | 
			
		||||
                                videoEl.currentTime = 0;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "1":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.1;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "2":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.2;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "3":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.3;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "4":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.4;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "5":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.5;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "6":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.6;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "7":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.7;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "8":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.8;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "9":
 | 
			
		||||
                                videoEl.currentTime = videoEl.duration * 0.9;
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "shift+,":
 | 
			
		||||
                                self.$player.trickPlay(Math.max(videoEl.playbackRate - 0.25, 0.25));
 | 
			
		||||
                                break;
 | 
			
		||||
                            case "shift+.":
 | 
			
		||||
                                self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
 | 
			
		||||
                                break;
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
    },
 | 
			
		||||
    deactivated() {
 | 
			
		||||
@@ -194,12 +188,30 @@ export default {
 | 
			
		||||
            videoEl.setAttribute("poster", this.video.thumbnailUrl);
 | 
			
		||||
 | 
			
		||||
            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) {
 | 
			
		||||
                var tx = window.db.transaction("watch_history", "readonly");
 | 
			
		||||
                var store = tx.objectStore("watch_history");
 | 
			
		||||
                var request = store.get(this.video.id);
 | 
			
		||||
                request.onsuccess = function(event) {
 | 
			
		||||
                request.onsuccess = function (event) {
 | 
			
		||||
                    var video = event.target.result;
 | 
			
		||||
                    if (video && video.currentTime) {
 | 
			
		||||
                        videoEl.currentTime = video.currentTime;
 | 
			
		||||
@@ -228,10 +240,9 @@ export default {
 | 
			
		||||
                mime = "application/x-mpegURL";
 | 
			
		||||
            } else if (this.video.audioStreams.length > 0 && !lbry && MseSupport) {
 | 
			
		||||
                if (!this.video.dash) {
 | 
			
		||||
                    const dash = require("@/utils/DashUtils.js").default.generate_dash_file_from_formats(
 | 
			
		||||
                        streams,
 | 
			
		||||
                        this.video.duration,
 | 
			
		||||
                    );
 | 
			
		||||
                    const dash = (
 | 
			
		||||
                        await import("@/utils/DashUtils.js").then(mod => mod.default)
 | 
			
		||||
                    ).generate_dash_file_from_formats(streams, this.video.duration);
 | 
			
		||||
 | 
			
		||||
                    uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
 | 
			
		||||
                } else uri = this.video.dash;
 | 
			
		||||
@@ -484,7 +495,7 @@ export default {
 | 
			
		||||
            var tx = window.db.transaction("watch_history", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("watch_history");
 | 
			
		||||
            var request = store.get(this.video.id);
 | 
			
		||||
            request.onsuccess = function(event) {
 | 
			
		||||
            request.onsuccess = function (event) {
 | 
			
		||||
                var video = event.target.result;
 | 
			
		||||
                if (video) {
 | 
			
		||||
                    video.currentTime = time;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,34 +2,36 @@
 | 
			
		||||
    <ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
 | 
			
		||||
 | 
			
		||||
    <div v-if="playlist" v-show="!playlist.error">
 | 
			
		||||
        <h1 class="uk-text-center">
 | 
			
		||||
            <img :src="playlist.avatarUrl" height="48" width="48" loading="lazy" />
 | 
			
		||||
            {{ playlist.name }}
 | 
			
		||||
        </h1>
 | 
			
		||||
        <h1 class="text-center" v-text="playlist.name" />
 | 
			
		||||
 | 
			
		||||
        <b
 | 
			
		||||
            ><router-link class="uk-text-justify" :to="playlist.uploaderUrl || '/'">
 | 
			
		||||
                <img :src="playlist.uploaderAvatar" loading="lazy" class="uk-border-circle" />
 | 
			
		||||
                {{ playlist.uploader }}</router-link
 | 
			
		||||
            ></b
 | 
			
		||||
        >
 | 
			
		||||
 | 
			
		||||
        <div class="uk-align-right">
 | 
			
		||||
            <b>{{ playlist.videos }} {{ $t("video.videos") }}</b>
 | 
			
		||||
            <br />
 | 
			
		||||
            <a :href="getRssUrl"><font-awesome-icon icon="rss"></font-awesome-icon></a>
 | 
			
		||||
        <div class="grid grid-cols-2">
 | 
			
		||||
            <div>
 | 
			
		||||
                <router-link class="link" :to="playlist.uploaderUrl || '/'">
 | 
			
		||||
                    <img :src="playlist.uploaderAvatar" loading="lazy" class="rounded-full" />
 | 
			
		||||
                    <strong v-text="playlist.uploader" />
 | 
			
		||||
                </router-link>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <div class="right-2vw absolute">
 | 
			
		||||
                    <strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
			
		||||
                    <br />
 | 
			
		||||
                    <a :href="getRssUrl">
 | 
			
		||||
                        <font-awesome-icon icon="rss" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <hr />
 | 
			
		||||
 | 
			
		||||
        <div class="uk-grid uk-grid-xl">
 | 
			
		||||
            <div
 | 
			
		||||
        <div class="video-grid">
 | 
			
		||||
            <VideoItem
 | 
			
		||||
                v-for="video in playlist.relatedStreams"
 | 
			
		||||
                :key="video.url"
 | 
			
		||||
                class="uk-width-1-2 uk-width-1-3@m uk-width-1-4@l uk-width-1-5@xl"
 | 
			
		||||
            >
 | 
			
		||||
                <VideoItem :video="video" height="94" width="168" />
 | 
			
		||||
            </div>
 | 
			
		||||
                :video="video"
 | 
			
		||||
                height="94"
 | 
			
		||||
                width="168"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,205 +1,169 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="uk-flex uk-flex-between uk-flex-middle">
 | 
			
		||||
        <button class="uk-button uk-button-text" @click="$router.go(-1) || $router.push('/')">
 | 
			
		||||
            <font-awesome-icon icon="chevron-left" />  {{ $t("actions.back") }}
 | 
			
		||||
    <div class="flex">
 | 
			
		||||
        <button @click="$router.go(-1) || $router.push('/')">
 | 
			
		||||
            <font-awesome-icon icon="chevron-left" /><span class="ml-1.5" v-text="$t('actions.back')" />
 | 
			
		||||
        </button>
 | 
			
		||||
        <span><h1 v-t="'titles.preferences'" class="uk-text-bold uk-text-center"/></span>
 | 
			
		||||
        <span />
 | 
			
		||||
    </div>
 | 
			
		||||
    <h1 v-t="'titles.preferences'" class="font-bold text-center" />
 | 
			
		||||
    <hr />
 | 
			
		||||
    <h2>SponsorBlock</h2>
 | 
			
		||||
    <p>{{ $t("actions.uses_api_from") }}<a href="https://sponsor.ajay.app/">sponsor.ajay.app</a></p>
 | 
			
		||||
    <label for="chkEnableSponsorblock"><b v-t="'actions.enable_sponsorblock'"/></label>
 | 
			
		||||
    <p>
 | 
			
		||||
        <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 />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkEnableSponsorblock"
 | 
			
		||||
        v-model="sponsorBlock"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        class="checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkSkipSponsors"><b v-t="'actions.skip_sponsors'"/></label>
 | 
			
		||||
    <label for="chkSkipSponsors"><strong v-t="'actions.skip_sponsors'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="chkSkipIntro"><b v-t="'actions.skip_intro'"/></label>
 | 
			
		||||
    <label for="chkSkipIntro"><strong v-t="'actions.skip_intro'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="chkSkipOutro"><b v-t="'actions.skip_outro'"/></label>
 | 
			
		||||
    <label for="chkSkipOutro"><strong v-t="'actions.skip_outro'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="chkSkipPreview"><b v-t="'actions.skip_preview'"/></label>
 | 
			
		||||
    <label for="chkSkipPreview"><strong v-t="'actions.skip_preview'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="chkSkipInteraction"><b v-t="'actions.skip_interaction'"/></label>
 | 
			
		||||
    <label for="chkSkipInteraction"><strong v-t="'actions.skip_interaction'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkSkipInteraction"
 | 
			
		||||
        v-model="skipInteraction"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        class="checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkSkipSelfPromo"><b v-t="'actions.skip_self_promo'"/></label>
 | 
			
		||||
    <label for="chkSkipSelfPromo"><strong v-t="'actions.skip_self_promo'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkSkipSelfPromo"
 | 
			
		||||
        v-model="skipSelfPromo"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <input id="chkSkipSelfPromo" v-model="skipSelfPromo" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkSkipNonMusic"><b v-t="'actions.skip_non_music'"/></label>
 | 
			
		||||
    <label for="chkSkipNonMusic"><strong v-t="'actions.skip_non_music'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkSkipNonMusic"
 | 
			
		||||
        v-model="skipMusicOffTopic"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        class="checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="ddlTheme"><b v-t="'actions.theme'"/></label>
 | 
			
		||||
    <label for="ddlTheme"><strong v-t="'actions.theme'" /></label>
 | 
			
		||||
    <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.dark'" value="dark" />
 | 
			
		||||
        <option v-t="'actions.light'" value="light" />
 | 
			
		||||
    </select>
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkAutoPlayVideo"><b v-t="'actions.autoplay_video'"/></label>
 | 
			
		||||
    <label for="chkAutoPlayVideo"><strong v-t="'actions.autoplay_video'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkAutoPlayVideo"
 | 
			
		||||
        v-model="autoPlayVideo"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <input id="chkAutoPlayVideo" v-model="autoPlayVideo" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkAudioOnly"><b v-t="'actions.audio_only'"/></label>
 | 
			
		||||
    <label for="chkAudioOnly"><strong v-t="'actions.audio_only'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="ddlDefaultQuality"><b v-t="'actions.default_quality'"/></label>
 | 
			
		||||
    <label for="ddlDefaultQuality"><strong v-t="'actions.default_quality'" /></label>
 | 
			
		||||
    <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-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>
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="txtBufferingGoal"><b v-t="'actions.buffering_goal'"/></label>
 | 
			
		||||
    <label for="txtBufferingGoal"><strong v-t="'actions.buffering_goal'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="txtBufferingGoal"
 | 
			
		||||
        v-model="bufferingGoal"
 | 
			
		||||
        class="uk-input uk-width-auto"
 | 
			
		||||
        type="text"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <input id="txtBufferingGoal" v-model="bufferingGoal" class="input w-auto" type="text" @change="onChange($event)" />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="ddlCountrySelection"><b v-t="'actions.country_selection'"/></label>
 | 
			
		||||
    <label for="ddlCountrySelection"><strong v-t="'actions.country_selection'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <select
 | 
			
		||||
        id="ddlCountrySelection"
 | 
			
		||||
        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 id="ddlCountrySelection" v-model="countrySelected" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
        <option v-for="country in countryMap" :key="country.code" :value="country.code" v-text="country.name" />
 | 
			
		||||
    </select>
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="ddlDefaultHomepage"><b v-t="'actions.default_homepage'"/></label>
 | 
			
		||||
    <label for="ddlDefaultHomepage"><strong v-t="'actions.default_homepage'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <select
 | 
			
		||||
        id="ddlDefaultHomepage"
 | 
			
		||||
        v-model="defaultHomepage"
 | 
			
		||||
        class="uk-select uk-width-auto"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    >
 | 
			
		||||
    <select id="ddlDefaultHomepage" v-model="defaultHomepage" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
        <option v-t="'titles.trending'" value="trending" />
 | 
			
		||||
        <option v-t="'titles.feed'" value="feed" />
 | 
			
		||||
    </select>
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkShowComments"><b v-t="'actions.show_comments'"/></label>
 | 
			
		||||
    <label for="chkShowComments"><strong v-t="'actions.show_comments'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="chkMinimizeDescription"><b v-t="'actions.minimize_description_default'"/></label>
 | 
			
		||||
    <label for="chkMinimizeDescription"><strong v-t="'actions.minimize_description_default'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkMinimizeDescription"
 | 
			
		||||
        v-model="minimizeDescription"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        class="checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkStoreWatchHistory"><b v-t="'actions.store_watch_history'"/></label>
 | 
			
		||||
    <label for="chkStoreWatchHistory"><strong v-t="'actions.store_watch_history'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <input
 | 
			
		||||
        id="chkStoreWatchHistory"
 | 
			
		||||
        v-model="watchHistory"
 | 
			
		||||
        class="uk-checkbox"
 | 
			
		||||
        class="checkbox"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    />
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="ddlLanguageSelection"><b v-t="'actions.language_selection'"/></label>
 | 
			
		||||
    <label for="ddlLanguageSelection"><strong v-t="'actions.language_selection'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <select
 | 
			
		||||
        id="ddlLanguageSelection"
 | 
			
		||||
        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 id="ddlLanguageSelection" v-model="selectedLanguage" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
        <option v-for="language in languages" :key="language.code" :value="language.code" v-text="language.name" />
 | 
			
		||||
    </select>
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="ddlEnabledCodecs"><b v-t="'actions.enabled_codecs'"/></label>
 | 
			
		||||
    <label for="ddlEnabledCodecs"><strong v-t="'actions.enabled_codecs'" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <select
 | 
			
		||||
        id="ddlEnabledCodecs"
 | 
			
		||||
        v-model="enabledCodecs"
 | 
			
		||||
        class="uk-select uk-width-auto"
 | 
			
		||||
        multiple
 | 
			
		||||
        @change="onChange($event)"
 | 
			
		||||
    >
 | 
			
		||||
    <select id="ddlEnabledCodecs" v-model="enabledCodecs" class="select w-auto" multiple @change="onChange($event)">
 | 
			
		||||
        <option value="av1">AV1</option>
 | 
			
		||||
        <option value="vp9">VP9</option>
 | 
			
		||||
        <option value="avc">AVC (h.264)</option>
 | 
			
		||||
    </select>
 | 
			
		||||
    <br />
 | 
			
		||||
    <label for="chkDisableLBRY"><b v-t="'actions.disable_lbry'"/></label>
 | 
			
		||||
    <label for="chkDisableLBRY"><strong v-t="'actions.disable_lbry'" /></label>
 | 
			
		||||
    <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 />
 | 
			
		||||
    <label for="chkEnableLBRYProxy"><b v-t="'actions.enable_lbry_proxy'"/></label>
 | 
			
		||||
    <label for="chkEnableLBRYProxy"><strong v-t="'actions.enable_lbry_proxy'" /></label>
 | 
			
		||||
    <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'" />
 | 
			
		||||
    <table class="uk-table">
 | 
			
		||||
    <table class="table">
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ $t("preferences.instance_name") }}</th>
 | 
			
		||||
                <th>{{ $t("preferences.instance_locations") }}</th>
 | 
			
		||||
                <th>{{ $t("preferences.has_cdn") }}</th>
 | 
			
		||||
                <th>{{ $t("preferences.ssl_score") }}</th>
 | 
			
		||||
                <th v-text="$t('preferences.instance_name')" />
 | 
			
		||||
                <th v-text="$t('preferences.instance_locations')" />
 | 
			
		||||
                <th v-text="$t('preferences.has_cdn')" />
 | 
			
		||||
                <th v-text="$t('preferences.ssl_score')" />
 | 
			
		||||
            </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody v-for="instance in instances" :key="instance.name">
 | 
			
		||||
            <tr>
 | 
			
		||||
                <td>{{ instance.name }}</td>
 | 
			
		||||
                <td>{{ instance.locations }}</td>
 | 
			
		||||
                <td>{{ instance.cdn == "Yes" ? $t("actions.yes") : $t("actions.no") }}</td>
 | 
			
		||||
                <td v-text="instance.name" />
 | 
			
		||||
                <td v-text="instance.locations" />
 | 
			
		||||
                <td v-text="$t(`actions.${instance.cdn === 'Yes' ? 'yes' : 'no'}`)" />
 | 
			
		||||
                <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>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
@@ -207,19 +171,10 @@
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <label for="ddlInstanceSelection"
 | 
			
		||||
        ><b>{{ $t("actions.instance_selection") }}:</b></label
 | 
			
		||||
    >
 | 
			
		||||
    <label for="ddlInstanceSelection"><strong v-text="`${$t('actions.instance_selection')}:`" /></label>
 | 
			
		||||
    <br />
 | 
			
		||||
    <select
 | 
			
		||||
        id="ddlInstanceSelection"
 | 
			
		||||
        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 id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
        <option v-for="instance in instances" :key="instance.name" :value="instance.apiurl" v-text="instance.name" />
 | 
			
		||||
    </select>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -326,7 +281,14 @@ export default {
 | 
			
		||||
            this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true);
 | 
			
		||||
            if (localStorage.getItem("selectedSkip") !== null) {
 | 
			
		||||
                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 => {
 | 
			
		||||
                    switch (skip) {
 | 
			
		||||
                        case "sponsor":
 | 
			
		||||
@@ -373,10 +335,8 @@ export default {
 | 
			
		||||
            this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
 | 
			
		||||
            if (this.selectedLanguage != "en") {
 | 
			
		||||
                try {
 | 
			
		||||
                    this.CountryMap = await import("@/utils/CountryMaps/" + this.selectedLanguage + ".json").then(
 | 
			
		||||
                        val => {
 | 
			
		||||
                            this.countryMap = val;
 | 
			
		||||
                        },
 | 
			
		||||
                    this.CountryMap = await import(`../utils/CountryMaps/${this.selectedLanguage}.json`).then(
 | 
			
		||||
                        val => val.default,
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    console.error("Countries not translated into " + this.selectedLanguage);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="uk-vertical-align uk-text-center uk-height-1-1">
 | 
			
		||||
        <form class="uk-panel uk-panel-box">
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
    <div class="text-center">
 | 
			
		||||
        <form class="children:pb-3">
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="username"
 | 
			
		||||
                    class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
 | 
			
		||||
                    class="input"
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    autocomplete="username"
 | 
			
		||||
                    :placeholder="$t('login.username')"
 | 
			
		||||
                    :aria-label="$t('login.username')"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="password"
 | 
			
		||||
                    class="uk-width-1-1 uk-form-large uk-input uk-width-auto"
 | 
			
		||||
                    class="input"
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    autocomplete="password"
 | 
			
		||||
                    :placeholder="$t('login.password')"
 | 
			
		||||
                    :aria-label="$t('login.password')"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="uk-form-row">
 | 
			
		||||
                <a class="uk-width-1-1 uk-button uk-button-large uk-width-auto" @click="register">
 | 
			
		||||
                    {{ $t("titles.register") }}</a
 | 
			
		||||
                >
 | 
			
		||||
            <div>
 | 
			
		||||
                <a class="btn w-auto" @click="register" v-text="$t('titles.register')" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,69 +1,55 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <h1 class="uk-text-center">
 | 
			
		||||
        {{ $route.query.search_query }}
 | 
			
		||||
    </h1>
 | 
			
		||||
    <h1 class="text-center" v-text="$route.query.search_query" />
 | 
			
		||||
 | 
			
		||||
    <label for="ddlSearchFilters"
 | 
			
		||||
        ><b>{{ $t("actions.filter") }}: </b></label
 | 
			
		||||
    >
 | 
			
		||||
    <label for="ddlSearchFilters">
 | 
			
		||||
        <strong v-text="`${$t('actions.filter')}:`" />
 | 
			
		||||
    </label>
 | 
			
		||||
    <select
 | 
			
		||||
        id="ddlSearchFilters"
 | 
			
		||||
        v-model="selectedFilter"
 | 
			
		||||
        default="all"
 | 
			
		||||
        class="uk-select uk-width-auto"
 | 
			
		||||
        style="height: 100%"
 | 
			
		||||
        class="select w-auto"
 | 
			
		||||
        @change="updateResults()"
 | 
			
		||||
    >
 | 
			
		||||
        <option v-for="filter in availableFilters" :key="filter" :value="filter">
 | 
			
		||||
            {{ filter.replace("_", " ") }}
 | 
			
		||||
        </option>
 | 
			
		||||
        <option v-for="filter in availableFilters" :key="filter" :value="filter" v-text="filter.replace('_', ' ')" />
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div v-if="results && results.corrected" style="height: 7vh">
 | 
			
		||||
        {{ $t("search.did_you_mean") }}
 | 
			
		||||
        <i>
 | 
			
		||||
            <router-link :to="{ name: 'SearchResults', query: { search_query: results.suggestion } }">
 | 
			
		||||
                {{ results.suggestion }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
        </i>
 | 
			
		||||
        <span v-text="$t('search.did_you_mean')" />
 | 
			
		||||
 | 
			
		||||
        <router-link :to="{ name: 'SearchResults', query: { search_query: results.suggestion } }">
 | 
			
		||||
            <em v-text="results.suggestion" />
 | 
			
		||||
        </router-link>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div v-if="results" class="uk-grid uk-grid-xl">
 | 
			
		||||
        <div
 | 
			
		||||
            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"
 | 
			
		||||
        >
 | 
			
		||||
    <div v-if="results" class="video-grid">
 | 
			
		||||
        <div v-for="result in results.items" :key="result.url">
 | 
			
		||||
            <VideoItem v-if="shouldUseVideoItem(result)" :video="result" height="94" width="168" />
 | 
			
		||||
            <div v-if="!shouldUseVideoItem(result)" class="uk-text-secondary">
 | 
			
		||||
                <router-link class="uk-text-emphasis" :to="result.url">
 | 
			
		||||
                    <div class="uk-position-relative">
 | 
			
		||||
                        <img style="width: 100%" :src="result.thumbnail" loading="lazy" />
 | 
			
		||||
            <div v-if="!shouldUseVideoItem(result)">
 | 
			
		||||
                <router-link :to="result.url">
 | 
			
		||||
                    <div class="relative">
 | 
			
		||||
                        <img class="w-full" :src="result.thumbnail" loading="lazy" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        {{ result.name }} <font-awesome-icon
 | 
			
		||||
                            v-if="result.verified"
 | 
			
		||||
                            icon="check"
 | 
			
		||||
                        ></font-awesome-icon>
 | 
			
		||||
                        <span v-text="result.name" />
 | 
			
		||||
                        <font-awesome-icon class="ml-1.5" v-if="result.verified" icon="check" />
 | 
			
		||||
                    </p>
 | 
			
		||||
                </router-link>
 | 
			
		||||
                <p v-if="result.description">{{ result.description }}</p>
 | 
			
		||||
                <router-link v-if="result.uploaderUrl" class="uk-link-muted" :to="result.uploaderUrl">
 | 
			
		||||
                <p v-if="result.description" v-text="result.description" />
 | 
			
		||||
                <router-link v-if="result.uploaderUrl" class="link" :to="result.uploaderUrl">
 | 
			
		||||
                    <p>
 | 
			
		||||
                        {{ result.uploader }} <font-awesome-icon
 | 
			
		||||
                            v-if="result.uploaderVerified"
 | 
			
		||||
                            icon="check"
 | 
			
		||||
                        ></font-awesome-icon>
 | 
			
		||||
                        <span v-text="result.uploader" />
 | 
			
		||||
                        <font-awesome-icon class="ml-1.5" v-if="result.uploaderVerified" icon="check" />
 | 
			
		||||
                    </p>
 | 
			
		||||
                </router-link>
 | 
			
		||||
 | 
			
		||||
                <a v-if="result.uploaderName" class="uk-text-muted">{{ result.uploaderName }}</a>
 | 
			
		||||
                <b v-if="result.videos >= 0"
 | 
			
		||||
                    ><br v-if="result.uploaderName" />{{ result.videos }} {{ $t("video.videos") }}</b
 | 
			
		||||
                >
 | 
			
		||||
                <a v-if="result.uploaderName" class="link" v-text="result.uploaderName" />
 | 
			
		||||
                <template v-if="result.videos >= 0">
 | 
			
		||||
                    <br v-if="result.uploaderName" />
 | 
			
		||||
                    <strong v-text="`${result.videos} ${$t('video.videos')}`" />
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <br />
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div
 | 
			
		||||
        class="uk-position-absolute uk-panel uk-box-shadow-large suggestions-container"
 | 
			
		||||
        :style="[{ background: secondaryBackgroundColor }]"
 | 
			
		||||
    >
 | 
			
		||||
        <ul class="uk-list uk-margin-remove uk-text-secondary">
 | 
			
		||||
    <div class="absolute suggestions-container">
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li
 | 
			
		||||
                v-for="(suggestion, i) in searchSuggestions"
 | 
			
		||||
                :key="i"
 | 
			
		||||
                :style="[selected === i ? { background: secondaryForegroundColor } : {}]"
 | 
			
		||||
                class="uk-margin-remove suggestion"
 | 
			
		||||
                class="suggestion"
 | 
			
		||||
                :class="{ 'suggestion-selected': selected === i }"
 | 
			
		||||
                @mouseover="onMouseOver(i)"
 | 
			
		||||
                @mousedown.stop="onClick(i)"
 | 
			
		||||
            >
 | 
			
		||||
                {{ suggestion }}
 | 
			
		||||
            </li>
 | 
			
		||||
                v-text="suggestion"
 | 
			
		||||
            />
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -79,25 +75,30 @@ export default {
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.suggestions-container {
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    transform: translateX(-50%);
 | 
			
		||||
    max-width: 640px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    padding: 5px 0;
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
    @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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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 {
 | 
			
		||||
    padding: 4px 15px;
 | 
			
		||||
}
 | 
			
		||||
@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);
 | 
			
		||||
    }
 | 
			
		||||
    @apply p-y-1;
 | 
			
		||||
}
 | 
			
		||||
</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>
 | 
			
		||||
    <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">
 | 
			
		||||
        <button v-if="authenticated" class="uk-button uk-button-small" style=" margin-right: 0.5rem" type="button">
 | 
			
		||||
            <router-link to="/import">
 | 
			
		||||
                {{ $t("actions.import_from_json") }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
    <div v-if="authenticated">
 | 
			
		||||
        <button class="btn mr-0.5">
 | 
			
		||||
            <router-link to="/import" v-text="$t('actions.import_from_json')" />
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="authenticated"
 | 
			
		||||
            class="uk-button uk-button-small"
 | 
			
		||||
            style="color: white"
 | 
			
		||||
            type="button"
 | 
			
		||||
            @click="exportHandler"
 | 
			
		||||
        >
 | 
			
		||||
            {{ $t("actions.export_to_json") }}
 | 
			
		||||
        </button>
 | 
			
		||||
        <button class="btn" @click="exportHandler" v-text="$t('actions.export_to_json')" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div v-for="subscription in subscriptions" :key="subscription.url" style="text-align: center">
 | 
			
		||||
        <div class="uk-text-primary" :style="[{ background: backgroundColor }]">
 | 
			
		||||
            <a :href="subscription.url">
 | 
			
		||||
                <img :src="subscription.avatar" class="uk-margin-small-right uk-border-circle" width="96" height="96" />
 | 
			
		||||
                <span
 | 
			
		||||
                    class="uk-text-large"
 | 
			
		||||
                    style="width: 30rem; display: inline-block; text-align: center; margin-left: 6rem"
 | 
			
		||||
                    >{{ subscription.name }}</span
 | 
			
		||||
                >
 | 
			
		||||
            </a>
 | 
			
		||||
            <button
 | 
			
		||||
                class="uk-button uk-button-large"
 | 
			
		||||
                style="background: #222; margin-left: 0.5rem; width: 185px"
 | 
			
		||||
                type="button"
 | 
			
		||||
                @click="handleButton(subscription)"
 | 
			
		||||
            >
 | 
			
		||||
                {{ subscription.subscribed ? $t("actions.unsubscribe") : $t("actions.subscribe") }}
 | 
			
		||||
            </button>
 | 
			
		||||
    <div class="grid">
 | 
			
		||||
        <div class="mb-3" v-for="subscription in subscriptions" :key="subscription.url">
 | 
			
		||||
            <div class="flex justify-center place-items-center">
 | 
			
		||||
                <div class="w-full grid grid-cols-3">
 | 
			
		||||
                    <router-link :to="subscription.url" class="col-start-2 block flex text-center font-bold text-4xl">
 | 
			
		||||
                        <img :src="subscription.avatar" class="rounded-full" width="48" height="48" />
 | 
			
		||||
                        <span v-text="subscription.name" />
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="btn !w-min"
 | 
			
		||||
                        @click="handleButton(subscription)"
 | 
			
		||||
                        v-text="$t(`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`)"
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
    </div>
 | 
			
		||||
    <br />
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,10 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <h1 v-t="'titles.trending'" class="uk-text-bold uk-text-center" />
 | 
			
		||||
    <h1 v-t="'titles.trending'" class="font-bold text-center" />
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div class="uk-grid uk-grid-xl">
 | 
			
		||||
        <div
 | 
			
		||||
            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 class="video-grid">
 | 
			
		||||
        <VideoItem v-for="video in videos" :key="video.url" :video="video" height="118" width="210" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,92 +1,83 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="uk-text-secondary" :style="[{ background: backgroundColor }]">
 | 
			
		||||
        <router-link class="uk-text-emphasis" :to="video.url">
 | 
			
		||||
            <img :height="height" :width="width" style="width: 100%" :src="video.thumbnail" alt="" loading="lazy" />
 | 
			
		||||
            <div class="uk-position-relative">
 | 
			
		||||
    <div>
 | 
			
		||||
        <router-link :to="video.url">
 | 
			
		||||
            <img :height="height" :width="width" class="w-full" :src="video.thumbnail" alt="" loading="lazy" />
 | 
			
		||||
            <div class="relative text-sm">
 | 
			
		||||
                <span
 | 
			
		||||
                    v-if="video.duration"
 | 
			
		||||
                    class="uk-label uk-border-rounded uk-position-absolute video-duration"
 | 
			
		||||
                    style="bottom: 5px; right: 5px; background: rgba(0, 0, 0, 0.75); color: white; padding: 0 5px"
 | 
			
		||||
                    >{{ timeFormat(video.duration) }}</span
 | 
			
		||||
                >
 | 
			
		||||
                    class="thumbnail-overlay bottom-5px right-5px px-5px"
 | 
			
		||||
                    v-text="timeFormat(video.duration)"
 | 
			
		||||
                />
 | 
			
		||||
                <span
 | 
			
		||||
                    v-if="video.watched"
 | 
			
		||||
                    class="uk-label uk-border-rounded uk-position-absolute video-duration"
 | 
			
		||||
                    style="bottom: 5px; left: 5px; background: rgba(0, 0, 0, 0.75); color: white; padding: 0 5px"
 | 
			
		||||
                    >{{ $t("video.watched") }}</span
 | 
			
		||||
                >
 | 
			
		||||
                    class="thumbnail-overlay bottom-5px left-5px px-5px"
 | 
			
		||||
                    v-text="$t('video.watched')"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div>
 | 
			
		||||
                <p
 | 
			
		||||
                    style="
 | 
			
		||||
                        padding-top: 0.5rem;
 | 
			
		||||
                        margin-bottom: 0.5rem;
 | 
			
		||||
                        display: -webkit-box;
 | 
			
		||||
                        -webkit-line-clamp: 2;
 | 
			
		||||
                        -webkit-box-orient: vertical;
 | 
			
		||||
                        overflow: hidden;
 | 
			
		||||
                    "
 | 
			
		||||
                    style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
			
		||||
                    class="my-2 overflow-hidden flex link"
 | 
			
		||||
                    :title="video.title"
 | 
			
		||||
                >
 | 
			
		||||
                    {{ video.title }}
 | 
			
		||||
                </p>
 | 
			
		||||
                    v-text="video.title"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        </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
 | 
			
		||||
                :to="video.url + '&listen=1'"
 | 
			
		||||
                :aria-label="'Listen to ' + video.title"
 | 
			
		||||
                :title="'Listen to ' + video.title"
 | 
			
		||||
            >
 | 
			
		||||
                <font-awesome-icon icon="headphones"></font-awesome-icon>
 | 
			
		||||
                <font-awesome-icon icon="headphones" />
 | 
			
		||||
            </router-link>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div style="display: flex; flex-flow: row; height: 15%">
 | 
			
		||||
            <router-link class="uk-link-muted" :to="video.uploaderUrl">
 | 
			
		||||
        <div class="flex">
 | 
			
		||||
            <router-link :to="video.uploaderUrl">
 | 
			
		||||
                <img
 | 
			
		||||
                    v-if="video.uploaderAvatar"
 | 
			
		||||
                    :src="video.uploaderAvatar"
 | 
			
		||||
                    loading="lazy"
 | 
			
		||||
                    :alt="video.uploaderName"
 | 
			
		||||
                    class="uk-border-circle"
 | 
			
		||||
                    style="margin-right: 0.5rem; margin-top: 0.5rem; width: 32px; height: 32px"
 | 
			
		||||
                    class="rounded-full mr-0.5 mt-0.5 w-32px h-32px"
 | 
			
		||||
                    width="68"
 | 
			
		||||
                    height="68"
 | 
			
		||||
                />
 | 
			
		||||
            </router-link>
 | 
			
		||||
 | 
			
		||||
            <div style="width: calc(100% - 32px - 8px)">
 | 
			
		||||
            <div class="w-[calc(100%-32px-1rem)]">
 | 
			
		||||
                <router-link
 | 
			
		||||
                    v-if="video.uploaderUrl && video.uploaderName && !hideChannel"
 | 
			
		||||
                    class="uk-link-muted uk-overflow-hidden"
 | 
			
		||||
                    class="link-secondary overflow-hidden block"
 | 
			
		||||
                    :to="video.uploaderUrl"
 | 
			
		||||
                    :title="video.uploaderName"
 | 
			
		||||
                    style="display: block; width: 90%"
 | 
			
		||||
                >
 | 
			
		||||
                    {{ video.uploaderName }} <font-awesome-icon
 | 
			
		||||
                        v-if="video.uploaderVerified"
 | 
			
		||||
                        icon="check"
 | 
			
		||||
                    ></font-awesome-icon>
 | 
			
		||||
                    <span v-text="video.uploaderName" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" v-if="video.uploaderVerified" icon="check" />
 | 
			
		||||
                </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">
 | 
			
		||||
                        <font-awesome-icon icon="eye"></font-awesome-icon>
 | 
			
		||||
                        {{ numberFormat(video.views) }} •
 | 
			
		||||
                        <font-awesome-icon icon="eye" />
 | 
			
		||||
                        <span class="pl-0.5" v-text="`${numberFormat(video.views)} •`" />
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <span v-if="video.uploadedDate">
 | 
			
		||||
                        {{ video.uploadedDate }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <span v-if="video.uploaded">
 | 
			
		||||
                        {{ timeAgo(video.uploaded) }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </b>
 | 
			
		||||
                    <span v-if="video.uploadedDate" class="pl-0.5" v-text="video.uploadedDate" />
 | 
			
		||||
                    <span v-if="video.uploaded" class="pl-0.5" v-text="timeAgo(video.uploaded)" />
 | 
			
		||||
                </strong>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.thumbnail-overlay {
 | 
			
		||||
    @apply rounded-md absolute bg-black bg-opacity-75;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>{{ $t("actions.loading") }}</div>
 | 
			
		||||
    <div v-text="$t('actions.loading')" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<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
 | 
			
		||||
            ref="videoPlayer"
 | 
			
		||||
            :video="video"
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
        />
 | 
			
		||||
    </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" />
 | 
			
		||||
 | 
			
		||||
        <div v-show="!video.error">
 | 
			
		||||
@@ -21,125 +21,118 @@
 | 
			
		||||
                :selected-auto-play="selectedAutoPlay"
 | 
			
		||||
                :selected-auto-loop="selectedAutoLoop"
 | 
			
		||||
            />
 | 
			
		||||
            <div class="uk-text-bold uk-margin-small-top uk-text-large uk-text-emphasis uk-text-break">
 | 
			
		||||
                {{ video.title }}
 | 
			
		||||
            <div class="font-bold mt-2 text-2xl break-words" v-text="video.title" />
 | 
			
		||||
 | 
			
		||||
            <div class="flex mb-1.5">
 | 
			
		||||
                <span v-text="`${addCommas(video.views)} views`" />
 | 
			
		||||
                <span class="ml-2" v-text="uploadDate" />
 | 
			
		||||
 | 
			
		||||
                <div class="flex items-center relative ml-auto children:ml-2">
 | 
			
		||||
                    <template v-if="video.likes >= 0">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <font-awesome-icon icon="thumbs-up" />
 | 
			
		||||
                            <strong class="ml-2" v-text="addCommas(video.likes)" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <font-awesome-icon icon="thumbs-down" />
 | 
			
		||||
                            <strong class="ml-2" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <template v-if="video.likes < 0">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <strong v-t="'video.ratings_disabled'" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                    <a :href="`https://youtu.be/${getVideoId()}`" class="btn">
 | 
			
		||||
                        <strong v-text="$t('player.watch_on')" />
 | 
			
		||||
                        <font-awesome-icon class="ml-1.5" :icon="['fab', 'youtube']" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <a v-if="video.lbryId" :href="'https://odysee.com/' + video.lbryId" class="btn">
 | 
			
		||||
                        <strong v-text="`${$t('player.watch_on')} LBRY`" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <router-link
 | 
			
		||||
                        :to="toggleListenUrl"
 | 
			
		||||
                        :aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                        :title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                        class="btn"
 | 
			
		||||
                    >
 | 
			
		||||
                        <font-awesome-icon :icon="isListening ? 'tv' : 'headphones'" />
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="uk-flex uk-flex-middle">
 | 
			
		||||
                <div class="uk-margin-small-right">{{ addCommas(video.views) }} views</div>
 | 
			
		||||
                <div class="uk-margin-small-right">{{ uploadDate }}</div>
 | 
			
		||||
                <div class="uk-flex-1"></div>
 | 
			
		||||
                <template v-if="video.likes >= 0">
 | 
			
		||||
                    <div class="uk-margin-small-left">
 | 
			
		||||
                        <font-awesome-icon class="uk-margin-small-right" icon="thumbs-up"></font-awesome-icon>
 | 
			
		||||
                        <b>{{ addCommas(video.likes) }}</b>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="uk-margin-small-left">
 | 
			
		||||
                        <font-awesome-icon class="uk-margin-small-right" icon="thumbs-down"></font-awesome-icon>
 | 
			
		||||
                        <b>{{ video.dislikes >= 0 ? addCommas(video.dislikes) : "?" }}</b>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template v-if="video.likes < 0">
 | 
			
		||||
                    <div class="uk-margin-small-left">
 | 
			
		||||
                        <b v-t="'video.ratings_disabled'" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <a :href="'https://youtu.be/' + getVideoId()" class="uk-margin-small-left uk-button uk-button-small">
 | 
			
		||||
                    <b>{{ $t("player.watch_on") }} </b>
 | 
			
		||||
                    <font-awesome-icon class="uk-margin-small-right" :icon="['fab', 'youtube']"></font-awesome-icon>
 | 
			
		||||
                </a>
 | 
			
		||||
                <a
 | 
			
		||||
                    v-if="video.lbryId"
 | 
			
		||||
                    :href="'https://odysee.com/' + video.lbryId"
 | 
			
		||||
                    class="uk-margin-small-left uk-button uk-button-small"
 | 
			
		||||
                >
 | 
			
		||||
                    <b>{{ $t("player.watch_on") }} LBRY</b>
 | 
			
		||||
                </a>
 | 
			
		||||
                <router-link
 | 
			
		||||
                    :to="toggleListenUrl"
 | 
			
		||||
                    :aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                    :title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                    class="uk-margin-small-left uk-button uk-button-small"
 | 
			
		||||
                >
 | 
			
		||||
                    <font-awesome-icon :icon="isListening ? 'tv' : 'headphones'"></font-awesome-icon>
 | 
			
		||||
                </router-link>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="uk-flex uk-flex-middle uk-margin-small-top">
 | 
			
		||||
                <img :src="video.uploaderAvatar" alt="" loading="lazy" class="uk-border-circle" />
 | 
			
		||||
                <router-link v-if="video.uploaderUrl" class="uk-link uk-margin-small-left" :to="video.uploaderUrl">
 | 
			
		||||
                    {{ video.uploader }} </router-link
 | 
			
		||||
                > <font-awesome-icon v-if="video.uploaderVerified" icon="check"></font-awesome-icon>
 | 
			
		||||
                <div class="uk-flex-1"></div>
 | 
			
		||||
                <button v-if="authenticated" class="uk-button uk-button-small" type="button" @click="subscribeHandler">
 | 
			
		||||
                    {{ subscribed ? $t("actions.unsubscribe") : $t("actions.subscribe") }}
 | 
			
		||||
                </button>
 | 
			
		||||
            <div class="flex">
 | 
			
		||||
                <div class="flex items-center">
 | 
			
		||||
                    <img :src="video.uploaderAvatar" alt="" loading="lazy" class="rounded-full" />
 | 
			
		||||
                    <router-link
 | 
			
		||||
                        v-if="video.uploaderUrl"
 | 
			
		||||
                        class="link ml-1.5"
 | 
			
		||||
                        :to="video.uploaderUrl"
 | 
			
		||||
                        v-text="video.uploader"
 | 
			
		||||
                    />
 | 
			
		||||
                    <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>
 | 
			
		||||
 | 
			
		||||
            <hr />
 | 
			
		||||
 | 
			
		||||
            <a class="uk-button uk-button-small" @click="showDesc = !showDesc">
 | 
			
		||||
                {{ showDesc ? $t("actions.minimize_description") : $t("actions.show_description") }}
 | 
			
		||||
            </a>
 | 
			
		||||
            <button
 | 
			
		||||
                class="btn mb-2"
 | 
			
		||||
                @click="showDesc = !showDesc"
 | 
			
		||||
                v-text="$t(`actions.${showDesc ? 'minimize_description' : 'show_description'}`)"
 | 
			
		||||
            />
 | 
			
		||||
            <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
            <p v-show="showDesc" :style="[{ colour: foregroundColor }]" v-html="purifyHTML(video.description)"></p>
 | 
			
		||||
            <div v-if="showDesc && sponsors && sponsors.segments">
 | 
			
		||||
                {{ $t("video.sponsor_segments") }}: {{ sponsors.segments.length }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <p v-show="showDesc" class="break-words" v-html="purifyHTML(video.description)" />
 | 
			
		||||
            <div
 | 
			
		||||
                v-if="showDesc && sponsors && sponsors.segments"
 | 
			
		||||
                v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
 | 
			
		||||
            />
 | 
			
		||||
        </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)"
 | 
			
		||||
        />
 | 
			
		||||
        <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"
 | 
			
		||||
            ><b>{{ $t("actions.auto_play_next_video") }}:</b></label
 | 
			
		||||
        > 
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkAutoPlay"
 | 
			
		||||
            v-model="selectedAutoPlay"
 | 
			
		||||
            class="uk-checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
        <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="uk-grid">
 | 
			
		||||
            <div v-if="comments" ref="comments" class="uk-width-4-5@xl uk-width-3-4@s uk-width-1">
 | 
			
		||||
                <div
 | 
			
		||||
        <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"
 | 
			
		||||
                    :key="comment.commentId"
 | 
			
		||||
                    class="uk-tile-default uk-align-left uk-width-expand"
 | 
			
		||||
                    :style="[{ background: backgroundColor }]"
 | 
			
		||||
                >
 | 
			
		||||
                    <Comment :comment="comment" :uploader="video.uploader" :video-id="getVideoId()" />
 | 
			
		||||
                </div>
 | 
			
		||||
                    :comment="comment"
 | 
			
		||||
                    :uploader="video.uploader"
 | 
			
		||||
                    :video-id="getVideoId()"
 | 
			
		||||
                />
 | 
			
		||||
            </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">
 | 
			
		||||
                <a class="uk-button uk-button-small uk-margin-small-bottom uk-hidden@s" @click="showRecs = !showRecs">
 | 
			
		||||
                    {{ showRecs ? $t("actions.minimize_recommendations") : $t("actions.show_recommendations") }}
 | 
			
		||||
                </a>
 | 
			
		||||
                <div
 | 
			
		||||
                    v-for="related in video.relatedStreams"
 | 
			
		||||
                    v-show="showRecs || !smallView"
 | 
			
		||||
                    :key="related.url"
 | 
			
		||||
                    class="uk-tile-default uk-width-auto"
 | 
			
		||||
                    :style="[{ background: backgroundColor }]"
 | 
			
		||||
                >
 | 
			
		||||
                    <VideoItem :video="related" height="94" width="168" />
 | 
			
		||||
            <div v-if="video" class="order-first sm:order-last">
 | 
			
		||||
                <a
 | 
			
		||||
                    class="btn mb-2 sm:hidden"
 | 
			
		||||
                    @click="showRecs = !showRecs"
 | 
			
		||||
                    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"
 | 
			
		||||
                        :key="related.url"
 | 
			
		||||
                        :video="related"
 | 
			
		||||
                        height="94"
 | 
			
		||||
                        width="168"
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
                <hr class="uk-hidden@s" />
 | 
			
		||||
                <hr class="sm:hidden" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -207,7 +200,7 @@ export default {
 | 
			
		||||
                    var tx = window.db.transaction("watch_history", "readwrite");
 | 
			
		||||
                    var store = tx.objectStore("watch_history");
 | 
			
		||||
                    var request = store.get(videoId);
 | 
			
		||||
                    request.onsuccess = function(event) {
 | 
			
		||||
                    request.onsuccess = function (event) {
 | 
			
		||||
                        var video = event.target.result;
 | 
			
		||||
                        if (video) {
 | 
			
		||||
                            video.watchedAt = Date.now();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								src/main.js
									
									
									
									
									
								
							@@ -34,9 +34,7 @@ library.add(
 | 
			
		||||
    faTv,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
import("uikit/dist/css/uikit-core.css");
 | 
			
		||||
 | 
			
		||||
import router from "@/router/router";
 | 
			
		||||
import router from "@/router/router.js";
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
 | 
			
		||||
import DOMPurify from "dompurify";
 | 
			
		||||
@@ -49,6 +47,7 @@ TimeAgo.addDefaultLocale(en);
 | 
			
		||||
 | 
			
		||||
import { createI18n } from "vue-i18n";
 | 
			
		||||
import enLocale from "@/locales/en.json";
 | 
			
		||||
import "windi.css";
 | 
			
		||||
 | 
			
		||||
const timeAgo = new TimeAgo("en-US");
 | 
			
		||||
 | 
			
		||||
@@ -56,8 +55,8 @@ import("./registerServiceWorker");
 | 
			
		||||
 | 
			
		||||
const mixin = {
 | 
			
		||||
    methods: {
 | 
			
		||||
        timeFormat: function(duration) {
 | 
			
		||||
            var pad = function(num, size) {
 | 
			
		||||
        timeFormat: function (duration) {
 | 
			
		||||
            var pad = function (num, size) {
 | 
			
		||||
                return ("000" + num).slice(size * -1);
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
@@ -94,7 +93,7 @@ const mixin = {
 | 
			
		||||
            num = parseInt(num);
 | 
			
		||||
            return num.toLocaleString("en-US");
 | 
			
		||||
        },
 | 
			
		||||
        fetchJson: function(url, params, options) {
 | 
			
		||||
        fetchJson: function (url, params, options) {
 | 
			
		||||
            if (params) {
 | 
			
		||||
                url = new URL(url);
 | 
			
		||||
                for (var param in params) url.searchParams.set(param, params[param]);
 | 
			
		||||
@@ -147,18 +146,11 @@ const mixin = {
 | 
			
		||||
        apiUrl() {
 | 
			
		||||
            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() {
 | 
			
		||||
            return this.getPreferenceString("authToken" + this.hashCode(this.apiUrl()));
 | 
			
		||||
        },
 | 
			
		||||
        hashCode(s) {
 | 
			
		||||
            return s.split("").reduce(function(a, b) {
 | 
			
		||||
            return s.split("").reduce(function (a, b) {
 | 
			
		||||
                a = (a << 5) - a + b.charCodeAt(0);
 | 
			
		||||
                return a & a;
 | 
			
		||||
            }, 0);
 | 
			
		||||
@@ -170,7 +162,7 @@ const mixin = {
 | 
			
		||||
            const regex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
 | 
			
		||||
            if (!string) return "";
 | 
			
		||||
            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) {
 | 
			
		||||
@@ -179,7 +171,7 @@ const mixin = {
 | 
			
		||||
                var store = tx.objectStore("watch_history");
 | 
			
		||||
                videos.map(async video => {
 | 
			
		||||
                    var request = store.get(video.url.substr(-11));
 | 
			
		||||
                    request.onsuccess = function(event) {
 | 
			
		||||
                    request.onsuccess = function (event) {
 | 
			
		||||
                        if (event.target.result) {
 | 
			
		||||
                            video.watched = true;
 | 
			
		||||
                        }
 | 
			
		||||
@@ -189,20 +181,8 @@ const mixin = {
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        backgroundColor() {
 | 
			
		||||
            return this.getEffectiveTheme() === "light" ? "#fff" : "#0b0e0f";
 | 
			
		||||
        },
 | 
			
		||||
        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";
 | 
			
		||||
        theme() {
 | 
			
		||||
            return this.getPreferenceString("theme", "dark");
 | 
			
		||||
        },
 | 
			
		||||
        authenticated(_this) {
 | 
			
		||||
            return _this.getAuthToken() !== undefined;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,7 @@
 | 
			
		||||
/* eslint-disable no-console */
 | 
			
		||||
 | 
			
		||||
import { register } from "register-service-worker";
 | 
			
		||||
import { registerSW } from "virtual:pwa-register";
 | 
			
		||||
 | 
			
		||||
if (process.env.NODE_ENV === "production") {
 | 
			
		||||
    register(`/service-worker.js`, {
 | 
			
		||||
        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);
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    registerSW();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ const routes = [
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
    history: createWebHistory(),
 | 
			
		||||
    routes,
 | 
			
		||||
    scrollBehavior: function(_to, _from, savedPosition) {
 | 
			
		||||
    scrollBehavior: function (_to, _from, savedPosition) {
 | 
			
		||||
        return savedPosition ? savedPosition : window.scrollTo(0, 0);
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
// 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 = {
 | 
			
		||||
    generate_dash_file_from_formats(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) {
 | 
			
		||||
        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",
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user