mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-14 19:51:26 +03:00
Added description to player component and simplified the database by un-splitting videos and playlists by type
This commit is contained in:
150
backend/app.js
150
backend/app.js
@@ -13,7 +13,7 @@ var express = require("express");
|
|||||||
var bodyParser = require("body-parser");
|
var bodyParser = require("body-parser");
|
||||||
var archiver = require('archiver');
|
var archiver = require('archiver');
|
||||||
var unzipper = require('unzipper');
|
var unzipper = require('unzipper');
|
||||||
var db_api = require('./db')
|
var db_api = require('./db');
|
||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var mergeFiles = require('merge-files');
|
var mergeFiles = require('merge-files');
|
||||||
const low = require('lowdb')
|
const low = require('lowdb')
|
||||||
@@ -87,14 +87,8 @@ categories_api.initialize(db, users_db, logger, db_api);
|
|||||||
// Set some defaults
|
// Set some defaults
|
||||||
db.defaults(
|
db.defaults(
|
||||||
{
|
{
|
||||||
playlists: {
|
playlists: [],
|
||||||
audio: [],
|
files: [],
|
||||||
video: []
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
audio: [],
|
|
||||||
video: []
|
|
||||||
},
|
|
||||||
configWriteFlag: false,
|
configWriteFlag: false,
|
||||||
downloads: {},
|
downloads: {},
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
@@ -218,10 +212,12 @@ async function checkMigrations() {
|
|||||||
|
|
||||||
// 4.1->4.2 migration
|
// 4.1->4.2 migration
|
||||||
|
|
||||||
const add_description_migration_complete = false; // db.get('add_description_migration_complete').value();
|
const add_description_migration_complete = db.get('add_description_migration_complete').value();
|
||||||
if (!add_description_migration_complete) {
|
if (!add_description_migration_complete) {
|
||||||
logger.info('Beginning migration: 4.1->4.2+')
|
logger.info('Beginning migration: 4.1->4.2+')
|
||||||
const success = await addMetadataPropertyToDB('description');
|
let success = await simplifyDBFileStructure();
|
||||||
|
success = success && await addMetadataPropertyToDB('view_count');
|
||||||
|
success = success && await addMetadataPropertyToDB('description');
|
||||||
if (success) { logger.info('4.1->4.2+ migration complete!'); }
|
if (success) { logger.info('4.1->4.2+ migration complete!'); }
|
||||||
else { logger.error('Migration failed: 4.1->4.2+'); }
|
else { logger.error('Migration failed: 4.1->4.2+'); }
|
||||||
}
|
}
|
||||||
@@ -229,6 +225,7 @@ async function checkMigrations() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
async function runFilesToDBMigration() {
|
async function runFilesToDBMigration() {
|
||||||
try {
|
try {
|
||||||
let mp3s = await getMp3s();
|
let mp3s = await getMp3s();
|
||||||
@@ -260,6 +257,37 @@ async function runFilesToDBMigration() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function simplifyDBFileStructure() {
|
||||||
|
let users = users_db.get('users').value();
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
const user = users[i];
|
||||||
|
if (user['files']['video'] !== undefined && user['files']['audio'] !== undefined) {
|
||||||
|
const user_files = user['files']['video'].concat(user['files']['audio']);
|
||||||
|
const user_db_path = users_db.get('users').find({uid: user['uid']});
|
||||||
|
user_db_path.assign({files: user_files}).write();
|
||||||
|
}
|
||||||
|
if (user['playlists']['video'] !== undefined && user['playlists']['audio'] !== undefined) {
|
||||||
|
const user_playlists = user['playlists']['video'].concat(user['playlists']['audio']);
|
||||||
|
const user_db_path = users_db.get('users').find({uid: user['uid']});
|
||||||
|
user_db_path.assign({playlists: user_playlists}).write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.get('files.video').value() !== undefined && db.get('files.audio').value() !== undefined) {
|
||||||
|
const files = db.get('files.video').value().concat(db.get('files.audio'));
|
||||||
|
db.assign({files: files}).write();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.get('playlists.video').value() !== undefined && db.get('playlists.audio').value() !== undefined) {
|
||||||
|
const playlists = db.get('playlists.video').value().concat(db.get('playlists.audio'));
|
||||||
|
db.assign({playlists: playlists}).write();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function addMetadataPropertyToDB(property_key) {
|
async function addMetadataPropertyToDB(property_key) {
|
||||||
try {
|
try {
|
||||||
@@ -592,6 +620,9 @@ async function loadConfig() {
|
|||||||
// creates archive path if missing
|
// creates archive path if missing
|
||||||
await fs.ensureDir(archivePath);
|
await fs.ensureDir(archivePath);
|
||||||
|
|
||||||
|
// check migrations
|
||||||
|
await checkMigrations();
|
||||||
|
|
||||||
// now this is done here due to youtube-dl's repo takedown
|
// now this is done here due to youtube-dl's repo takedown
|
||||||
await startYoutubeDL();
|
await startYoutubeDL();
|
||||||
|
|
||||||
@@ -606,9 +637,6 @@ async function loadConfig() {
|
|||||||
|
|
||||||
db_api.importUnregisteredFiles();
|
db_api.importUnregisteredFiles();
|
||||||
|
|
||||||
// check migrations
|
|
||||||
await checkMigrations();
|
|
||||||
|
|
||||||
// load in previous downloads
|
// load in previous downloads
|
||||||
downloads = db.get('downloads').value();
|
downloads = db.get('downloads').value();
|
||||||
|
|
||||||
@@ -1994,7 +2022,7 @@ async function addThumbnails(files) {
|
|||||||
|
|
||||||
// gets all download mp3s
|
// gets all download mp3s
|
||||||
app.get('/api/getMp3s', optionalJwt, async function(req, res) {
|
app.get('/api/getMp3s', optionalJwt, async function(req, res) {
|
||||||
var mp3s = db.get('files.audio').value(); // getMp3s();
|
var mp3s = db.get('files').chain().find({isAudio: true}).value(); // getMp3s();
|
||||||
var playlists = db.get('playlists.audio').value();
|
var playlists = db.get('playlists.audio').value();
|
||||||
const is_authenticated = req.isAuthenticated();
|
const is_authenticated = req.isAuthenticated();
|
||||||
if (is_authenticated) {
|
if (is_authenticated) {
|
||||||
@@ -2020,8 +2048,8 @@ app.get('/api/getMp3s', optionalJwt, async function(req, res) {
|
|||||||
|
|
||||||
// gets all download mp4s
|
// gets all download mp4s
|
||||||
app.get('/api/getMp4s', optionalJwt, async function(req, res) {
|
app.get('/api/getMp4s', optionalJwt, async function(req, res) {
|
||||||
var mp4s = db.get('files.video').value(); // getMp4s();
|
var mp4s = db.get('files').chain().find({isAudio: false}).value(); // getMp4s();
|
||||||
var playlists = db.get('playlists.video').value();
|
var playlists = db.get('playlists').value();
|
||||||
|
|
||||||
const is_authenticated = req.isAuthenticated();
|
const is_authenticated = req.isAuthenticated();
|
||||||
if (is_authenticated) {
|
if (is_authenticated) {
|
||||||
@@ -2052,21 +2080,11 @@ app.post('/api/getFile', optionalJwt, function (req, res) {
|
|||||||
var file = null;
|
var file = null;
|
||||||
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
file = auth_api.getUserVideo(req.user.uid, uid, type);
|
file = auth_api.getUserVideo(req.user.uid, uid);
|
||||||
} else if (uuid) {
|
} else if (uuid) {
|
||||||
file = auth_api.getUserVideo(uuid, uid, type, true);
|
file = auth_api.getUserVideo(uuid, uid, true);
|
||||||
} else {
|
} else {
|
||||||
if (!type) {
|
file = db.get('files').find({uid: uid}).value();
|
||||||
file = db.get('files.audio').find({uid: uid}).value();
|
|
||||||
if (!file) {
|
|
||||||
file = db.get('files.video').find({uid: uid}).value();
|
|
||||||
if (file) type = 'video';
|
|
||||||
} else {
|
|
||||||
type = 'audio';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file && type) file = db.get(`files.${type}`).find({uid: uid}).value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if chat exists for twitch videos
|
// check if chat exists for twitch videos
|
||||||
@@ -2086,32 +2104,20 @@ app.post('/api/getFile', optionalJwt, function (req, res) {
|
|||||||
|
|
||||||
app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
|
app.post('/api/getAllFiles', optionalJwt, async function (req, res) {
|
||||||
// these are returned
|
// these are returned
|
||||||
let files = [];
|
let files = null;
|
||||||
let playlists = [];
|
let playlists = null;
|
||||||
let subscription_files = [];
|
|
||||||
|
|
||||||
let videos = null;
|
|
||||||
let audios = null;
|
|
||||||
let audio_playlists = null;
|
|
||||||
let video_playlists = null;
|
|
||||||
let subscriptions = config_api.getConfigItem('ytdl_allow_subscriptions') ? (subscriptions_api.getAllSubscriptions(req.isAuthenticated() ? req.user.uid : null)) : [];
|
let subscriptions = config_api.getConfigItem('ytdl_allow_subscriptions') ? (subscriptions_api.getAllSubscriptions(req.isAuthenticated() ? req.user.uid : null)) : [];
|
||||||
|
|
||||||
// get basic info depending on multi-user mode being enabled
|
// get basic info depending on multi-user mode being enabled
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
videos = auth_api.getUserVideos(req.user.uid, 'video');
|
files = auth_api.getUserVideos(req.user.uid);
|
||||||
audios = auth_api.getUserVideos(req.user.uid, 'audio');
|
playlists = auth_api.getUserPlaylists(req.user.uid);
|
||||||
audio_playlists = auth_api.getUserPlaylists(req.user.uid, 'audio');
|
|
||||||
video_playlists = auth_api.getUserPlaylists(req.user.uid, 'video');
|
|
||||||
} else {
|
} else {
|
||||||
videos = db.get('files.audio').value();
|
files = db.get('files').value();
|
||||||
audios = db.get('files.video').value();
|
playlists = db.get('playlists').value();
|
||||||
audio_playlists = db.get('playlists.audio').value();
|
|
||||||
video_playlists = db.get('playlists.video').value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
files = videos.concat(audios);
|
|
||||||
playlists = video_playlists.concat(audio_playlists);
|
|
||||||
|
|
||||||
// loop through subscriptions and add videos
|
// loop through subscriptions and add videos
|
||||||
for (let i = 0; i < subscriptions.length; i++) {
|
for (let i = 0; i < subscriptions.length; i++) {
|
||||||
sub = subscriptions[i];
|
sub = subscriptions[i];
|
||||||
@@ -2187,14 +2193,13 @@ app.post('/api/downloadTwitchChatByVODID', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
// video sharing
|
// video sharing
|
||||||
app.post('/api/enableSharing', optionalJwt, function(req, res) {
|
app.post('/api/enableSharing', optionalJwt, function(req, res) {
|
||||||
var type = req.body.type;
|
|
||||||
var uid = req.body.uid;
|
var uid = req.body.uid;
|
||||||
var is_playlist = req.body.is_playlist;
|
var is_playlist = req.body.is_playlist;
|
||||||
let success = false;
|
let success = false;
|
||||||
// multi-user mode
|
// multi-user mode
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
// if multi user mode, use this method instead
|
// if multi user mode, use this method instead
|
||||||
success = auth_api.changeSharingMode(req.user.uid, uid, type, is_playlist, true);
|
success = auth_api.changeSharingMode(req.user.uid, uid, is_playlist, true);
|
||||||
res.send({success: success});
|
res.send({success: success});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2203,12 +2208,12 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) {
|
|||||||
try {
|
try {
|
||||||
success = true;
|
success = true;
|
||||||
if (!is_playlist && type !== 'subscription') {
|
if (!is_playlist && type !== 'subscription') {
|
||||||
db.get(`files.${type}`)
|
db.get(`files`)
|
||||||
.find({uid: uid})
|
.find({uid: uid})
|
||||||
.assign({sharingEnabled: true})
|
.assign({sharingEnabled: true})
|
||||||
.write();
|
.write();
|
||||||
} else if (is_playlist) {
|
} else if (is_playlist) {
|
||||||
db.get(`playlists.${type}`)
|
db.get(`playlists`)
|
||||||
.find({id: uid})
|
.find({id: uid})
|
||||||
.assign({sharingEnabled: true})
|
.assign({sharingEnabled: true})
|
||||||
.write();
|
.write();
|
||||||
@@ -2237,7 +2242,7 @@ app.post('/api/disableSharing', optionalJwt, function(req, res) {
|
|||||||
// multi-user mode
|
// multi-user mode
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
// if multi user mode, use this method instead
|
// if multi user mode, use this method instead
|
||||||
success = auth_api.changeSharingMode(req.user.uid, uid, type, is_playlist, false);
|
success = auth_api.changeSharingMode(req.user.uid, uid, is_playlist, false);
|
||||||
res.send({success: success});
|
res.send({success: success});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2246,12 +2251,12 @@ app.post('/api/disableSharing', optionalJwt, function(req, res) {
|
|||||||
try {
|
try {
|
||||||
success = true;
|
success = true;
|
||||||
if (!is_playlist && type !== 'subscription') {
|
if (!is_playlist && type !== 'subscription') {
|
||||||
db.get(`files.${type}`)
|
db.get(`files`)
|
||||||
.find({uid: uid})
|
.find({uid: uid})
|
||||||
.assign({sharingEnabled: false})
|
.assign({sharingEnabled: false})
|
||||||
.write();
|
.write();
|
||||||
} else if (is_playlist) {
|
} else if (is_playlist) {
|
||||||
db.get(`playlists.${type}`)
|
db.get(`playlists`)
|
||||||
.find({id: uid})
|
.find({id: uid})
|
||||||
.assign({sharingEnabled: false})
|
.assign({sharingEnabled: false})
|
||||||
.write();
|
.write();
|
||||||
@@ -2544,7 +2549,7 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
|||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
auth_api.addPlaylist(req.user.uid, new_playlist, type);
|
auth_api.addPlaylist(req.user.uid, new_playlist, type);
|
||||||
} else {
|
} else {
|
||||||
db.get(`playlists.${type}`)
|
db.get(`playlists`)
|
||||||
.push(new_playlist)
|
.push(new_playlist)
|
||||||
.write();
|
.write();
|
||||||
}
|
}
|
||||||
@@ -2558,26 +2563,15 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlistID;
|
||||||
let type = req.body.type;
|
|
||||||
let uuid = req.body.uuid;
|
let uuid = req.body.uuid;
|
||||||
|
|
||||||
let playlist = null;
|
let playlist = null;
|
||||||
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID, type);
|
playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID);
|
||||||
type = playlist.type;
|
type = playlist.type;
|
||||||
} else {
|
} else {
|
||||||
if (!type) {
|
playlist = db.get(`playlists`).find({id: playlistID}).value();
|
||||||
playlist = db.get('playlists.audio').find({id: playlistID}).value();
|
|
||||||
if (!playlist) {
|
|
||||||
playlist = db.get('playlists.video').find({id: playlistID}).value();
|
|
||||||
if (playlist) type = 'video';
|
|
||||||
} else {
|
|
||||||
type = 'audio';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!playlist) playlist = db.get(`playlists.${type}`).find({id: playlistID}).value();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
@@ -2590,14 +2584,13 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
|||||||
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlistID;
|
||||||
let fileNames = req.body.fileNames;
|
let fileNames = req.body.fileNames;
|
||||||
let type = req.body.type;
|
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
try {
|
try {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
auth_api.updatePlaylistFiles(req.user.uid, playlistID, fileNames, type);
|
auth_api.updatePlaylistFiles(req.user.uid, playlistID, fileNames);
|
||||||
} else {
|
} else {
|
||||||
db.get(`playlists.${type}`)
|
db.get(`playlists`)
|
||||||
.find({id: playlistID})
|
.find({id: playlistID})
|
||||||
.assign({fileNames: fileNames})
|
.assign({fileNames: fileNames})
|
||||||
.write();
|
.write();
|
||||||
@@ -2623,15 +2616,14 @@ app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/deletePlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/deletePlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlistID;
|
||||||
let type = req.body.type;
|
|
||||||
|
|
||||||
let success = null;
|
let success = null;
|
||||||
try {
|
try {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
auth_api.removePlaylist(req.user.uid, playlistID, type);
|
auth_api.removePlaylist(req.user.uid, playlistID);
|
||||||
} else {
|
} else {
|
||||||
// removes playlist from playlists
|
// removes playlist from playlists
|
||||||
db.get(`playlists.${type}`)
|
db.get(`playlists`)
|
||||||
.remove({id: playlistID})
|
.remove({id: playlistID})
|
||||||
.write();
|
.write();
|
||||||
}
|
}
|
||||||
@@ -2653,23 +2645,23 @@ app.post('/api/deleteFile', optionalJwt, async (req, res) => {
|
|||||||
var blacklistMode = req.body.blacklistMode;
|
var blacklistMode = req.body.blacklistMode;
|
||||||
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
let success = auth_api.deleteUserFile(req.user.uid, uid, type, blacklistMode);
|
let success = auth_api.deleteUserFile(req.user.uid, uid, blacklistMode);
|
||||||
res.send(success);
|
res.send(success);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_obj = db.get(`files.${type}`).find({uid: uid}).value();
|
var file_obj = db.get(`files`).find({uid: uid}).value();
|
||||||
var name = file_obj.id;
|
var name = file_obj.id;
|
||||||
var fullpath = file_obj ? file_obj.path : null;
|
var fullpath = file_obj ? file_obj.path : null;
|
||||||
var wasDeleted = false;
|
var wasDeleted = false;
|
||||||
if (await fs.pathExists(fullpath))
|
if (await fs.pathExists(fullpath))
|
||||||
{
|
{
|
||||||
wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode);
|
wasDeleted = type === 'audio' ? await deleteAudioFile(name, path.basename(fullpath), blacklistMode) : await deleteVideoFile(name, path.basename(fullpath), blacklistMode);
|
||||||
db.get('files.video').remove({uid: uid}).write();
|
db.get('files').remove({uid: uid}).write();
|
||||||
// wasDeleted = true;
|
// wasDeleted = true;
|
||||||
res.send(wasDeleted);
|
res.send(wasDeleted);
|
||||||
} else if (video_obj) {
|
} else if (video_obj) {
|
||||||
db.get('files.video').remove({uid: uid}).write();
|
db.get('files').remove({uid: uid}).write();
|
||||||
wasDeleted = true;
|
wasDeleted = true;
|
||||||
res.send(wasDeleted);
|
res.send(wasDeleted);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -281,24 +281,13 @@ exports.adminExists = function() {
|
|||||||
|
|
||||||
// video stuff
|
// video stuff
|
||||||
|
|
||||||
exports.getUserVideos = function(user_uid, type) {
|
exports.getUserVideos = function(user_uid) {
|
||||||
const user = users_db.get('users').find({uid: user_uid}).value();
|
const user = users_db.get('users').find({uid: user_uid}).value();
|
||||||
return user['files'][type];
|
return user['files'];
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false) {
|
exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) {
|
||||||
let file = null;
|
let file = users_db.get('users').find({uid: user_uid}).get(`files`).find({uid: file_uid}).value();
|
||||||
if (!type) {
|
|
||||||
file = users_db.get('users').find({uid: user_uid}).get(`files.audio`).find({uid: file_uid}).value();
|
|
||||||
if (!file) {
|
|
||||||
file = users_db.get('users').find({uid: user_uid}).get(`files.video`).find({uid: file_uid}).value();
|
|
||||||
if (file) type = 'video';
|
|
||||||
} else {
|
|
||||||
type = 'audio';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file && type) file = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value();
|
|
||||||
|
|
||||||
// prevent unauthorized users from accessing the file info
|
// prevent unauthorized users from accessing the file info
|
||||||
if (file && !file['sharingEnabled'] && requireSharing) file = null;
|
if (file && !file['sharingEnabled'] && requireSharing) file = null;
|
||||||
@@ -306,38 +295,28 @@ exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.addPlaylist = function(user_uid, new_playlist, type) {
|
exports.addPlaylist = function(user_uid, new_playlist) {
|
||||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).push(new_playlist).write();
|
users_db.get('users').find({uid: user_uid}).get(`playlists`).push(new_playlist).write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updatePlaylistFiles = function(user_uid, playlistID, new_filenames, type) {
|
exports.updatePlaylistFiles = function(user_uid, playlistID, new_filenames) {
|
||||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).assign({fileNames: new_filenames});
|
users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}).assign({fileNames: new_filenames});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.removePlaylist = function(user_uid, playlistID, type) {
|
exports.removePlaylist = function(user_uid, playlistID) {
|
||||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).remove({id: playlistID}).write();
|
users_db.get('users').find({uid: user_uid}).get(`playlists`).remove({id: playlistID}).write();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getUserPlaylists = function(user_uid, type) {
|
exports.getUserPlaylists = function(user_uid) {
|
||||||
const user = users_db.get('users').find({uid: user_uid}).value();
|
const user = users_db.get('users').find({uid: user_uid}).value();
|
||||||
return user['playlists'][type];
|
return user['playlists'];
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getUserPlaylist = function(user_uid, playlistID, type, requireSharing = false) {
|
exports.getUserPlaylist = function(user_uid, playlistID, requireSharing = false) {
|
||||||
let playlist = null;
|
let playlist = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}).value();
|
||||||
if (!type) {
|
|
||||||
playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.audio`).find({id: playlistID}).value();
|
|
||||||
if (!playlist) {
|
|
||||||
playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.video`).find({id: playlistID}).value();
|
|
||||||
if (playlist) type = 'video';
|
|
||||||
} else {
|
|
||||||
type = 'audio';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!playlist) playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).value();
|
|
||||||
|
|
||||||
// prevent unauthorized users from accessing the file info
|
// prevent unauthorized users from accessing the file info
|
||||||
if (requireSharing && !playlist['sharingEnabled']) playlist = null;
|
if (requireSharing && !playlist['sharingEnabled']) playlist = null;
|
||||||
@@ -345,21 +324,22 @@ exports.getUserPlaylist = function(user_uid, playlistID, type, requireSharing =
|
|||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.registerUserFile = function(user_uid, file_object, type) {
|
exports.registerUserFile = function(user_uid, file_object) {
|
||||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
users_db.get('users').find({uid: user_uid}).get(`files`)
|
||||||
.remove({
|
.remove({
|
||||||
path: file_object['path']
|
path: file_object['path']
|
||||||
}).write();
|
}).write();
|
||||||
|
|
||||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
users_db.get('users').find({uid: user_uid}).get(`files`)
|
||||||
.push(file_object)
|
.push(file_object)
|
||||||
.write();
|
.write();
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode = false) {
|
exports.deleteUserFile = async function(user_uid, file_uid, blacklistMode = false) {
|
||||||
let success = false;
|
let success = false;
|
||||||
const file_obj = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value();
|
const file_obj = users_db.get('users').find({uid: user_uid}).get(`files`).find({uid: file_uid}).value();
|
||||||
if (file_obj) {
|
if (file_obj) {
|
||||||
|
const type = file_obj.isAudio ? 'audio' : 'video';
|
||||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||||
|
|
||||||
@@ -375,7 +355,7 @@ exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode
|
|||||||
}
|
}
|
||||||
|
|
||||||
const full_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext);
|
const full_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext);
|
||||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
users_db.get('users').find({uid: user_uid}).get(`files`)
|
||||||
.remove({
|
.remove({
|
||||||
uid: file_uid
|
uid: file_uid
|
||||||
}).write();
|
}).write();
|
||||||
@@ -424,11 +404,11 @@ exports.deleteUserFile = async function(user_uid, file_uid, type, blacklistMode
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.changeSharingMode = function(user_uid, file_uid, type, is_playlist, enabled) {
|
exports.changeSharingMode = function(user_uid, file_uid, is_playlist, enabled) {
|
||||||
let success = false;
|
let success = false;
|
||||||
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
||||||
if (user_db_obj.value()) {
|
if (user_db_obj.value()) {
|
||||||
const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({id: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid});
|
const file_db_obj = is_playlist ? user_db_obj.get(`playlists`).find({id: file_uid}) : user_db_obj.get(`files`).find({uid: file_uid});
|
||||||
if (file_db_obj.value()) {
|
if (file_db_obj.value()) {
|
||||||
success = true;
|
success = true;
|
||||||
file_db_obj.assign({sharingEnabled: enabled}).write();
|
file_db_obj.assign({sharingEnabled: enabled}).write();
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null, custo
|
|||||||
if (!sub) {
|
if (!sub) {
|
||||||
if (multiUserMode) {
|
if (multiUserMode) {
|
||||||
const user_uid = multiUserMode.user;
|
const user_uid = multiUserMode.user;
|
||||||
db_path = users_db.get('users').find({uid: user_uid}).get(`files.${type}`);
|
db_path = users_db.get('users').find({uid: user_uid}).get(`files`);
|
||||||
} else {
|
} else {
|
||||||
db_path = db.get(`files.${type}`)
|
db_path = db.get(`files`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (multiUserMode) {
|
if (multiUserMode) {
|
||||||
@@ -94,18 +94,18 @@ function generateFileObject(id, type, customPath = null, sub = null) {
|
|||||||
var thumbnail = jsonobj.thumbnail;
|
var thumbnail = jsonobj.thumbnail;
|
||||||
var duration = jsonobj.duration;
|
var duration = jsonobj.duration;
|
||||||
var isaudio = type === 'audio';
|
var isaudio = type === 'audio';
|
||||||
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date);
|
var description = jsonobj.description;
|
||||||
|
var file_obj = new utils.File(id, title, thumbnail, isaudio, duration, url, uploader, size, file_path, upload_date, description);
|
||||||
return file_obj;
|
return file_obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePlaylist(playlist, user_uid) {
|
function updatePlaylist(playlist, user_uid) {
|
||||||
let playlistID = playlist.id;
|
let playlistID = playlist.id;
|
||||||
let type = playlist.type;
|
|
||||||
let db_loc = null;
|
let db_loc = null;
|
||||||
if (user_uid) {
|
if (user_uid) {
|
||||||
db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID});
|
db_loc = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID});
|
||||||
} else {
|
} else {
|
||||||
db_loc = db.get(`playlists.${type}`).find({id: playlistID});
|
db_loc = db.get(`playlists`).find({id: playlistID});
|
||||||
}
|
}
|
||||||
db_loc.assign(playlist).write();
|
db_loc.assign(playlist).write();
|
||||||
return true;
|
return true;
|
||||||
@@ -132,14 +132,14 @@ function getFileDirectoriesAndDBs() {
|
|||||||
// add user's audio dir to check list
|
// add user's audio dir to check list
|
||||||
dirs_to_check.push({
|
dirs_to_check.push({
|
||||||
basePath: path.join(usersFileFolder, user.uid, 'audio'),
|
basePath: path.join(usersFileFolder, user.uid, 'audio'),
|
||||||
dbPath: users_db.get('users').find({uid: user.uid}).get('files.audio'),
|
dbPath: users_db.get('users').find({uid: user.uid}).get('files'),
|
||||||
type: 'audio'
|
type: 'audio'
|
||||||
});
|
});
|
||||||
|
|
||||||
// add user's video dir to check list
|
// add user's video dir to check list
|
||||||
dirs_to_check.push({
|
dirs_to_check.push({
|
||||||
basePath: path.join(usersFileFolder, user.uid, 'video'),
|
basePath: path.join(usersFileFolder, user.uid, 'video'),
|
||||||
dbPath: users_db.get('users').find({uid: user.uid}).get('files.video'),
|
dbPath: users_db.get('users').find({uid: user.uid}).get('files'),
|
||||||
type: 'video'
|
type: 'video'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -153,14 +153,14 @@ function getFileDirectoriesAndDBs() {
|
|||||||
// add audio dir to check list
|
// add audio dir to check list
|
||||||
dirs_to_check.push({
|
dirs_to_check.push({
|
||||||
basePath: audioFolderPath,
|
basePath: audioFolderPath,
|
||||||
dbPath: db.get('files.audio'),
|
dbPath: db.get('files'),
|
||||||
type: 'audio'
|
type: 'audio'
|
||||||
});
|
});
|
||||||
|
|
||||||
// add video dir to check list
|
// add video dir to check list
|
||||||
dirs_to_check.push({
|
dirs_to_check.push({
|
||||||
basePath: videoFolderPath,
|
basePath: videoFolderPath,
|
||||||
dbPath: db.get('files.video'),
|
dbPath: db.get('files'),
|
||||||
type: 'video'
|
type: 'video'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ async function recFindByExt(base,ext,files,result)
|
|||||||
|
|
||||||
// objects
|
// objects
|
||||||
|
|
||||||
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date) {
|
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.thumbnailURL = thumbnailURL;
|
this.thumbnailURL = thumbnailURL;
|
||||||
@@ -200,6 +200,7 @@ function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, p
|
|||||||
this.size = size;
|
this.size = size;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.upload_date = upload_date;
|
this.upload_date = upload_date;
|
||||||
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import { EditSubscriptionDialogComponent } from './dialogs/edit-subscription-dia
|
|||||||
import { CustomPlaylistsComponent } from './components/custom-playlists/custom-playlists.component';
|
import { CustomPlaylistsComponent } from './components/custom-playlists/custom-playlists.component';
|
||||||
import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit-category-dialog.component';
|
import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit-category-dialog.component';
|
||||||
import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component';
|
import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component';
|
||||||
|
import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component';
|
||||||
|
|
||||||
registerLocaleData(es, 'es');
|
registerLocaleData(es, 'es');
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
VideoInfoDialogComponent,
|
VideoInfoDialogComponent,
|
||||||
ArgModifierDialogComponent,
|
ArgModifierDialogComponent,
|
||||||
HighlightPipe,
|
HighlightPipe,
|
||||||
|
LinkifyPipe,
|
||||||
UpdaterComponent,
|
UpdaterComponent,
|
||||||
UpdateProgressDialogComponent,
|
UpdateProgressDialogComponent,
|
||||||
ShareMediaDialogComponent,
|
ShareMediaDialogComponent,
|
||||||
@@ -127,7 +129,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
EditSubscriptionDialogComponent,
|
EditSubscriptionDialogComponent,
|
||||||
CustomPlaylistsComponent,
|
CustomPlaylistsComponent,
|
||||||
EditCategoryDialogComponent,
|
EditCategoryDialogComponent,
|
||||||
TwitchChatComponent
|
TwitchChatComponent,
|
||||||
|
SeeMoreComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -188,7 +191,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
PostsService
|
PostsService
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
HighlightPipe
|
HighlightPipe,
|
||||||
|
LinkifyPipe
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|||||||
11
src/app/components/see-more/see-more.component.html
Normal file
11
src/app/components/see-more/see-more.component.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<span class="text" [ngStyle]="{'-webkit-line-clamp': !see_more_active ? line_limit : null}" [innerHTML]="text | linkify"></span>
|
||||||
|
<span>
|
||||||
|
<a [routerLink]="" (click)="toggleSeeMore()">
|
||||||
|
<ng-container *ngIf="!see_more_active" i18n="See more">
|
||||||
|
See more.
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="see_more_active" i18n="See less">
|
||||||
|
See less.
|
||||||
|
</ng-container>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
7
src/app/components/see-more/see-more.component.scss
Normal file
7
src/app/components/see-more/see-more.component.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
25
src/app/components/see-more/see-more.component.spec.ts
Normal file
25
src/app/components/see-more/see-more.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SeeMoreComponent } from './see-more.component';
|
||||||
|
|
||||||
|
describe('SeeMoreComponent', () => {
|
||||||
|
let component: SeeMoreComponent;
|
||||||
|
let fixture: ComponentFixture<SeeMoreComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SeeMoreComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SeeMoreComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
60
src/app/components/see-more/see-more.component.ts
Normal file
60
src/app/components/see-more/see-more.component.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Component, Input, OnInit, Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Pipe({ name: 'linkify' })
|
||||||
|
export class LinkifyPipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor(private _domSanitizer: DomSanitizer) {}
|
||||||
|
|
||||||
|
transform(value: any, args?: any): any {
|
||||||
|
return this._domSanitizer.bypassSecurityTrustHtml(this.stylize(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify this method according to your custom logic
|
||||||
|
private stylize(text: string): string {
|
||||||
|
let stylizedText: string = '';
|
||||||
|
if (text && text.length > 0) {
|
||||||
|
for (let line of text.split("\n")) {
|
||||||
|
for (let t of line.split(" ")) {
|
||||||
|
if (t.startsWith("http") && t.length>7) {
|
||||||
|
stylizedText += `<a target="_blank" href="${t}">${t}</a> `;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stylizedText += t + " ";
|
||||||
|
}
|
||||||
|
stylizedText += '<br>';
|
||||||
|
}
|
||||||
|
return stylizedText;
|
||||||
|
}
|
||||||
|
else return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-see-more',
|
||||||
|
templateUrl: './see-more.component.html',
|
||||||
|
providers: [LinkifyPipe],
|
||||||
|
styleUrls: ['./see-more.component.scss']
|
||||||
|
})
|
||||||
|
export class SeeMoreComponent implements OnInit {
|
||||||
|
|
||||||
|
see_more_active = false;
|
||||||
|
|
||||||
|
@Input() text = '';
|
||||||
|
@Input() line_limit = 2;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSeeMore() {
|
||||||
|
this.see_more_active = !this.see_more_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseText() {
|
||||||
|
return this.text.replace(/(http.*?\s)/, "<a href=\"$1\">$1</a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,14 +8,28 @@
|
|||||||
</video>
|
</video>
|
||||||
</vg-player>
|
</vg-player>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="file_obj" style="height: fit-content; width: 100%; margin-top: 10px;">
|
<div *ngIf="db_file" style="height: fit-content; width: 100%; margin-top: 10px;">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-2 col-lg-1">
|
||||||
<ng-container *ngIf="file_obj">{{file_obj['local_play_count'] ? file_obj['local_play_count'] : 0}} <ng-container i18n="View count label">views</ng-container></ng-container>
|
<ng-container *ngIf="db_file">{{db_file['local_view_count'] ? db_file['local_view_count'] : 0}} <ng-container i18n="View count label">views</ng-container></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div style="white-space: pre-line;" class="col-9 col-lg-10">
|
||||||
|
<ng-container *ngIf="db_file && db_file['description']">
|
||||||
|
<p>
|
||||||
|
<app-see-more [text]="db_file['description']"></app-see-more>
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!db_file || !db_file['description']">
|
||||||
|
<p style="text-align: center;">
|
||||||
|
No description available.
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<div *ngIf="db_file && db_file.url.includes('twitch.tv/videos/') && postsService['config']['API']['use_twitch_API']">
|
||||||
|
<button (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,15 +39,11 @@
|
|||||||
<mat-button-toggle cdkDrag *ngFor="let playlist_item of playlist; let i = index" [checked]="currentItem.title === playlist_item.title" (click)="onClickPlaylistItem(playlist_item, i)" class="toggle-button" [value]="playlist_item.title">{{playlist_item.label}}</mat-button-toggle>
|
<mat-button-toggle cdkDrag *ngFor="let playlist_item of playlist; let i = index" [checked]="currentItem.title === playlist_item.title" (click)="onClickPlaylistItem(playlist_item, i)" class="toggle-button" [value]="playlist_item.title">{{playlist_item.label}}</mat-button-toggle>
|
||||||
</mat-button-toggle-group>
|
</mat-button-toggle-group>
|
||||||
</div>
|
</div>
|
||||||
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists']">
|
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists'] && postsService['config']['API']['use_twitch_API']">
|
||||||
<ng-container *ngIf="api_ready && db_file && db_file.url.includes('twitch.tv/videos/')">
|
<ng-container *ngIf="api_ready && db_file && db_file.url.includes('twitch.tv/videos/')">
|
||||||
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime"></app-twitch-chat>
|
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime"></app-twitch-chat>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-drawer>
|
</mat-drawer>
|
||||||
|
|
||||||
<div *ngIf="db_file && db_file.url.includes('twitch.tv/videos/') && postsService['config']['API']['use_twitch_API']" style="position: absolute; right: 0px">
|
|
||||||
<button style="right: 0px; top: -46px;" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="update-playlist-button-div" *ngIf="id && playlistChanged()">
|
<div class="update-playlist-button-div" *ngIf="id && playlistChanged()">
|
||||||
<div class="spinner-div">
|
<div class="spinner-div">
|
||||||
|
|||||||
Reference in New Issue
Block a user