Compare commits

...

22 Commits

Author SHA1 Message Date
Kavin 873b221df4
Merge c8f9ae3441 into b9cea6e4cd 2024-05-16 21:42:55 +05:00
github-actions[bot] b9cea6e4cd
Merge pull request #3623 from TeamPiped/weblate
Translations update from Hosted Weblate
2024-05-16 16:28:10 +00:00
Priit Jõerüüt 9727295a16
Translated using Weblate (Estonian)
Currently translated at 100.0% (210 of 210 strings)

Translation: Piped/Frontend
Translate-URL: https://hosted.weblate.org/projects/piped/frontend/et/
2024-05-16 18:26:28 +02:00
Matthaiks debc10a899
Translated using Weblate (Polish)
Currently translated at 100.0% (210 of 210 strings)

Translation: Piped/Frontend
Translate-URL: https://hosted.weblate.org/projects/piped/frontend/pl/
2024-05-16 18:26:28 +02:00
Bnyro a43d56984d
Merge pull request #3622 from Bnyro/master
feat: custom playback speed option
2024-05-16 18:26:20 +02:00
Bnyro cdb9d0188a feat: custom playback speed option 2024-05-16 18:24:43 +02:00
Bnyro c30062eb94
Merge pull request #3610 from TeamPiped/renovate/dompurify-3.x
fix(deps): update dependency dompurify to v3.1.3
2024-05-16 17:18:56 +02:00
Bnyro adeb0a92bd
Merge pull request #3621 from Bnyro/master
fix: links in comments are not clickable
2024-05-16 17:18:39 +02:00
Bnyro afd5c5b3b8 fix: links in comments are not clickable 2024-05-16 17:18:19 +02:00
Bnyro ba4e946dc2
Merge pull request #3620 from Bnyro/master
feat: show tooltips when creator replied / liked comment
2024-05-16 17:04:35 +02:00
Bnyro ca5e39dfd8 feat: show tooltips when creator replied / liked comment 2024-05-16 17:04:08 +02:00
Bnyro c31f9fbb54
Merge pull request #3619 from Bnyro/master
fix(register): input height doesn't match view password icon
2024-05-16 16:58:07 +02:00
Bnyro 9762542596 fix(register): input height doesn't match view password icon 2024-05-16 16:57:47 +02:00
Bnyro 1440eea4a0
Merge pull request #3618 from Bnyro/master
refactor: use browser inbuilt tooltip on login/register page
2024-05-16 16:57:40 +02:00
Bnyro c0a9b16aa8 refactor: use browser inbuilt tooltip on login/register page 2024-05-16 16:51:04 +02:00
github-actions[bot] 3b18da84b1
Merge pull request #3617 from TeamPiped/weblate
Translations update from Hosted Weblate
2024-05-16 13:03:25 +00:00
Kārlis Korlašs 1262dfa596
Translated using Weblate (Latvian)
Currently translated at 100.0% (208 of 208 strings)

Translation: Piped/Frontend
Translate-URL: https://hosted.weblate.org/projects/piped/frontend/lv/
2024-05-16 15:01:51 +02:00
github-actions[bot] e5e31aed5e
Merge pull request #3615 from TeamPiped/weblate
Translations update from Hosted Weblate
2024-05-15 00:03:04 +00:00
Ghost of Sparta 6513170ab2
Translated using Weblate (Hungarian)
Currently translated at 100.0% (208 of 208 strings)

Translation: Piped/Frontend
Translate-URL: https://hosted.weblate.org/projects/piped/frontend/hu/
2024-05-15 02:01:46 +02:00
maboroshin a996997e28
Translated using Weblate (Japanese)
Currently translated at 100.0% (208 of 208 strings)

Translation: Piped/Frontend
Translate-URL: https://hosted.weblate.org/projects/piped/frontend/ja/
2024-05-15 02:01:45 +02:00
renovate[bot] eccf4d1f78
fix(deps): update dependency dompurify to v3.1.3 2024-05-11 12:44:43 +00:00
Kavin c8f9ae3441
Add reload prompt for when a new update. 2023-06-19 03:28:54 +01:00
21 changed files with 124 additions and 96 deletions

View File

@ -11,7 +11,7 @@
"lint": "eslint --fix --color --ignore-path .gitignore --ext .js,.vue ."
},
"dependencies": {
"dompurify": "3.1.2",
"dompurify": "3.1.3",
"fast-xml-parser": "4.3.6",
"hotkeys-js": "3.13.7",
"javascript-time-ago": "2.5.10",

View File

@ -6,8 +6,8 @@ settings:
dependencies:
dompurify:
specifier: 3.1.2
version: 3.1.2
specifier: 3.1.3
version: 3.1.3
fast-xml-parser:
specifier: 4.3.6
version: 4.3.6
@ -2937,8 +2937,8 @@ packages:
esutils: 2.0.3
dev: true
/dompurify@3.1.2:
resolution: {integrity: sha512-hLGGBI1tw5N8qTELr3blKjAML/LY4ANxksbS612UiJyDfyf/2D092Pvm+S7pmeTGJRqvlJkFzBoHBQKgQlOQVg==}
/dompurify@3.1.3:
resolution: {integrity: sha512-5sOWYSNPaxz6o2MUPvtyxTTqR4D3L77pr5rUQoWgD5ROQtVIZQgJkXbo1DLlK3vj11YGw5+LnF4SYti4gZmwng==}
dev: false
/duplexer@0.1.2:

View File

@ -9,6 +9,7 @@
</router-view>
</div>
<ReloadPrompt />
<FooterComponent />
</div>
</template>
@ -16,6 +17,7 @@
<script>
import NavBar from "./components/NavBar.vue";
import FooterComponent from "./components/FooterComponent.vue";
import ReloadPrompt from "./components/ReloadPrompt.vue";
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
@ -23,6 +25,7 @@ export default {
components: {
NavBar,
FooterComponent,
ReloadPrompt,
},
data() {
return {

View File

@ -3,7 +3,7 @@
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-if="showFullText" class="contentText" v-html="fullText()" />
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-else v-html="colapsedText()" />
<span v-else v-html="collapsedText()" />
<span v-if="text.length > visibleLimit && !showFullText">...</span>
<button
v-if="text.length > visibleLimit"
@ -38,7 +38,7 @@ export default {
fullText() {
return purifyHTML(rewriteDescription(this.text));
},
colapsedText() {
collapsedText() {
return purifyHTML(rewriteDescription(this.text.slice(0, this.visibleLimit)));
},
},

View File

@ -33,8 +33,13 @@
<div class="comment-footer my-1 flex items-center gap-3">
<div class="i-fa6-solid:thumbs-up" />
<span v-text="numberFormat(comment.likeCount)" />
<i v-if="comment.hearted" class="i-fa6-solid:heart" />
<img v-if="comment.creatorReplied" :src="uploaderAvatarUrl" class="h-5 w-5 rounded-full" />
<i v-if="comment.hearted" class="i-fa6-solid:heart" :title="$t('actions.creator_liked')" />
<img
v-if="comment.creatorReplied"
:src="uploaderAvatarUrl"
class="h-5 w-5 rounded-full"
:title="$t('actions.creator_replied')"
/>
</div>
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
<div class="cursor-pointer" @click="loadReplies">

View File

@ -1,7 +1,7 @@
<template>
<div class="flex justify-center">
<h1 v-t="'titles.login'" class="my-4 text-center font-bold" />
<TooltipIcon class="mb-6" icon="i-fa6-solid:circle-info" :tooltip="$t('info.login_note')" />
<i class="i-fa6-solid:circle-info ml-2 mt-6 cursor-pointer" :title="$t('info.login_note')" />
</div>
<hr />
<div class="w-full flex items-center justify-center text-center">
@ -36,12 +36,7 @@
</template>
<script>
import TooltipIcon from "./TooltipIcon.vue";
export default {
components: {
TooltipIcon,
},
data() {
return {
username: null,

View File

@ -6,10 +6,10 @@
<CollapsableText v-if="playlist?.description" :text="playlist.description" />
<div class="mt-1 flex <md:flex-col md:items-center justify-between">
<div class="mt-1 flex justify-between <md:flex-col md:items-center">
<div>
<router-link class="link flex items-center gap-3" :to="playlist.uploaderUrl || '/'">
<img loading="lazy" :src="playlist.uploaderAvatar" class="rounded-full h-12" />
<img loading="lazy" :src="playlist.uploaderAvatar" class="h-12 rounded-full" />
<strong v-text="playlist.uploader" />
</router-link>
</div>

View File

@ -1,7 +1,7 @@
<template>
<div class="flex justify-center">
<h1 v-t="'titles.register'" class="my-4 text-center font-bold" />
<TooltipIcon class="mb-6" icon="i-fa6-solid:circle-info" :tooltip="$t('info.register_note')" />
<i class="i-fa6-solid:circle-info ml-2 mt-6 cursor-pointer" :title="$t('info.register_note')" />
</div>
<hr />
<div class="flex flex-col items-center justify-center text-center">
@ -20,7 +20,7 @@
<div class="flex justify-center">
<input
v-model="password"
class="input w-full"
class="input h-auto w-full"
:type="showPassword ? 'text' : 'password'"
autocomplete="password"
:placeholder="$t('login.password')"
@ -34,7 +34,7 @@
<div class="flex justify-center">
<input
v-model="passwordConfirm"
class="input w-full"
class="input h-auto w-full"
:type="showConfirmPassword ? 'text' : 'password'"
autocomplete="password"
:placeholder="$t('login.password_confirm')"
@ -65,10 +65,9 @@
<script>
import { isEmail } from "../utils/Misc.js";
import ConfirmModal from "./ConfirmModal.vue";
import TooltipIcon from "./TooltipIcon.vue";
export default {
components: { ConfirmModal, TooltipIcon },
components: { ConfirmModal },
data() {
return {
username: null,

View File

@ -0,0 +1,33 @@
<script setup>
import { useRegisterSW } from "virtual:pwa-register/vue";
const { offlineReady, needRefresh, updateServiceWorker } = useRegisterSW();
const close = async () => {
offlineReady.value = false;
needRefresh.value = false;
};
</script>
<template>
<div v-if="offlineReady || needRefresh" class="pwa-toast" role="alert">
<div class="message">
<span v-if="offlineReady"> App ready to work offline </span>
<span v-else> New content available, click on reload button to update. </span>
</div>
<button v-if="needRefresh" @click="updateServiceWorker()">Reload</button>
<button @click="close">Close</button>
</div>
</template>
<style>
.pwa-toast {
@apply fixed right-0 bottom-0 m-4 p-3 border border-gray-500 rounded shadow bg-white text-gray-700;
}
.pwa-toast .message {
@apply margin-bottom-2;
}
.pwa-toast button {
@apply border border-solid border-gray-500 rounded px-2 py-1;
}
</style>

View File

@ -1,29 +0,0 @@
<template>
<div id="container" class="m-2 self-center">
<div :class="icon" class="cursor-pointer"></div>
<p id="tooltip" class="absolute mr-[20vw] mt-2 hidden rounded-l bg-gray-800 px-2 py-1 text-gray-200">
{{ tooltip }}
</p>
</div>
</template>
<script>
export default {
props: {
icon: {
type: String, // the class name of a font awesome icon
required: true,
},
tooltip: {
type: String,
required: true,
},
},
};
</script>
<style>
#container:hover #tooltip {
display: block;
}
</style>

View File

@ -31,16 +31,32 @@
class="absolute top-8 rounded bg-black/80 p-2 text-lg backdrop-blur-sm"
/>
</div>
<ModalComponent v-if="showSpeedModal" @close="showSpeedModal = false">
<h2 v-t="'actions.playback_speed'" />
<div class="flex flex-col">
<input
v-model="playbackSpeedInput"
class="input my-3"
type="text"
:placeholder="$t('actions.playback_speed')"
@keyup.enter="setSpeedFromInput()"
/>
<button v-t="'actions.okay'" class="btn ml-auto w-min" @click="setSpeedFromInput()" />
</div>
</ModalComponent>
</template>
<script>
import "shaka-player/dist/controls.css";
import { parseTimeParam } from "@/utils/Misc";
import ModalComponent from "./ModalComponent.vue";
const shaka = import("shaka-player/dist/shaka-player.ui.js");
const hotkeys = import("hotkeys-js");
export default {
components: { ModalComponent },
props: {
video: {
type: Object,
@ -66,6 +82,8 @@ export default {
destroying: false,
inSegment: false,
isHoveringTimebar: false,
showSpeedModal: false,
playbackSpeedInput: null,
currentTime: 0,
seekbarPadding: 2,
error: 0,
@ -106,7 +124,7 @@ export default {
this.hotkeysPromise.then(() => {
var self = this;
this.$hotkeys(
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.,alt+p,return,.,,",
"f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+s,shift+,,shift+.,alt+p,return,.,,",
function (e, handler) {
const videoEl = self.$refs.videoEl;
switch (handler.key) {
@ -196,11 +214,14 @@ export default {
self.$emit("navigateNext");
e.preventDefault();
break;
case "shift+s":
self.showSpeedModal = true;
break;
case "shift+,":
self.$player.trickPlay(Math.max(videoEl.playbackRate - 0.25, 0.25));
self.adjustPlaybackSpeed(videoEl.playbackRate - 0.25);
break;
case "shift+.":
self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
self.adjustPlaybackSpeed(videoEl.playbackRate + 0.25);
break;
case "alt+p":
document.pictureInPictureElement
@ -651,7 +672,19 @@ export default {
this.$refs.videoEl.currentTime = time;
}
},
adjustPlaybackSpeed(newSpeed) {
const normalizedSpeed = Math.min(4, Math.max(0.25, newSpeed));
this.$player.trickPlay(normalizedSpeed);
},
setSpeedFromInput() {
try {
const newSpeed = Number(this.playbackSpeedInput);
this.adjustPlaybackSpeed(newSpeed);
} catch (err) {
alert(this.$t("actions.invalid_input"));
}
this.showSpeedModal = false;
},
updateMarkers() {
const markers = this.$refs.container.querySelector(".shaka-ad-markers");
const array = ["to right"];

View File

@ -546,33 +546,13 @@ export default {
this.fetchSponsors().then(data => (this.sponsors = data));
},
async getComments() {
this.fetchComments().then(data => {
this.rewriteComments(data.comments);
this.comments = data;
});
this.comments = await this.fetchComments();
},
async fetchSubscribedStatus() {
if (!this.channelId) return;
this.subscribed = await this.fetchSubscriptionStatus(this.channelId);
},
rewriteComments(data) {
data.forEach(comment => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(comment.commentText, "text/html");
xmlDoc.querySelectorAll("a").forEach(elem => {
if (!elem.innerText.match(/(?:[\d]{1,2}:)?(?:[\d]{1,2}):(?:[\d]{1,2})/))
elem.outerHTML = elem.getAttribute("href");
});
comment.commentText = xmlDoc
.querySelector("body")
.innerHTML.replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
.replaceAll(
/(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm,
"/watch?v=$1",
);
});
},
subscribeHandler() {
this.toggleSubscriptionState(this.channelId, this.subscribed).then(success => {
if (success) this.subscribed = !this.subscribed;
@ -616,7 +596,6 @@ export default {
}).then(json => {
this.comments.nextpage = json.nextpage;
this.loading = false;
this.rewriteComments(json.comments);
this.comments.comments = this.comments.comments.concat(json.comments);
});
}

View File

@ -156,7 +156,11 @@
"concurrent_prefetch_limit": "Concurrent Stream Prefetch Limit",
"customize": "Customize",
"invalid_url": "Invalid URL!",
"add": "Add"
"add": "Add",
"creator_replied": "Creator replied",
"creator_liked": "Creator liked",
"playback_speed": "Playback speed",
"invalid_input": "Invalid input"
},
"comment": {
"pinned_by": "Pinned by {author}",
@ -227,4 +231,4 @@
"register_note": "Register an account for this Piped instance. This will allow you to sync your subscriptions and playlists with your account, so they're stored on the server side. You can use all features without an account, but all data will be stored in your browser's local cache. Please make sure you do NOT use an email address as your username and choose a secure password that you do not use elsewhere.",
"login_note": "Log in with an account created on this instance."
}
}
}

View File

@ -156,7 +156,9 @@
"customize": "Kohanda",
"invalid_url": "Vigane URL!",
"add": "Lisa",
"delete_group_confirm": "Kas kustutame selle grupi?"
"delete_group_confirm": "Kas kustutame selle grupi?",
"creator_replied": "Autor vastas",
"creator_liked": "Autorile meeldis see"
},
"preferences": {
"has_cdn": "CDN'i olek?",

View File

@ -151,7 +151,8 @@
"download_frame": "Keret letöltése",
"customize": "Testreszab",
"invalid_url": "Érvénytelen URL!",
"add": "Hozzáadás"
"add": "Hozzáadás",
"delete_group_confirm": "Törli ezt a csoportot?"
},
"video": {
"ratings_disabled": "Értékelések Letiltva",

View File

@ -155,7 +155,8 @@
"concurrent_prefetch_limit": "同時に先読みするストリーム数上限",
"add": "追加",
"invalid_url": "無効なURLです",
"customize": "追加"
"customize": "追加",
"delete_group_confirm": "このグループを削除しますか?"
},
"comment": {
"pinned_by": "{author} によって固定",

View File

@ -127,7 +127,12 @@
"clone_playlist": "Klonēt Atskaņošanas Saturu",
"uses_api_from": "Izmanto API no ",
"add_to_playlist": "Pievienot Atskaņošanas Sarakstam",
"instances_not_shown": "Publiskās instances, kas šeit nav redzamas, pašlaik nav pieejamas."
"instances_not_shown": "Publiskās instances, kas šeit nav redzamas, pašlaik nav pieejamas.",
"delete_group_confirm": "Vai vēlaties dzēst šo grupu?",
"concurrent_prefetch_limit": "Vienlaicīgu Straumju Ielādes Limits",
"customize": "Pielāgot",
"invalid_url": "Nederīgs URL!",
"add": "Pievienot"
},
"search": {
"all": "YouTube: Visi",
@ -161,7 +166,9 @@
"playlists": "Atskaņošanas saraksts",
"register": "Reģistrēties",
"player": "Atskaņotājs",
"dearrow": "DeArrow"
"dearrow": "DeArrow",
"albums": "Albumi",
"custom_instances": "Pielāgotas instances"
},
"video": {
"all": "Visi",
@ -207,7 +214,9 @@
"has_cdn": "Vai ir satura piegādes tīkls?",
"instance_name": "Instances Nosaukums",
"registered_users": "Reģistrētie Lietotāji",
"instance_locations": "Instances Atrašanās Vietas"
"instance_locations": "Instances Atrašanās Vietas",
"uptime_30d": "Darbspējas laiks (30d)",
"api_url": "Api URL"
},
"login": {
"username": "Lietotājvārds",

View File

@ -156,7 +156,9 @@
"customize": "Dostosuj",
"invalid_url": "Nieprawidłowy adres URL!",
"add": "Dodaj",
"delete_group_confirm": "Usunąć tę grupę?"
"delete_group_confirm": "Usunąć tę grupę?",
"creator_replied": "Twórca odpowiedział",
"creator_liked": "Twórca polubił"
},
"comment": {
"pinned_by": "Przypięty przez {author}",

View File

@ -15,8 +15,6 @@ import "uno.css";
const timeAgo = new TimeAgo("en-US");
import("./registerServiceWorker");
const mixin = {
methods: {
timeFormat: function (duration) {

View File

@ -1,7 +0,0 @@
/* eslint-disable no-console */
import { registerSW } from "virtual:pwa-register";
if (process.env.NODE_ENV === "production") {
registerSW();
}

View File

@ -19,7 +19,7 @@ export default defineConfig({
targets: ["defaults", "not IE 11"],
}),
VitePWA({
registerType: "autoUpdate",
registerType: "prompt",
workbox: {
globPatterns: [
"**/*.{css,html}",