Compare commits
17 Commits
e5e31aed5e
...
b9cea6e4cd
Author | SHA1 | Date |
---|---|---|
github-actions[bot] | b9cea6e4cd | |
Priit Jõerüüt | 9727295a16 | |
Matthaiks | debc10a899 | |
Bnyro | a43d56984d | |
Bnyro | cdb9d0188a | |
Bnyro | c30062eb94 | |
Bnyro | adeb0a92bd | |
Bnyro | afd5c5b3b8 | |
Bnyro | ba4e946dc2 | |
Bnyro | ca5e39dfd8 | |
Bnyro | c31f9fbb54 | |
Bnyro | 9762542596 | |
Bnyro | 1440eea4a0 | |
Bnyro | c0a9b16aa8 | |
github-actions[bot] | 3b18da84b1 | |
Kārlis Korlašs | 1262dfa596 | |
renovate[bot] | eccf4d1f78 |
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)));
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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"];
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}",
|
||||
|
|
Loading…
Reference in New Issue