mirror of
https://github.com/TeamPiped/Piped.git
synced 2026-02-02 17:09:56 +00:00
Merge from main + Update efy submodule
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
<font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="whitespace-pre-wrap" v-html="urlify(comment.commentText)" />
|
||||
<div class="whitespace-pre-wrap" v-html="purifyHTML(comment.commentText)" />
|
||||
<template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
|
||||
<div @click="loadReplies" class="cursor-pointer">
|
||||
<a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
|
||||
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
@apply w-min m-auto min-w-[20vw] relative;
|
||||
@apply w-300rem m-auto max-w-[100vw] relative;
|
||||
}
|
||||
|
||||
.modal-container > button {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<ModalComponent>
|
||||
<h2 v-t="'actions.select_playlist'" />
|
||||
<select class="select w-full mt-3" v-model="selectedPlaylist">
|
||||
<h4 v-t="'actions.select_playlist'" class="mb-2" />
|
||||
<select class="select w-full mb-2" v-model="selectedPlaylist">
|
||||
<option v-for="playlist in playlists" :value="playlist.id" :key="playlist.id" v-text="playlist.name" />
|
||||
</select>
|
||||
<div class="flex justify-end mt-3">
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="btn"
|
||||
@click="handleClick(selectedPlaylist)"
|
||||
|
||||
@@ -149,10 +149,7 @@ export default {
|
||||
version: 1,
|
||||
playlists: [],
|
||||
};
|
||||
let tasks = [];
|
||||
for (var i = 0; i < this.playlists.length; i++) {
|
||||
tasks.push(this.fetchPlaylistJson(this.playlists[i].id));
|
||||
}
|
||||
let tasks = this.playlists.map(playlist => this.fetchPlaylistJson(playlist.id));
|
||||
json.playlists = await Promise.all(tasks);
|
||||
this.download(JSON.stringify(json), "playlists.json", "application/json");
|
||||
},
|
||||
@@ -165,31 +162,44 @@ export default {
|
||||
// as Invidious supports public and private playlists
|
||||
visibility: "private",
|
||||
// list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
|
||||
videos: [],
|
||||
videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
|
||||
};
|
||||
for (var i = 0; i < playlist.relatedStreams.length; i++) {
|
||||
playlistJson.videos.push("https://youtube.com" + playlist.relatedStreams[i].url);
|
||||
}
|
||||
return playlistJson;
|
||||
},
|
||||
async importPlaylists() {
|
||||
const file = this.$refs.fileSelector.files[0];
|
||||
let text = await file.text();
|
||||
let playlists = JSON.parse(text).playlists;
|
||||
if (!playlists.length) {
|
||||
let tasks = [];
|
||||
// list of playlists exported from Piped
|
||||
if (text.includes("playlists")) {
|
||||
let playlists = JSON.parse(text).playlists;
|
||||
if (!playlists.length) {
|
||||
alert(this.$t("actions.no_valid_playlists"));
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < playlists.length; i++) {
|
||||
tasks.push(this.createPlaylistWithVideos(playlists[i]));
|
||||
}
|
||||
// CSV from Google Takeout
|
||||
} else if (file.name.slice(-4).toLowerCase() == ".csv") {
|
||||
const lines = text.split("\n");
|
||||
const playlist = {
|
||||
name: lines[1].split(",")[4],
|
||||
videos: lines
|
||||
.slice(4, lines.length)
|
||||
.filter(line => line != "")
|
||||
.map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
|
||||
};
|
||||
tasks.push(this.createPlaylistWithVideos(playlist));
|
||||
} else {
|
||||
alert(this.$t("actions.no_valid_playlists"));
|
||||
return;
|
||||
}
|
||||
let tasks = [];
|
||||
for (var i = 0; i < playlists.length; i++) {
|
||||
tasks.push(this.createPlaylistWithVideos(playlists[i]));
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
window.location.reload();
|
||||
},
|
||||
async createPlaylistWithVideos(playlist) {
|
||||
let newPlaylist = await this.createPlaylist(playlist.name);
|
||||
console.log(newPlaylist);
|
||||
let videoIds = playlist.videos.map(url => url.substr(-11));
|
||||
await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
|
||||
},
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
<template>
|
||||
<ModalComponent>
|
||||
<h2 v-t="'actions.share'" />
|
||||
<div class="flex justify-between mt-4">
|
||||
<label v-t="'actions.with_timecode'" for="withTimeCode" />
|
||||
<input id="withTimeCode" type="checkbox" v-model="withTimeCode" @change="onChange" />
|
||||
</div>
|
||||
<div class="flex justify-between mt-2">
|
||||
<h4 v-t="'actions.share'" />
|
||||
<div class="flex justify-between mt-2 mb-2">
|
||||
<label v-t="'actions.piped_link'" />
|
||||
<input type="checkbox" v-model="pipedLink" @change="onChange" />
|
||||
</div>
|
||||
<div class="flex justify-between mt-2">
|
||||
<label v-t="'actions.time_code'" />
|
||||
<input class="input w-300" type="text" v-model="timeStamp" />
|
||||
<div v-if="this.hasPlaylist" class="flex justify-between">
|
||||
<label v-t="'actions.with_playlist'" />
|
||||
<input type="checkbox" v-model="withPlaylist" @change="onChange" />
|
||||
</div>
|
||||
<a :href="generatedLink" target="_blank"><h6 class="mb-2" v-text="generatedLink" /></a>
|
||||
<div class="flex justify-between">
|
||||
<label v-t="'actions.with_timecode'" for="withTimeCode" />
|
||||
<input id="withTimeCode" type="checkbox" v-model="withTimeCode" @change="onChange" />
|
||||
</div>
|
||||
<div v-if="this.withTimeCode" class="flex justify-between mt-2" style="align-items: center">
|
||||
<label v-t="'actions.time_code'" />
|
||||
<input class="input w-300 mb-0rem" type="text" v-model="timeStamp" />
|
||||
</div>
|
||||
<a :href="generatedLink" target="_blank">
|
||||
<h6 class="mb-2 mt-2" v-text="generatedLink" />
|
||||
</a>
|
||||
<div class="flex justify-end mt-4">
|
||||
<button class="btn" style="margin-right: 15rem" v-t="'actions.follow_link'" @click="followLink()" />
|
||||
<button class="btn" v-t="'actions.copy_link'" @click="copyLink()" />
|
||||
@@ -34,6 +40,12 @@ export default {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
playlistId: {
|
||||
type: String,
|
||||
},
|
||||
playlistIndex: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
ModalComponent,
|
||||
@@ -42,13 +54,17 @@ export default {
|
||||
return {
|
||||
withTimeCode: true,
|
||||
pipedLink: true,
|
||||
withPlaylist: true,
|
||||
timeStamp: null,
|
||||
hasPlaylist: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.timeStamp = parseInt(this.currentTime);
|
||||
this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
|
||||
this.pipedLink = this.getPreferenceBoolean("shareAsPipedLink", true);
|
||||
this.withPlaylist = this.getPreferenceBoolean("shareWithPlaylist", true);
|
||||
this.hasPlaylist = this.playlistId != undefined && !isNaN(this.playlistIndex);
|
||||
},
|
||||
methods: {
|
||||
followLink() {
|
||||
@@ -68,6 +84,7 @@ export default {
|
||||
onChange() {
|
||||
this.setPreference("shareWithTimeCode", this.withTimeCode, true);
|
||||
this.setPreference("shareAsPipedLink", this.pipedLink, true);
|
||||
this.setPreference("shareWithPlaylist", this.withPlaylist, true);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
@@ -77,6 +94,10 @@ export default {
|
||||
: "https://youtu.be/" + this.videoId;
|
||||
var url = new URL(baseUrl);
|
||||
if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
|
||||
if (this.hasPlaylist && this.withPlaylist) {
|
||||
url.searchParams.append("list", this.playlistId);
|
||||
url.searchParams.append("index", this.playlistIndex);
|
||||
}
|
||||
return url.href;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -124,6 +124,8 @@
|
||||
v-if="showShareModal"
|
||||
:video-id="getVideoId()"
|
||||
:current-time="currentTime"
|
||||
:playlist-id="playlistId"
|
||||
:playlist-index="index"
|
||||
@close="showShareModal = !showShareModal"
|
||||
/>
|
||||
</div>
|
||||
@@ -438,7 +440,10 @@ export default {
|
||||
this.fetchSponsors().then(data => (this.sponsors = data));
|
||||
},
|
||||
async getComments() {
|
||||
this.fetchComments().then(data => (this.comments = data));
|
||||
this.fetchComments().then(data => {
|
||||
this.rewriteComments(data.comments);
|
||||
this.comments = data;
|
||||
});
|
||||
},
|
||||
async fetchSubscribedStatus() {
|
||||
if (!this.channelId) return;
|
||||
@@ -461,6 +466,23 @@ export default {
|
||||
this.subscribed = json.subscribed;
|
||||
});
|
||||
},
|
||||
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() {
|
||||
if (this.authenticated) {
|
||||
this.fetchJson(this.authApiUrl() + (this.subscribed ? "/unsubscribe" : "/subscribe"), null, {
|
||||
@@ -487,7 +509,8 @@ export default {
|
||||
}).then(json => {
|
||||
this.comments.nextpage = json.nextpage;
|
||||
this.loading = false;
|
||||
json.comments.map(comment => this.comments.comments.push(comment));
|
||||
this.rewriteComments(json.comments);
|
||||
this.comments.comments = this.comments.comments.concat(json.comments);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user