mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2025-10-31 12:42:07 +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
	 Kavin
					Kavin