mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-06-26 10:15:00 +03:00
Compare commits
81 Commits
v4.1
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ea47af7a1 | ||
|
|
cf01a0d8ab | ||
|
|
d49a67dfd0 | ||
|
|
96c52f2d5b | ||
|
|
61c03b6681 | ||
|
|
7c349163b6 | ||
|
|
a0acdd1d86 | ||
|
|
25bfb9e518 | ||
|
|
c9b615c659 | ||
|
|
c4f21dc1cc | ||
|
|
e678f4b476 | ||
|
|
356f31343e | ||
|
|
38c46b5be5 | ||
|
|
12dcdfcb3c | ||
|
|
47c19c0cdc | ||
|
|
d0eff42f2a | ||
|
|
4472aae3e9 | ||
|
|
c55d3de9a0 | ||
|
|
1cdc1640ac | ||
|
|
fcaf8b5a62 | ||
|
|
8c7b2dfc79 | ||
|
|
59ad74ed79 | ||
|
|
690871f6b2 | ||
|
|
5088ce0291 | ||
|
|
576e2109d7 | ||
|
|
c6c80579df | ||
|
|
0ab6535fec | ||
|
|
d7aa39599d | ||
|
|
ee169cd7ce | ||
|
|
835790e69c | ||
|
|
68037613d8 | ||
|
|
3df384de22 | ||
|
|
f0c4ed4590 | ||
|
|
fd35153721 | ||
|
|
d0aed8f144 | ||
|
|
d17b68d76e | ||
|
|
a481869166 | ||
|
|
eb4ed32fcb | ||
|
|
9aee6e91cd | ||
|
|
37d3e9326c | ||
|
|
dbf8f9ebfd | ||
|
|
7858e26b15 | ||
|
|
057ad67672 | ||
|
|
3ebc903ce9 | ||
|
|
21c5795f1c | ||
|
|
f12ea017bc | ||
|
|
cd18bce509 | ||
|
|
5ef4388d73 | ||
|
|
a78c0cb56c | ||
|
|
ae9e4e6857 | ||
|
|
333556c305 | ||
|
|
c5b0a7a697 | ||
|
|
d7631360cc | ||
|
|
c800308b9d | ||
|
|
c1c57135ba | ||
|
|
8384b73c4c | ||
|
|
834ac00694 | ||
|
|
935ae3452c | ||
|
|
cc189a3abd | ||
|
|
335b588c3a | ||
|
|
493abc3a4c | ||
|
|
238abc1686 | ||
|
|
fbfad6c3e2 | ||
|
|
a6ff65f004 | ||
|
|
89cd969fcb | ||
|
|
4ebb2d4297 | ||
|
|
d371ccf094 | ||
|
|
e7181b57c7 | ||
|
|
38fa39d765 | ||
|
|
9e5de88675 | ||
|
|
9cf4949c30 | ||
|
|
dd80c51f16 | ||
|
|
84d83f228e | ||
|
|
14bd82c508 | ||
|
|
0b6606cafb | ||
|
|
e97e9ec717 | ||
|
|
990b3d4037 | ||
|
|
2e7b1c2d53 | ||
|
|
a4eb7fb745 | ||
|
|
2442067ca0 | ||
|
|
41d4dfeba1 |
60
README.md
60
README.md
@@ -53,34 +53,6 @@ NOTE: If you are intending to use a [reverse proxy](https://github.com/Tzahi1234
|
|||||||
|
|
||||||
If you experience problems, know that it's usually caused by a configuration problem. The first thing you should do is check the console. To get there, right click anywhere on the page and click "Inspect element." Then on the menu that pops up, click console. Look at the error there, and try to investigate.
|
If you experience problems, know that it's usually caused by a configuration problem. The first thing you should do is check the console. To get there, right click anywhere on the page and click "Inspect element." Then on the menu that pops up, click console. Look at the error there, and try to investigate.
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
NOTE: If you are using YoutubeDL-Material v3.2 or lower, click [here](https://github.com/Tzahi12345/YoutubeDL-Material/blob/b87a9f1e2fd896b8e3b2f12429b7ffb15ea4521b/README.md#configuration) for the old README
|
|
||||||
|
|
||||||
Here is an explanation for the configuration entries. Check out the [default config](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/backend/config/default.json) for more context.
|
|
||||||
|
|
||||||
| Config item | Description | Default |
|
|
||||||
| ------------- | ------------- | ------------- |
|
|
||||||
| url | URL to the server hosting YoutubeDL-Material | "http://example.com" |
|
|
||||||
| port | Desired port for YoutubeDL-Material | "17442" |
|
|
||||||
| use-encryption | true if you intend to use SSL encryption (https) | false |
|
|
||||||
| cert-file-path | Cert file path - required if using encryption | "/etc/letsencrypt/live/example.com/fullchain.pem" |
|
|
||||||
| key-file-path | Private key file path - required if using encryption | "/etc/letsencrypt/live/example.com/privkey.pem" |
|
|
||||||
| path-audio | Path to audio folder for saved mp3s | "audio/" |
|
|
||||||
| path-video | Path to video folder for saved mp4s | "video/" |
|
|
||||||
| title_top | Title shown on the top toolbar | "Youtube Downloader" |
|
|
||||||
| file_manager_enabled | true if you want to use the file manager | true |
|
|
||||||
| allow_quality_select | true if you want to select a videos quality level before downloading | true |
|
|
||||||
| download_only_mode | true if you want files to directly download to the client with no media player | false |
|
|
||||||
| allow_multi_download_mode | true if you want the ability to download multiple videos at the same time | true |
|
|
||||||
| use_youtube_API | true if you want to use the Youtube API which is used for YT searches | false |
|
|
||||||
| youtube_API_key | Youtube API key. Required if use_youtube_API is enabled | "" |
|
|
||||||
| default_theme | Default theme to use. Options are "default" and "dark" | "default" |
|
|
||||||
| allow_theme_change | true if you want the icon in the top toolbar that toggles dark mode | true |
|
|
||||||
| use_default_downloading_agent | true if you want to use youtube-dl's default downloader | true |
|
|
||||||
| custom_downloading_agent | If not using the default downloader, this is the downloader you want to use | "" |
|
|
||||||
| allow_advanced_download | true if you want to use the Advanced download options | false |
|
|
||||||
|
|
||||||
## Build it yourself
|
## Build it yourself
|
||||||
|
|
||||||
If you'd like to install YoutubeDL-Material, go to the Installation section. If you want to build it yourself and/or develop the repository, then this section is for you.
|
If you'd like to install YoutubeDL-Material, go to the Installation section. If you want to build it yourself and/or develop the repository, then this section is for you.
|
||||||
@@ -97,14 +69,24 @@ Alternatively, you can port forward the port specified in the config (defaults t
|
|||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
If you are looking to setup YoutubeDL-Material with Docker, this section is for you. And you're in luck! Docker setup is quite simple.
|
If you are looking to setup YoutubeDL-Material with Docker, this section is for you. And you're in luck! Docker setup is quite simple.
|
||||||
|
|
||||||
1. Run `curl -L https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest/download/youtubedl-material-docker.zip -o youtubedl-material-docker.zip` to download the latest Docker zip release, or go to the [releases](https://github.com/Tzahi12345/YoutubeDL-Material/releases/) page to grab the version you'd like.
|
1. Run `curl -L https://github.com/Tzahi12345/YoutubeDL-Material/releases/latest/download/docker-compose.yml -o docker-compose.yml` to download the latest Docker Compose, or go to the [releases](https://github.com/Tzahi12345/YoutubeDL-Material/releases/) page to grab the version you'd like.
|
||||||
2. Unzip the `youtubedl-material-docker.zip` and navigate into the root folder.
|
2. Run `docker-compose pull`. This will download the official YoutubeDL-Material docker image.
|
||||||
3. Modify the config items in the `appdata` folder to your liking. The default options will work, however, and point to `http://localhost:8998`. You can find an explanation of these configuration items in [Configuration](#Configuration) section.
|
3. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 8998" or something similar.
|
||||||
4. Run `docker-compose pull`. This will download the official YoutubeDL-Material docker image.
|
4. Make sure you can connect to the specified URL + port, and if so, you are done!
|
||||||
5. Run `docker-compose up` to start it up. If successful, it should say "HTTP(S): Started on port 8998" or something similar.
|
|
||||||
6. Make sure you can connect to the specified URL + port, and if so, you are done!
|
### Custom UID/GID
|
||||||
|
|
||||||
|
By default, the Docker container runs as non-root with UID=1000 and GID=1000. To set this to your own UID/GID, simply update the `environment` section in your `docker-compose.yml` like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
environment:
|
||||||
|
UID: YOUR_UID
|
||||||
|
GID: YOUR_GID
|
||||||
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
@@ -116,12 +98,20 @@ Once you have enabled the API and have the key, you can start sending requests b
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Feel free to submit a pull request! I have no guidelines as of yet, so no need to worry about that.
|
If you're interested in contributing, first: awesome! Second, please refer to the guidelines/setup information located in the [Contributing](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Contributing) wiki page, it's a helpful way to get you on your feet and coding away.
|
||||||
|
|
||||||
|
Pull requests are always appreciated! If you're a bit rusty with coding, that's no problem: we can always help you learn. And if that's too scary, that's OK too! You can create issues for features you'd like to see or bugs you encounter, it all helps this project grow.
|
||||||
|
|
||||||
|
If you're interested in translating the app into a new language, check out the [Translate](https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Translate) wiki page.
|
||||||
|
|
||||||
## Authors
|
## Authors
|
||||||
|
|
||||||
* **Isaac Grynsztein** (me!) - *Initial work*
|
* **Isaac Grynsztein** (me!) - *Initial work*
|
||||||
|
|
||||||
|
Official translators:
|
||||||
|
* Spanish - tzahi12345
|
||||||
|
* German - UnlimitedCookies
|
||||||
|
|
||||||
See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project.
|
See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
29
backend/Dockerfile-armhf
Normal file
29
backend/Dockerfile-armhf
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM arm32v7/alpine:3.12
|
||||||
|
|
||||||
|
COPY qemu-arm-static /usr/bin
|
||||||
|
|
||||||
|
ENV UID=1000 \
|
||||||
|
GID=1000 \
|
||||||
|
USER=youtube
|
||||||
|
|
||||||
|
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
ffmpeg \
|
||||||
|
npm \
|
||||||
|
python2 \
|
||||||
|
su-exec \
|
||||||
|
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
|
||||||
|
atomicparsley
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --chown=$UID:$GID [ "package.json", "package-lock.json", "/app/" ]
|
||||||
|
|
||||||
|
RUN npm install && chown -R $UID:$GID ./
|
||||||
|
|
||||||
|
COPY --chown=$UID:$GID [ "./", "/app/" ]
|
||||||
|
|
||||||
|
EXPOSE 17442
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||||
|
CMD [ "node", "app.js" ]
|
||||||
286
backend/app.js
286
backend/app.js
@@ -30,6 +30,7 @@ var subscriptions_api = require('./subscriptions')
|
|||||||
const CONSTS = require('./consts')
|
const CONSTS = require('./consts')
|
||||||
const { spawn } = require('child_process')
|
const { spawn } = require('child_process')
|
||||||
const read_last_lines = require('read-last-lines');
|
const read_last_lines = require('read-last-lines');
|
||||||
|
var ps = require('ps-node');
|
||||||
|
|
||||||
const is_windows = process.platform === 'win32';
|
const is_windows = process.platform === 'win32';
|
||||||
|
|
||||||
@@ -96,7 +97,6 @@ db.defaults(
|
|||||||
configWriteFlag: false,
|
configWriteFlag: false,
|
||||||
downloads: {},
|
downloads: {},
|
||||||
subscriptions: [],
|
subscriptions: [],
|
||||||
pin_md5: '',
|
|
||||||
files_to_db_migration_complete: false
|
files_to_db_migration_complete: false
|
||||||
}).write();
|
}).write();
|
||||||
|
|
||||||
@@ -504,6 +504,43 @@ async function getLatestVersion() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function killAllDownloads() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
ps.lookup({
|
||||||
|
command: 'youtube-dl',
|
||||||
|
}, function(err, resultList ) {
|
||||||
|
if (err) {
|
||||||
|
// failed to get list of processes
|
||||||
|
logger.error('Failed to get a list of running youtube-dl processes.');
|
||||||
|
logger.error(err);
|
||||||
|
resolve({
|
||||||
|
details: err,
|
||||||
|
success: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// processes that contain the string 'youtube-dl' in the name will be looped
|
||||||
|
resultList.forEach(function( process ){
|
||||||
|
if (process) {
|
||||||
|
ps.kill(process.pid, 'SIGKILL', function( err ) {
|
||||||
|
if (err) {
|
||||||
|
// failed to kill, process may have ended on its own
|
||||||
|
logger.warn(`Failed to kill process with PID ${process.pid}`);
|
||||||
|
logger.warn(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.verbose(`Process ${process.pid} has been killed!`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve({
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function setPortItemFromENV() {
|
async function setPortItemFromENV() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
config_api.setConfigItem('ytdl_port', backendPort.toString());
|
config_api.setConfigItem('ytdl_port', backendPort.toString());
|
||||||
@@ -881,14 +918,16 @@ async function createPlaylistZipFile(fileNames, type, outputName, fullPathProvid
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAudioFile(name, blacklistMode = false) {
|
async function deleteAudioFile(name, customPath = null, blacklistMode = false) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// TODO: split descriptors into audio and video descriptors, as deleting an audio file will close all video file streams
|
let filePath = customPath ? customPath : audioFolderPath;
|
||||||
var jsonPath = path.join(audioFolderPath,name+'.mp3.info.json');
|
|
||||||
var altJSONPath = path.join(audioFolderPath,name+'.info.json');
|
var jsonPath = path.join(filePath,name+'.mp3.info.json');
|
||||||
var audioFilePath = path.join(audioFolderPath,name+'.mp3');
|
var altJSONPath = path.join(filePath,name+'.info.json');
|
||||||
|
var audioFilePath = path.join(filePath,name+'.mp3');
|
||||||
var thumbnailPath = path.join(filePath,name+'.webp');
|
var thumbnailPath = path.join(filePath,name+'.webp');
|
||||||
var altThumbnailPath = path.join(filePath,name+'.jpg');
|
var altThumbnailPath = path.join(filePath,name+'.jpg');
|
||||||
|
|
||||||
jsonPath = path.join(__dirname, jsonPath);
|
jsonPath = path.join(__dirname, jsonPath);
|
||||||
altJSONPath = path.join(__dirname, altJSONPath);
|
altJSONPath = path.join(__dirname, altJSONPath);
|
||||||
audioFilePath = path.join(__dirname, audioFilePath);
|
audioFilePath = path.join(__dirname, audioFilePath);
|
||||||
@@ -928,7 +967,7 @@ async function deleteAudioFile(name, blacklistMode = false) {
|
|||||||
|
|
||||||
// get ID from JSON
|
// get ID from JSON
|
||||||
|
|
||||||
var jsonobj = utils.getJSONMp3(name, audioFolderPath);
|
var jsonobj = utils.getJSONMp3(name, filePath);
|
||||||
let id = null;
|
let id = null;
|
||||||
if (jsonobj) id = jsonobj.id;
|
if (jsonobj) id = jsonobj.id;
|
||||||
|
|
||||||
@@ -964,10 +1003,12 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let filePath = customPath ? customPath : videoFolderPath;
|
let filePath = customPath ? customPath : videoFolderPath;
|
||||||
var jsonPath = path.join(filePath,name+'.info.json');
|
var jsonPath = path.join(filePath,name+'.info.json');
|
||||||
|
|
||||||
var altJSONPath = path.join(filePath,name+'.mp4.info.json');
|
var altJSONPath = path.join(filePath,name+'.mp4.info.json');
|
||||||
var videoFilePath = path.join(filePath,name+'.mp4');
|
var videoFilePath = path.join(filePath,name+'.mp4');
|
||||||
var thumbnailPath = path.join(filePath,name+'.webp');
|
var thumbnailPath = path.join(filePath,name+'.webp');
|
||||||
var altThumbnailPath = path.join(filePath,name+'.jpg');
|
var altThumbnailPath = path.join(filePath,name+'.jpg');
|
||||||
|
|
||||||
jsonPath = path.join(__dirname, jsonPath);
|
jsonPath = path.join(__dirname, jsonPath);
|
||||||
videoFilePath = path.join(__dirname, videoFilePath);
|
videoFilePath = path.join(__dirname, videoFilePath);
|
||||||
|
|
||||||
@@ -1005,7 +1046,7 @@ async function deleteVideoFile(name, customPath = null, blacklistMode = false) {
|
|||||||
|
|
||||||
// get ID from JSON
|
// get ID from JSON
|
||||||
|
|
||||||
var jsonobj = utils.getJSONMp4(name, videoFolderPath);
|
var jsonobj = utils.getJSONMp4(name, filePath);
|
||||||
let id = null;
|
let id = null;
|
||||||
if (jsonobj) id = jsonobj.id;
|
if (jsonobj) id = jsonobj.id;
|
||||||
|
|
||||||
@@ -1194,6 +1235,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
options.customFileFolderPath = fileFolderPath;
|
options.customFileFolderPath = fileFolderPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.downloading_method = 'exec';
|
||||||
const downloadConfig = await generateArgs(url, type, options);
|
const downloadConfig = await generateArgs(url, type, options);
|
||||||
|
|
||||||
// adds download to download helper
|
// adds download to download helper
|
||||||
@@ -1329,6 +1371,7 @@ async function downloadFileByURL_normal(url, type, options, sessionID = null) {
|
|||||||
options.customFileFolderPath = fileFolderPath;
|
options.customFileFolderPath = fileFolderPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.downloading_method = 'normal';
|
||||||
const downloadConfig = await generateArgs(url, type, options);
|
const downloadConfig = await generateArgs(url, type, options);
|
||||||
|
|
||||||
// adds download to download helper
|
// adds download to download helper
|
||||||
@@ -1469,24 +1512,24 @@ async function generateArgs(url, type, options) {
|
|||||||
var youtubePassword = options.youtubePassword;
|
var youtubePassword = options.youtubePassword;
|
||||||
|
|
||||||
let downloadConfig = null;
|
let downloadConfig = null;
|
||||||
let qualityPath = (is_audio && !options.skip_audio_args) ? '-f bestaudio' :'-f best[ext=mp4]';
|
let qualityPath = (is_audio && !options.skip_audio_args) ? ['-f', 'bestaudio'] : ['-f', 'best[ext=mp4]'];
|
||||||
const is_youtube = url.includes('youtu');
|
const is_youtube = url.includes('youtu');
|
||||||
if (!is_audio && !is_youtube) {
|
if (!is_audio && !is_youtube) {
|
||||||
// tiktok videos fail when using the default format
|
// tiktok videos fail when using the default format
|
||||||
qualityPath = null;
|
qualityPath = null;
|
||||||
} else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
|
} else if (!is_audio && !is_youtube && (url.includes('reddit') || url.includes('pornhub'))) {
|
||||||
qualityPath = '-f bestvideo+bestaudio'
|
qualityPath = ['-f', 'bestvideo+bestaudio']
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customArgs) {
|
if (customArgs) {
|
||||||
downloadConfig = customArgs.split(',,');
|
downloadConfig = customArgs.split(',,');
|
||||||
} else {
|
} else {
|
||||||
if (customQualityConfiguration) {
|
if (customQualityConfiguration) {
|
||||||
qualityPath = `-f ${customQualityConfiguration}`;
|
qualityPath = ['-f', customQualityConfiguration];
|
||||||
} else if (selectedHeight && selectedHeight !== '' && !is_audio) {
|
} else if (selectedHeight && selectedHeight !== '' && !is_audio) {
|
||||||
qualityPath = `-f '(mp4)[height=${selectedHeight}]'`;
|
qualityPath = ['-f', `'(mp4)[height=${selectedHeight}'`];
|
||||||
} else if (maxBitrate && is_audio) {
|
} else if (maxBitrate && is_audio) {
|
||||||
qualityPath = `--audio-quality ${maxBitrate}`
|
qualityPath = ['--audio-quality', maxBitrate]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customOutput) {
|
if (customOutput) {
|
||||||
@@ -1495,7 +1538,7 @@ async function generateArgs(url, type, options) {
|
|||||||
downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json'];
|
downloadConfig = ['-o', path.join(fileFolderPath, videopath + (is_audio ? '.%(ext)s' : '.mp4')), '--write-info-json', '--print-json'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qualityPath) downloadConfig.push(qualityPath);
|
if (qualityPath && options.downloading_method === 'exec') downloadConfig.push(...qualityPath);
|
||||||
|
|
||||||
if (is_audio && !options.skip_audio_args) {
|
if (is_audio && !options.skip_audio_args) {
|
||||||
downloadConfig.push('-x');
|
downloadConfig.push('-x');
|
||||||
@@ -1849,7 +1892,7 @@ app.get('/api/config', function(req, res) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/setConfig', function(req, res) {
|
app.post('/api/setConfig', optionalJwt, function(req, res) {
|
||||||
let new_config_file = req.body.new_config_file;
|
let new_config_file = req.body.new_config_file;
|
||||||
if (new_config_file && new_config_file['YoutubeDLMaterial']) {
|
if (new_config_file && new_config_file['YoutubeDLMaterial']) {
|
||||||
let success = config_api.setConfigFile(new_config_file);
|
let success = config_api.setConfigFile(new_config_file);
|
||||||
@@ -1868,23 +1911,6 @@ app.get('/api/using-encryption', function(req, res) {
|
|||||||
res.send(usingEncryption);
|
res.send(usingEncryption);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/logs', async function(req, res) {
|
|
||||||
let logs = null;
|
|
||||||
let lines = req.body.lines;
|
|
||||||
logs_path = path.join('appdata', 'logs', 'combined.log')
|
|
||||||
if (fs.existsSync(logs_path)) {
|
|
||||||
if (lines) logs = await read_last_lines.read(logs_path, lines);
|
|
||||||
else logs = fs.readFileSync(logs_path, 'utf8');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
logger.error(`Failed to find logs file at the expected location: ${logs_path}`)
|
|
||||||
|
|
||||||
res.send({
|
|
||||||
logs: logs,
|
|
||||||
success: !!logs
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/api/tomp3', optionalJwt, async function(req, res) {
|
app.post('/api/tomp3', optionalJwt, async function(req, res) {
|
||||||
var url = req.body.url;
|
var url = req.body.url;
|
||||||
var options = {
|
var options = {
|
||||||
@@ -1912,8 +1938,6 @@ app.post('/api/tomp3', optionalJwt, async function(req, res) {
|
|||||||
} else {
|
} else {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end("yes");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/tomp4', optionalJwt, async function(req, res) {
|
app.post('/api/tomp4', optionalJwt, async function(req, res) {
|
||||||
@@ -1943,50 +1967,11 @@ app.post('/api/tomp4', optionalJwt, async function(req, res) {
|
|||||||
} else {
|
} else {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.end("yes");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// gets the status of the mp3 file that's being downloaded
|
app.post('/api/killAllDownloads', optionalJwt, async function(req, res) {
|
||||||
app.post('/api/fileStatusMp3', function(req, res) {
|
const result_obj = await killAllDownloads();
|
||||||
var name = decodeURIComponent(req.body.name + "");
|
res.send(result_obj);
|
||||||
var exists = "";
|
|
||||||
var fullpath = audioFolderPath + name + ".mp3";
|
|
||||||
if (fs.existsSync(fullpath)) {
|
|
||||||
exists = [basePath + audioFolderPath + name, getFileSizeMp3(name)];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var percent = 0;
|
|
||||||
var size = getFileSizeMp3(name);
|
|
||||||
var downloaded = getAmountDownloadedMp3(name);
|
|
||||||
if (size > 0)
|
|
||||||
percent = downloaded/size;
|
|
||||||
exists = ["failed", getFileSizeMp3(name), percent];
|
|
||||||
}
|
|
||||||
//logger.info(exists + " " + name);
|
|
||||||
res.send(exists);
|
|
||||||
res.end("yes");
|
|
||||||
});
|
|
||||||
|
|
||||||
// gets the status of the mp4 file that's being downloaded
|
|
||||||
app.post('/api/fileStatusMp4', function(req, res) {
|
|
||||||
var name = decodeURIComponent(req.body.name);
|
|
||||||
var exists = "";
|
|
||||||
var fullpath = videoFolderPath + name + ".mp4";
|
|
||||||
if (fs.existsSync(fullpath)) {
|
|
||||||
exists = [basePath + videoFolderPath + name, getFileSizeMp4(name)];
|
|
||||||
} else {
|
|
||||||
var percent = 0;
|
|
||||||
var size = getFileSizeMp4(name);
|
|
||||||
var downloaded = getAmountDownloadedMp4(name);
|
|
||||||
if (size > 0)
|
|
||||||
percent = downloaded/size;
|
|
||||||
exists = ["failed", getFileSizeMp4(name), percent];
|
|
||||||
}
|
|
||||||
//logger.info(exists + " " + name);
|
|
||||||
res.send(exists);
|
|
||||||
res.end("yes");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// gets all download mp3s
|
// gets all download mp3s
|
||||||
@@ -2064,6 +2049,52 @@ app.post('/api/getFile', optionalJwt, function (req, res) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/getAllFiles', optionalJwt, function (req, res) {
|
||||||
|
// these are returned
|
||||||
|
let files = [];
|
||||||
|
let playlists = [];
|
||||||
|
let subscription_files = [];
|
||||||
|
|
||||||
|
let videos = null;
|
||||||
|
let audios = null;
|
||||||
|
let audio_playlists = null;
|
||||||
|
let video_playlists = null;
|
||||||
|
let subscriptions = subscriptions_api.getAllSubscriptions(req.isAuthenticated() ? req.user.uid : null);
|
||||||
|
|
||||||
|
// get basic info depending on multi-user mode being enabled
|
||||||
|
if (req.isAuthenticated()) {
|
||||||
|
videos = auth_api.getUserVideos(req.user.uid, 'video');
|
||||||
|
audios = auth_api.getUserVideos(req.user.uid, 'audio');
|
||||||
|
audio_playlists = auth_api.getUserPlaylists(req.user.uid, 'audio');
|
||||||
|
video_playlists = auth_api.getUserPlaylists(req.user.uid, 'video');
|
||||||
|
} else {
|
||||||
|
videos = db.get('files.audio').value();
|
||||||
|
audios = db.get('files.video').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
|
||||||
|
for (let i = 0; i < subscriptions.length; i++) {
|
||||||
|
sub = subscriptions[i];
|
||||||
|
if (!sub.videos) continue;
|
||||||
|
// add sub id for UI
|
||||||
|
for (let j = 0; j < sub.videos.length; j++) {
|
||||||
|
sub.videos[j].sub_id = sub.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
files = files.concat(sub.videos);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
files: files,
|
||||||
|
playlists: playlists
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 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 type = req.body.type;
|
||||||
@@ -2325,6 +2356,16 @@ app.post('/api/downloadVideosForSubscription', optionalJwt, async (req, res) =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/updateSubscription', optionalJwt, async (req, res) => {
|
||||||
|
let updated_sub = req.body.subscription;
|
||||||
|
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
|
let success = subscriptions_api.updateSubscription(updated_sub, user_uid);
|
||||||
|
res.send({
|
||||||
|
success: success
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/getAllSubscriptions', optionalJwt, async (req, res) => {
|
app.post('/api/getAllSubscriptions', optionalJwt, async (req, res) => {
|
||||||
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
@@ -2341,13 +2382,16 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
|||||||
let fileNames = req.body.fileNames;
|
let fileNames = req.body.fileNames;
|
||||||
let type = req.body.type;
|
let type = req.body.type;
|
||||||
let thumbnailURL = req.body.thumbnailURL;
|
let thumbnailURL = req.body.thumbnailURL;
|
||||||
|
let duration = req.body.duration;
|
||||||
|
|
||||||
let new_playlist = {
|
let new_playlist = {
|
||||||
'name': playlistName,
|
name: playlistName,
|
||||||
fileNames: fileNames,
|
fileNames: fileNames,
|
||||||
id: shortid.generate(),
|
id: shortid.generate(),
|
||||||
thumbnailURL: thumbnailURL,
|
thumbnailURL: thumbnailURL,
|
||||||
type: type
|
type: type,
|
||||||
|
registered: Date.now(),
|
||||||
|
duration: duration
|
||||||
};
|
};
|
||||||
|
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated()) {
|
||||||
@@ -2473,11 +2517,10 @@ app.post('/api/deleteMp3', optionalJwt, async (req, res) => {
|
|||||||
var wasDeleted = false;
|
var wasDeleted = false;
|
||||||
if (fs.existsSync(fullpath))
|
if (fs.existsSync(fullpath))
|
||||||
{
|
{
|
||||||
deleteAudioFile(name, blacklistMode);
|
deleteAudioFile(name, null, blacklistMode);
|
||||||
db.get('files.audio').remove({uid: uid}).write();
|
db.get('files.audio').remove({uid: uid}).write();
|
||||||
wasDeleted = true;
|
wasDeleted = true;
|
||||||
res.send(wasDeleted);
|
res.send(wasDeleted);
|
||||||
res.end("yes");
|
|
||||||
} else if (audio_obj) {
|
} else if (audio_obj) {
|
||||||
db.get('files.audio').remove({uid: uid}).write();
|
db.get('files.audio').remove({uid: uid}).write();
|
||||||
wasDeleted = true;
|
wasDeleted = true;
|
||||||
@@ -2509,7 +2552,6 @@ app.post('/api/deleteMp4', optionalJwt, async (req, res) => {
|
|||||||
db.get('files.video').remove({uid: uid}).write();
|
db.get('files.video').remove({uid: uid}).write();
|
||||||
// wasDeleted = true;
|
// wasDeleted = true;
|
||||||
res.send(wasDeleted);
|
res.send(wasDeleted);
|
||||||
res.end("yes");
|
|
||||||
} else if (video_obj) {
|
} else if (video_obj) {
|
||||||
db.get('files.video').remove({uid: uid}).write();
|
db.get('files.video').remove({uid: uid}).write();
|
||||||
wasDeleted = true;
|
wasDeleted = true;
|
||||||
@@ -2517,7 +2559,6 @@ app.post('/api/deleteMp4', optionalJwt, async (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
wasDeleted = false;
|
wasDeleted = false;
|
||||||
res.send(wasDeleted);
|
res.send(wasDeleted);
|
||||||
res.end("yes");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2551,7 +2592,7 @@ app.post('/api/downloadFile', optionalJwt, async (req, res) => {
|
|||||||
else
|
else
|
||||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
|
|
||||||
file = path.join(__dirname, basePath, (subscriptionPlaylist === 'true' ? 'playlists' : 'channels'), subscriptionName, fileNames + ext);
|
file = path.join(__dirname, basePath, (subscriptionPlaylist === true || subscriptionPlaylist === 'true' ? 'playlists' : 'channels'), subscriptionName, fileNames + ext);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < fileNames.length; i++) {
|
for (let i = 0; i < fileNames.length; i++) {
|
||||||
@@ -2640,49 +2681,6 @@ app.post('/api/updateServer', async (req, res) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pin API calls
|
|
||||||
|
|
||||||
app.post('/api/isPinSet', async (req, res) => {
|
|
||||||
let stored_pin = db.get('pin_md5').value();
|
|
||||||
let is_set = false;
|
|
||||||
if (!stored_pin || stored_pin.length === 0) {
|
|
||||||
} else {
|
|
||||||
is_set = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send({
|
|
||||||
is_set: is_set
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/api/setPin', async (req, res) => {
|
|
||||||
let unhashed_pin = req.body.pin;
|
|
||||||
let hashed_pin = md5(unhashed_pin);
|
|
||||||
|
|
||||||
db.set('pin_md5', hashed_pin).write();
|
|
||||||
|
|
||||||
res.send({
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/api/checkPin', async (req, res) => {
|
|
||||||
let input_pin = req.body.input_pin;
|
|
||||||
let input_pin_md5 = md5(input_pin);
|
|
||||||
|
|
||||||
let stored_pin = db.get('pin_md5').value();
|
|
||||||
|
|
||||||
let successful = false;
|
|
||||||
|
|
||||||
if (input_pin_md5 === stored_pin) {
|
|
||||||
successful = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send({
|
|
||||||
success: successful
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// API Key API calls
|
// API Key API calls
|
||||||
|
|
||||||
app.post('/api/generateNewAPIKey', function (req, res) {
|
app.post('/api/generateNewAPIKey', function (req, res) {
|
||||||
@@ -2868,6 +2866,42 @@ app.get('/api/audio/:id', optionalJwt, function(req , res){
|
|||||||
res.send({success: success, downloads: downloads});
|
res.send({success: success, downloads: downloads});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// logs management
|
||||||
|
|
||||||
|
app.post('/api/logs', async function(req, res) {
|
||||||
|
let logs = null;
|
||||||
|
let lines = req.body.lines;
|
||||||
|
logs_path = path.join('appdata', 'logs', 'combined.log')
|
||||||
|
if (fs.existsSync(logs_path)) {
|
||||||
|
if (lines) logs = await read_last_lines.read(logs_path, lines);
|
||||||
|
else logs = fs.readFileSync(logs_path, 'utf8');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
logger.error(`Failed to find logs file at the expected location: ${logs_path}`)
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
logs: logs,
|
||||||
|
success: !!logs
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/clearAllLogs', async function(req, res) {
|
||||||
|
logs_path = path.join('appdata', 'logs', 'combined.log');
|
||||||
|
logs_err_path = path.join('appdata', 'logs', 'error.log');
|
||||||
|
let success = false;
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(logs_path, '');
|
||||||
|
fs.writeFileSync(logs_err_path, '');
|
||||||
|
success = true;
|
||||||
|
} catch(e) {
|
||||||
|
logger.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
success: success
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/getVideoInfos', async (req, res) => {
|
app.post('/api/getVideoInfos', async (req, res) => {
|
||||||
let fileNames = req.body.fileNames;
|
let fileNames = req.body.fileNames;
|
||||||
let urlMode = !!req.body.urlMode;
|
let urlMode = !!req.body.urlMode;
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"allow_quality_select": true,
|
"allow_quality_select": true,
|
||||||
"download_only_mode": false,
|
"download_only_mode": false,
|
||||||
"allow_multi_download_mode": true,
|
"allow_multi_download_mode": true,
|
||||||
"settings_pin_required": false,
|
|
||||||
"enable_downloads_manager": true
|
"enable_downloads_manager": true
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
@@ -38,8 +37,7 @@
|
|||||||
"Subscriptions": {
|
"Subscriptions": {
|
||||||
"allow_subscriptions": true,
|
"allow_subscriptions": true,
|
||||||
"subscriptions_base_path": "subscriptions/",
|
"subscriptions_base_path": "subscriptions/",
|
||||||
"subscriptions_check_interval": "300",
|
"subscriptions_check_interval": "300"
|
||||||
"subscriptions_use_youtubedl_archive": true
|
|
||||||
},
|
},
|
||||||
"Users": {
|
"Users": {
|
||||||
"base_path": "users/",
|
"base_path": "users/",
|
||||||
@@ -51,6 +49,7 @@
|
|||||||
"multi_user_mode": false,
|
"multi_user_mode": false,
|
||||||
"allow_advanced_download": false,
|
"allow_advanced_download": false,
|
||||||
"use_cookies": false,
|
"use_cookies": false,
|
||||||
|
"jwt_expiration": 86400,
|
||||||
"logger_level": "info"
|
"logger_level": "info"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"allow_quality_select": true,
|
"allow_quality_select": true,
|
||||||
"download_only_mode": false,
|
"download_only_mode": false,
|
||||||
"allow_multi_download_mode": true,
|
"allow_multi_download_mode": true,
|
||||||
"settings_pin_required": false,
|
|
||||||
"enable_downloads_manager": true
|
"enable_downloads_manager": true
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
@@ -38,8 +37,7 @@
|
|||||||
"Subscriptions": {
|
"Subscriptions": {
|
||||||
"allow_subscriptions": true,
|
"allow_subscriptions": true,
|
||||||
"subscriptions_base_path": "subscriptions/",
|
"subscriptions_base_path": "subscriptions/",
|
||||||
"subscriptions_check_interval": "300",
|
"subscriptions_check_interval": "300"
|
||||||
"subscriptions_use_youtubedl_archive": true
|
|
||||||
},
|
},
|
||||||
"Users": {
|
"Users": {
|
||||||
"base_path": "users/",
|
"base_path": "users/",
|
||||||
@@ -51,6 +49,7 @@
|
|||||||
"multi_user_mode": false,
|
"multi_user_mode": false,
|
||||||
"allow_advanced_download": false,
|
"allow_advanced_download": false,
|
||||||
"use_cookies": false,
|
"use_cookies": false,
|
||||||
|
"jwt_expiration": 86400,
|
||||||
"logger_level": "info"
|
"logger_level": "info"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var subscriptions_api = require('../subscriptions')
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
var jwt = require('jsonwebtoken');
|
var jwt = require('jsonwebtoken');
|
||||||
const { uuid } = require('uuidv4');
|
const { uuid } = require('uuidv4');
|
||||||
var bcrypt = require('bcrypt');
|
var bcrypt = require('bcryptjs');
|
||||||
|
|
||||||
|
|
||||||
var LocalStrategy = require('passport-local').Strategy;
|
var LocalStrategy = require('passport-local').Strategy;
|
||||||
@@ -29,7 +29,7 @@ exports.initialize = function(input_users_db, input_logger) {
|
|||||||
************************/
|
************************/
|
||||||
saltRounds = 10;
|
saltRounds = 10;
|
||||||
|
|
||||||
JWT_EXPIRATION = (60 * 60); // one hour
|
JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration');
|
||||||
|
|
||||||
SERVER_SECRET = null;
|
SERVER_SECRET = null;
|
||||||
if (users_db.get('jwt_secret').value()) {
|
if (users_db.get('jwt_secret').value()) {
|
||||||
@@ -307,21 +307,27 @@ exports.getUserVideos = function(user_uid, type) {
|
|||||||
return user['files'][type];
|
return user['files'][type];
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false) {
|
exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false, reqBody = null) {
|
||||||
if (!type) {
|
// check if it's a subscription first
|
||||||
file = users_db.get('users').find({uid: user_uid}).get(`files.audio`).find({uid: file_uid}).value();
|
let file = null;
|
||||||
if (!file) {
|
if (reqBody && reqBody.subscriptionName) {
|
||||||
file = users_db.get('users').find({uid: user_uid}).get(`files.video`).find({uid: file_uid}).value();
|
file = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({name: reqBody.subscriptionName}).get('videos').find({id: reqBody.fileNames}).value();
|
||||||
if (file) type = 'video';
|
} else {
|
||||||
} else {
|
if (!type) {
|
||||||
type = 'audio';
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (requireSharing && !file['sharingEnabled']) file = null;
|
if (file && requireSharing && !file['sharingEnabled']) file = null;
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,6 @@ DEFAULT_CONFIG = {
|
|||||||
"allow_quality_select": true,
|
"allow_quality_select": true,
|
||||||
"download_only_mode": false,
|
"download_only_mode": false,
|
||||||
"allow_multi_download_mode": true,
|
"allow_multi_download_mode": true,
|
||||||
"settings_pin_required": false,
|
|
||||||
"enable_downloads_manager": true
|
"enable_downloads_manager": true
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
@@ -215,8 +214,7 @@ DEFAULT_CONFIG = {
|
|||||||
"Subscriptions": {
|
"Subscriptions": {
|
||||||
"allow_subscriptions": true,
|
"allow_subscriptions": true,
|
||||||
"subscriptions_base_path": "subscriptions/",
|
"subscriptions_base_path": "subscriptions/",
|
||||||
"subscriptions_check_interval": "300",
|
"subscriptions_check_interval": "300"
|
||||||
"subscriptions_use_youtubedl_archive": true
|
|
||||||
},
|
},
|
||||||
"Users": {
|
"Users": {
|
||||||
"base_path": "users/",
|
"base_path": "users/",
|
||||||
@@ -228,6 +226,7 @@ DEFAULT_CONFIG = {
|
|||||||
"multi_user_mode": false,
|
"multi_user_mode": false,
|
||||||
"allow_advanced_download": false,
|
"allow_advanced_download": false,
|
||||||
"use_cookies": false,
|
"use_cookies": false,
|
||||||
|
"jwt_expiration": 86400,
|
||||||
"logger_level": "info"
|
"logger_level": "info"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,6 @@ let CONFIG_ITEMS = {
|
|||||||
'key': 'ytdl_allow_multi_download_mode',
|
'key': 'ytdl_allow_multi_download_mode',
|
||||||
'path': 'YoutubeDLMaterial.Extra.allow_multi_download_mode'
|
'path': 'YoutubeDLMaterial.Extra.allow_multi_download_mode'
|
||||||
},
|
},
|
||||||
'ytdl_settings_pin_required': {
|
|
||||||
'key': 'ytdl_settings_pin_required',
|
|
||||||
'path': 'YoutubeDLMaterial.Extra.settings_pin_required'
|
|
||||||
},
|
|
||||||
'ytdl_enable_downloads_manager': {
|
'ytdl_enable_downloads_manager': {
|
||||||
'key': 'ytdl_enable_downloads_manager',
|
'key': 'ytdl_enable_downloads_manager',
|
||||||
'path': 'YoutubeDLMaterial.Extra.enable_downloads_manager'
|
'path': 'YoutubeDLMaterial.Extra.enable_downloads_manager'
|
||||||
@@ -120,10 +116,6 @@ let CONFIG_ITEMS = {
|
|||||||
'key': 'ytdl_subscriptions_check_interval',
|
'key': 'ytdl_subscriptions_check_interval',
|
||||||
'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_check_interval'
|
'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_check_interval'
|
||||||
},
|
},
|
||||||
'ytdl_subscriptions_use_youtubedl_archive': {
|
|
||||||
'key': 'ytdl_use_youtubedl_archive',
|
|
||||||
'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_use_youtubedl_archive'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
'ytdl_users_base_path': {
|
'ytdl_users_base_path': {
|
||||||
@@ -156,6 +148,10 @@ let CONFIG_ITEMS = {
|
|||||||
'key': 'ytdl_use_cookies',
|
'key': 'ytdl_use_cookies',
|
||||||
'path': 'YoutubeDLMaterial.Advanced.use_cookies'
|
'path': 'YoutubeDLMaterial.Advanced.use_cookies'
|
||||||
},
|
},
|
||||||
|
'ytdl_jwt_expiration': {
|
||||||
|
'key': 'ytdl_jwt_expiration',
|
||||||
|
'path': 'YoutubeDLMaterial.Advanced.jwt_expiration'
|
||||||
|
},
|
||||||
'ytdl_logger_level': {
|
'ytdl_logger_level': {
|
||||||
'key': 'ytdl_logger_level',
|
'key': 'ytdl_logger_level',
|
||||||
'path': 'YoutubeDLMaterial.Advanced.logger_level'
|
'path': 'YoutubeDLMaterial.Advanced.logger_level'
|
||||||
@@ -174,5 +170,5 @@ AVAILABLE_PERMISSIONS = [
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
CONFIG_ITEMS: CONFIG_ITEMS,
|
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||||
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
|
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
|
||||||
CURRENT_VERSION: 'v4.0'
|
CURRENT_VERSION: 'v4.1'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null) {
|
|||||||
} else {
|
} else {
|
||||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||||
}
|
}
|
||||||
|
if (sub_db.get('videos').find({id: file_object.id}).value()) {
|
||||||
|
logger.verbose(`Subscription video ${file_object.id} already exists, skipping DB registration.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
sub_db.get('videos').push(file_object).write();
|
sub_db.get('videos').push(file_object).write();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ fi
|
|||||||
|
|
||||||
# chown current working directory to current user
|
# chown current working directory to current user
|
||||||
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
|
if [ "$*" = "$CMD" ] && [ "$(id -u)" = "0" ]; then
|
||||||
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' +
|
find . \! -user "$UID" -exec chown "$UID:$GID" -R '{}' + || echo "WARNING! Could not change directory ownership. If you manage permissions externally this is fine, otherwise you may experience issues when downloading or deleting videos."
|
||||||
exec su-exec "$UID:$GID" "$0" "$@"
|
exec su-exec "$UID:$GID" "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
3
backend/hooks/post_checkout
Normal file
3
backend/hooks/post_checkout
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# downloads a local copy of qemu on docker-hub build machines
|
||||||
|
curl -L https://github.com/balena-io/qemu/releases/download/v3.0.0%2Bresin/qemu-3.0.0+resin-arm.tar.gz | tar zxvf - -C . && mv qemu-3.0.0+resin-arm/qemu-arm-static .
|
||||||
4
backend/hooks/pre_build
Normal file
4
backend/hooks/pre_build
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Register qemu-*-static for all supported processors except the
|
||||||
|
# current one, but also remove all registered binfmt_misc before
|
||||||
|
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||||
375
backend/package-lock.json
generated
375
backend/package-lock.json
generated
@@ -50,6 +50,11 @@
|
|||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"any-promise": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
|
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
|
||||||
|
},
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
||||||
@@ -64,11 +69,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||||
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
|
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
|
||||||
},
|
},
|
||||||
"aproba": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
|
||||||
},
|
|
||||||
"archiver": {
|
"archiver": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz",
|
||||||
@@ -126,31 +126,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"are-we-there-yet": {
|
|
||||||
"version": "1.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
|
||||||
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
|
|
||||||
"requires": {
|
|
||||||
"delegates": "^1.0.0",
|
|
||||||
"readable-stream": "^2.0.6"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"readable-stream": {
|
|
||||||
"version": "2.3.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
|
||||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
|
||||||
"requires": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.3",
|
|
||||||
"isarray": "~1.0.0",
|
|
||||||
"process-nextick-args": "~2.0.0",
|
|
||||||
"safe-buffer": "~5.1.1",
|
|
||||||
"string_decoder": "~1.1.1",
|
|
||||||
"util-deprecate": "~1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"array-flatten": {
|
"array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
@@ -204,15 +179,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||||
},
|
},
|
||||||
"bcrypt": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==",
|
|
||||||
"requires": {
|
|
||||||
"node-addon-api": "^2.0.0",
|
|
||||||
"node-pre-gyp": "0.14.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bcrypt-pbkdf": {
|
"bcrypt-pbkdf": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
@@ -221,6 +187,11 @@
|
|||||||
"tweetnacl": "^0.14.3"
|
"tweetnacl": "^0.14.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"bcryptjs": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||||
|
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
|
||||||
|
},
|
||||||
"big-integer": {
|
"big-integer": {
|
||||||
"version": "1.6.48",
|
"version": "1.6.48",
|
||||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||||
@@ -434,11 +405,6 @@
|
|||||||
"readdirp": "~3.3.0"
|
"readdirp": "~3.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chownr": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
|
||||||
},
|
|
||||||
"ci-info": {
|
"ci-info": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
|
||||||
@@ -449,11 +415,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
|
||||||
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
|
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
|
||||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
|
||||||
},
|
|
||||||
"color": {
|
"color": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
|
||||||
@@ -614,10 +575,10 @@
|
|||||||
"xdg-basedir": "^3.0.0"
|
"xdg-basedir": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"connected-domain": {
|
||||||
"version": "1.1.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/connected-domain/-/connected-domain-1.0.0.tgz",
|
||||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
"integrity": "sha1-v+dyOMdL5FOnnwy2BY3utPI1jpM="
|
||||||
},
|
},
|
||||||
"content-disposition": {
|
"content-disposition": {
|
||||||
"version": "0.5.3",
|
"version": "0.5.3",
|
||||||
@@ -718,11 +679,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
},
|
},
|
||||||
"delegates": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
|
||||||
},
|
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
@@ -733,11 +689,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||||
},
|
},
|
||||||
"detect-libc": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
|
||||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
|
||||||
},
|
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
|
||||||
@@ -1051,14 +1002,6 @@
|
|||||||
"universalify": "^1.0.0"
|
"universalify": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs-minipass": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
|
||||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
|
||||||
"requires": {
|
|
||||||
"minipass": "^2.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fs.realpath": {
|
"fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@@ -1081,54 +1024,6 @@
|
|||||||
"rimraf": "2"
|
"rimraf": "2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gauge": {
|
|
||||||
"version": "2.7.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
|
||||||
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
|
|
||||||
"requires": {
|
|
||||||
"aproba": "^1.0.3",
|
|
||||||
"console-control-strings": "^1.0.0",
|
|
||||||
"has-unicode": "^2.0.0",
|
|
||||||
"object-assign": "^4.1.0",
|
|
||||||
"signal-exit": "^3.0.0",
|
|
||||||
"string-width": "^1.0.1",
|
|
||||||
"strip-ansi": "^3.0.1",
|
|
||||||
"wide-align": "^1.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
|
||||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
|
||||||
},
|
|
||||||
"is-fullwidth-code-point": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
|
||||||
"requires": {
|
|
||||||
"number-is-nan": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string-width": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
|
||||||
"requires": {
|
|
||||||
"code-point-at": "^1.0.0",
|
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
|
||||||
"strip-ansi": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
|
||||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "^2.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"get-stream": {
|
"get-stream": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
|
||||||
@@ -1228,11 +1123,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||||
},
|
},
|
||||||
"has-unicode": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
|
|
||||||
},
|
|
||||||
"hashish": {
|
"hashish": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/hashish/-/hashish-0.0.4.tgz",
|
||||||
@@ -1301,14 +1191,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
|
||||||
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk="
|
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk="
|
||||||
},
|
},
|
||||||
"ignore-walk": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
|
||||||
"requires": {
|
|
||||||
"minimatch": "^3.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"import-lazy": {
|
"import-lazy": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
|
||||||
@@ -1596,9 +1478,9 @@
|
|||||||
"integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc="
|
"integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc="
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.19",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||||
},
|
},
|
||||||
"lodash.defaults": {
|
"lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
@@ -1782,30 +1664,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
},
|
},
|
||||||
"minipass": {
|
|
||||||
"version": "2.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
|
||||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"yallist": "^3.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"yallist": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minizlib": {
|
|
||||||
"version": "1.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
|
||||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
|
||||||
"requires": {
|
|
||||||
"minipass": "^2.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
|
||||||
@@ -1859,46 +1717,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mz": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
|
"requires": {
|
||||||
|
"any-promise": "^1.0.0",
|
||||||
|
"object-assign": "^4.0.1",
|
||||||
|
"thenify-all": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nanoid": {
|
"nanoid": {
|
||||||
"version": "2.1.11",
|
"version": "2.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
|
||||||
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
|
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
|
||||||
},
|
},
|
||||||
"needle": {
|
|
||||||
"version": "2.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
|
|
||||||
"integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "^3.2.6",
|
|
||||||
"iconv-lite": "^0.4.4",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "3.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
|
||||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "^2.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"negotiator": {
|
"negotiator": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||||
},
|
},
|
||||||
"node-addon-api": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA=="
|
|
||||||
},
|
|
||||||
"node-fetch": {
|
"node-fetch": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||||
@@ -1912,34 +1750,6 @@
|
|||||||
"iconv-lite": "^0.4.15"
|
"iconv-lite": "^0.4.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-pre-gyp": {
|
|
||||||
"version": "0.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz",
|
|
||||||
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
|
|
||||||
"requires": {
|
|
||||||
"detect-libc": "^1.0.2",
|
|
||||||
"mkdirp": "^0.5.1",
|
|
||||||
"needle": "^2.2.1",
|
|
||||||
"nopt": "^4.0.1",
|
|
||||||
"npm-packlist": "^1.1.6",
|
|
||||||
"npmlog": "^4.0.2",
|
|
||||||
"rc": "^1.2.7",
|
|
||||||
"rimraf": "^2.6.1",
|
|
||||||
"semver": "^5.3.0",
|
|
||||||
"tar": "^4.4.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"nopt": {
|
|
||||||
"version": "4.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
|
||||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
|
||||||
"requires": {
|
|
||||||
"abbrev": "1",
|
|
||||||
"osenv": "^0.1.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nodemon": {
|
"nodemon": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz",
|
||||||
@@ -1985,29 +1795,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||||
},
|
},
|
||||||
"npm-bundled": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
|
|
||||||
"requires": {
|
|
||||||
"npm-normalize-package-bin": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm-normalize-package-bin": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
|
|
||||||
},
|
|
||||||
"npm-packlist": {
|
|
||||||
"version": "1.4.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
|
||||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
|
||||||
"requires": {
|
|
||||||
"ignore-walk": "^3.0.1",
|
|
||||||
"npm-bundled": "^1.0.1",
|
|
||||||
"npm-normalize-package-bin": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"npm-run-path": {
|
"npm-run-path": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
|
||||||
@@ -2016,22 +1803,6 @@
|
|||||||
"path-key": "^3.0.0"
|
"path-key": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmlog": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
|
|
||||||
"requires": {
|
|
||||||
"are-we-there-yet": "~1.1.2",
|
|
||||||
"console-control-strings": "~1.1.0",
|
|
||||||
"gauge": "~2.7.3",
|
|
||||||
"set-blocking": "~2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"number-is-nan": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
|
||||||
},
|
|
||||||
"oauth-sign": {
|
"oauth-sign": {
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||||
@@ -2076,25 +1847,6 @@
|
|||||||
"mimic-fn": "^2.1.0"
|
"mimic-fn": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"os-homedir": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
|
||||||
},
|
|
||||||
"os-tmpdir": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
|
||||||
},
|
|
||||||
"osenv": {
|
|
||||||
"version": "0.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
|
||||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
|
||||||
"requires": {
|
|
||||||
"os-homedir": "^1.0.0",
|
|
||||||
"os-tmpdir": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-finally": {
|
"p-finally": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz",
|
||||||
@@ -2243,6 +1995,14 @@
|
|||||||
"ipaddr.js": "1.9.1"
|
"ipaddr.js": "1.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ps-node": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/ps-node/-/ps-node-0.1.6.tgz",
|
||||||
|
"integrity": "sha1-mvZ6mdex0BMuUaUDCZ04qNKs4sM=",
|
||||||
|
"requires": {
|
||||||
|
"table-parser": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pseudomap": {
|
"pseudomap": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||||
@@ -2311,6 +2071,14 @@
|
|||||||
"strip-json-comments": "~2.0.1"
|
"strip-json-comments": "~2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"read-last-lines": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/read-last-lines/-/read-last-lines-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-K0yUvTYAYn6qpyLJufaJ7yC6BeL23qpgZ8SKM7/fA1R1rHotCDxB/zDp9i1I2JHvexWBW6/35jkt07iiIKKp4g==",
|
||||||
|
"requires": {
|
||||||
|
"mz": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||||
@@ -2403,11 +2171,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"sax": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
|
||||||
},
|
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||||
@@ -2459,11 +2222,6 @@
|
|||||||
"send": "0.17.1"
|
"send": "0.17.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"set-blocking": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
|
||||||
},
|
|
||||||
"setimmediate": {
|
"setimmediate": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
@@ -2603,25 +2361,12 @@
|
|||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tar": {
|
"table-parser": {
|
||||||
"version": "4.4.13",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/table-parser/-/table-parser-0.1.3.tgz",
|
||||||
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
|
"integrity": "sha1-BEHPzhallIFoTCfRtaZ/8VpDx7A=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chownr": "^1.1.1",
|
"connected-domain": "^1.0.0"
|
||||||
"fs-minipass": "^1.2.5",
|
|
||||||
"minipass": "^2.8.6",
|
|
||||||
"minizlib": "^1.2.1",
|
|
||||||
"mkdirp": "^0.5.0",
|
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"yallist": "^3.0.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"yallist": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tar-stream": {
|
"tar-stream": {
|
||||||
@@ -2724,6 +2469,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||||
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
|
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
|
||||||
},
|
},
|
||||||
|
"thenify": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||||
|
"requires": {
|
||||||
|
"any-promise": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thenify-all": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
|
"integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
|
||||||
|
"requires": {
|
||||||
|
"thenify": ">= 3.1.0 < 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"timed-out": {
|
"timed-out": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
|
||||||
@@ -2939,14 +2700,6 @@
|
|||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wide-align": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
|
||||||
"requires": {
|
|
||||||
"string-width": "^1.0.2 || 2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"widest-line": {
|
"widest-line": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^3.1.1",
|
"archiver": "^3.1.1",
|
||||||
"async": "^3.1.0",
|
"async": "^3.1.0",
|
||||||
"bcrypt": "^4.0.1",
|
"bcryptjs": "^2.4.0",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"config": "^3.2.3",
|
"config": "^3.2.3",
|
||||||
"exe": "^1.0.2",
|
"exe": "^1.0.2",
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
|
"ps-node": "^0.1.6",
|
||||||
"read-last-lines": "^1.7.2",
|
"read-last-lines": "^1.7.2",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"unzipper": "^0.10.10",
|
"unzipper": "^0.10.10",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
1
backend/public/1-es5.058e3b6a3de95b8ddbf8.js
Normal file
1
backend/public/1-es5.058e3b6a3de95b8ddbf8.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -190,6 +190,32 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
is-retina
|
||||||
|
MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Kyle Mathews
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ng-lazyload-image
|
ng-lazyload-image
|
||||||
MIT
|
MIT
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
@@ -215,6 +241,30 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
ngx-avatar
|
||||||
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Haithem Mosbahi, ngx-avatar
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
ngx-file-drop
|
ngx-file-drop
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
@@ -658,6 +708,9 @@ Apache-2.0
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ts-md5
|
||||||
|
MIT
|
||||||
|
|
||||||
tslib
|
tslib
|
||||||
Apache-2.0
|
Apache-2.0
|
||||||
Apache License
|
Apache License
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
||||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
||||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
||||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Benutzer-UID",
|
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Benutzer-UID:",
|
||||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Neues Passwort",
|
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Neues Passwort",
|
||||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Neues Passwort festlegen",
|
"6498fa1b8f563988f769654a75411bb8060134b9": "Neues Passwort festlegen",
|
||||||
"40da072004086c9ec00d125165da91eaade7f541": "Standard verwenden",
|
"40da072004086c9ec00d125165da91eaade7f541": "Standard verwenden",
|
||||||
|
|||||||
@@ -196,5 +196,20 @@
|
|||||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": " Rol ",
|
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": " Rol ",
|
||||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": " Acciones ",
|
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": " Acciones ",
|
||||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Agregar Usuarios",
|
"4d92a0395dd66778a931460118626c5794a3fc7a": "Agregar Usuarios",
|
||||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Editar Rol"
|
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Editar Rol",
|
||||||
|
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modify playlist",
|
||||||
|
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Editar",
|
||||||
|
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Sube nuevas cookies",
|
||||||
|
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Arrastrar y soltar",
|
||||||
|
"85e0725c870b28458fd3bbba905397d890f00a69": "NOTA: Cargar nuevas cookies anulará sus cookies anteriores. También tenga en cuenta que las cookies son de toda la instancia, no por usuario.",
|
||||||
|
"d01715b75228878a773ae6d059acc639d4898a03": "Anulación de descarga segura",
|
||||||
|
"00e274c496b094a019f0679c3fab3945793f3335": "Seleccione un nivel de registrador",
|
||||||
|
"431e5f3a0dde88768d1074baedd65266412b3f02": "Utilizar Cookies",
|
||||||
|
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Establecer Cookies",
|
||||||
|
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Registros",
|
||||||
|
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Solo audio",
|
||||||
|
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Estos se agregan después de los argumentos estándar.",
|
||||||
|
"98b6ec9ec138186d663e64770267b67334353d63": "Salida de archivo personalizada",
|
||||||
|
"fd59fb984749fcdb5e386ae85faec82f8e5ac098": "Los registros aparecerán aquí",
|
||||||
|
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Líneas:"
|
||||||
}
|
}
|
||||||
@@ -14,5 +14,5 @@
|
|||||||
<link rel="stylesheet" href="styles.5112d6db78cf21541598.css"></head>
|
<link rel="stylesheet" href="styles.5112d6db78cf21541598.css"></head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
<script src="runtime-es2015.1f02852de81190376ba1.js" type="module"></script><script src="runtime-es5.1f02852de81190376ba1.js" nomodule defer></script><script src="polyfills-es5.7f923c8f5afda210edd3.js" nomodule defer></script><script src="polyfills-es2015.5b408f108bcea938a7e2.js" type="module"></script><script src="main-es2015.0cbc545a4a3bee376826.js" type="module"></script><script src="main-es5.0cbc545a4a3bee376826.js" nomodule defer></script></body>
|
<script src="runtime-es2015.49084170e55eb5da181d.js" type="module"></script><script src="runtime-es5.49084170e55eb5da181d.js" nomodule defer></script><script src="polyfills-es5.7f923c8f5afda210edd3.js" nomodule defer></script><script src="polyfills-es2015.5b408f108bcea938a7e2.js" type="module"></script><script src="main-es2015.63a61681b752f95b7352.js" type="module"></script><script src="main-es5.63a61681b752f95b7352.js" nomodule defer></script></body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
1
backend/public/main-es2015.63a61681b752f95b7352.js
Normal file
1
backend/public/main-es2015.63a61681b752f95b7352.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
backend/public/main-es5.63a61681b752f95b7352.js
Normal file
1
backend/public/main-es5.63a61681b752f95b7352.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es2015."+{1:"6bc1f7cd24dfb6add92c"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es2015."+{1:"058e3b6a3de95b8ddbf8"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
||||||
@@ -1 +1 @@
|
|||||||
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es5."+{1:"6bc1f7cd24dfb6add92c"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es5."+{1:"058e3b6a3de95b8ddbf8"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);
|
||||||
@@ -81,7 +81,15 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
|||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// get videos
|
// get videos
|
||||||
let downloadConfig = ['--dump-json', '--playlist-end', '1']
|
let downloadConfig = ['--dump-json', '--playlist-end', '1'];
|
||||||
|
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
||||||
|
if (useCookies) {
|
||||||
|
if (fs.existsSync(path.join(__dirname, 'appdata', 'cookies.txt'))) {
|
||||||
|
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
||||||
|
} else {
|
||||||
|
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
|
||||||
|
}
|
||||||
|
}
|
||||||
youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
|
youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
logger.info('Subscribe: got info for subscription ' + sub.id);
|
logger.info('Subscribe: got info for subscription ' + sub.id);
|
||||||
@@ -115,7 +123,7 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||||
if (useArchive && !sub.archive) {
|
if (useArchive && !sub.archive) {
|
||||||
// must create the archive
|
// must create the archive
|
||||||
const archive_dir = path.join(__dirname, basePath, 'archives', sub.name);
|
const archive_dir = path.join(__dirname, basePath, 'archives', sub.name);
|
||||||
@@ -158,6 +166,11 @@ async function unsubscribe(sub, deleteMode, user_uid = null) {
|
|||||||
else
|
else
|
||||||
db.get('subscriptions').remove({id: id}).write();
|
db.get('subscriptions').remove({id: id}).write();
|
||||||
|
|
||||||
|
// failed subs have no name, on unsubscribe they shouldn't error
|
||||||
|
if (!sub.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
if (deleteMode && fs.existsSync(appendedBasePath)) {
|
if (deleteMode && fs.existsSync(appendedBasePath)) {
|
||||||
if (sub.archive && fs.existsSync(sub.archive)) {
|
if (sub.archive && fs.existsSync(sub.archive)) {
|
||||||
@@ -184,7 +197,7 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
|||||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||||
}
|
}
|
||||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||||
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
const name = file;
|
const name = file;
|
||||||
let retrievedID = null;
|
let retrievedID = null;
|
||||||
@@ -260,7 +273,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
|||||||
else
|
else
|
||||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
|
|
||||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||||
|
|
||||||
let appendedBasePath = null
|
let appendedBasePath = null
|
||||||
appendedBasePath = getAppendedBasePath(sub, basePath);
|
appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
@@ -398,7 +411,6 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_
|
|||||||
// add to db
|
// add to db
|
||||||
sub_db.get('videos').push(output_json).write();
|
sub_db.get('videos').push(output_json).write();
|
||||||
} else {
|
} else {
|
||||||
// TODO: make multiUserMode obj
|
|
||||||
db_api.registerFileDB(path.basename(output_json['_filename']), sub.type, multiUserMode, sub);
|
db_api.registerFileDB(path.basename(output_json['_filename']), sub.type, multiUserMode, sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,6 +429,15 @@ function getSubscription(subID, user_uid = null) {
|
|||||||
return db.get('subscriptions').find({id: subID}).value();
|
return db.get('subscriptions').find({id: subID}).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSubscription(sub, user_uid = null) {
|
||||||
|
if (user_uid) {
|
||||||
|
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(sub).write();
|
||||||
|
} else {
|
||||||
|
db.get('subscriptions').find({id: sub.id}).assign(sub).write();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function subExists(subID, user_uid = null) {
|
function subExists(subID, user_uid = null) {
|
||||||
if (user_uid)
|
if (user_uid)
|
||||||
return !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value();
|
return !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value();
|
||||||
@@ -476,6 +497,7 @@ function removeIDFromArchive(archive_path, id) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
getSubscription : getSubscription,
|
getSubscription : getSubscription,
|
||||||
getAllSubscriptions : getAllSubscriptions,
|
getAllSubscriptions : getAllSubscriptions,
|
||||||
|
updateSubscription : updateSubscription,
|
||||||
subscribe : subscribe,
|
subscribe : subscribe,
|
||||||
unsubscribe : unsubscribe,
|
unsubscribe : unsubscribe,
|
||||||
deleteSubscriptionFile : deleteSubscriptionFile,
|
deleteSubscriptionFile : deleteSubscriptionFile,
|
||||||
|
|||||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-dl-material",
|
"name": "youtube-dl-material",
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6588,6 +6588,11 @@
|
|||||||
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
|
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-retina": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M="
|
||||||
|
},
|
||||||
"is-stream": {
|
"is-stream": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
@@ -9137,6 +9142,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-7.1.0.tgz",
|
||||||
"integrity": "sha512-1fip2FdPBDRnjGyBokI/DupBxOnrKh2lbtT8X8N1oPbE3KBZXXl82VIKcK2Sx+XQD67/+VtFzlISmrgsatzYuw=="
|
"integrity": "sha512-1fip2FdPBDRnjGyBokI/DupBxOnrKh2lbtT8X8N1oPbE3KBZXXl82VIKcK2Sx+XQD67/+VtFzlISmrgsatzYuw=="
|
||||||
},
|
},
|
||||||
|
"ngx-avatar": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngx-avatar/-/ngx-avatar-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Uk40UXl26RvDy1ori9NDsGFB+f84AaxMnsIwZA6JPJK0pLcbo3F4vZTmzLZeOusOw1Qtgk5IzF630jo06keXwQ==",
|
||||||
|
"requires": {
|
||||||
|
"is-retina": "^1.0.3",
|
||||||
|
"ts-md5": "^1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ngx-file-drop": {
|
"ngx-file-drop": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-9.0.1.tgz",
|
||||||
@@ -13457,6 +13471,11 @@
|
|||||||
"utf8-byte-length": "^1.0.1"
|
"utf8-byte-length": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ts-md5": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-emODogvKGWi1KO1l9c6YxLMBn6CEH3VrH5mVPIyOtxBG52BvV4jP3GWz6bOZCz61nLgBc3ffQYE4+EHfCD+V7w=="
|
||||||
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-3.0.6.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-dl-material",
|
"name": "youtube-dl-material",
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
"fingerprintjs2": "^2.1.0",
|
"fingerprintjs2": "^2.1.0",
|
||||||
"nan": "^2.14.1",
|
"nan": "^2.14.1",
|
||||||
"ng-lazyload-image": "^7.0.1",
|
"ng-lazyload-image": "^7.0.1",
|
||||||
|
"ngx-avatar": "^4.0.0",
|
||||||
"ngx-file-drop": "^9.0.1",
|
"ngx-file-drop": "^9.0.1",
|
||||||
"ngx-videogular": "^9.0.1",
|
"ngx-videogular": "^9.0.1",
|
||||||
"rxjs": "^6.5.3",
|
"rxjs": "^6.5.3",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<span i18n="Dark mode toggle label">Dark</span>
|
<span i18n="Dark mode toggle label">Dark</span>
|
||||||
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
|
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('settings')" (click)="openSettingsDialog()" mat-menu-item>
|
<button *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('settings')))" (click)="openSettingsDialog()" mat-menu-item>
|
||||||
<mat-icon>settings</mat-icon>
|
<mat-icon>settings</mat-icon>
|
||||||
<span i18n="Settings menu label">Settings</span>
|
<span i18n="Settings menu label">Settings</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -38,12 +38,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sidenav-container" style="height: calc(100% - 64px)">
|
<div class="sidenav-container" style="height: calc(100% - 64px)">
|
||||||
<mat-sidenav-container style="height: 100%">
|
<mat-sidenav-container style="height: 100%">
|
||||||
<mat-sidenav #sidenav>
|
<mat-sidenav [opened]="postsService.sidepanel_mode === 'side' && router.url === '/home'" [mode]="postsService.sidepanel_mode" #sidenav>
|
||||||
<mat-nav-list>
|
<mat-nav-list>
|
||||||
<a *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn)" mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
|
<a *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn)" mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
|
||||||
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
|
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
|
||||||
<a *ngIf="postsService.config && allowSubscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
<a *ngIf="postsService.config && allowSubscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||||
<a *ngIf="postsService.config && enableDownloadsManager && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('downloads_manager')))" mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
<a *ngIf="postsService.config && enableDownloadsManager && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('downloads_manager')))" mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||||
|
<ng-container *ngIf="postsService.config && allowSubscriptions && postsService.subscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<a *ngFor="let subscription of postsService.subscriptions" mat-list-item (click)="sidenav.close()" [routerLink]="['/subscription', { id: subscription.id }]"><ngx-avatar [style.margin-right]="'10px'" size="32" [name]="subscription.name"></ngx-avatar><ng-container i18n="Navigation menu Downloads Page title">{{subscription.name}}</ng-container></a>
|
||||||
|
</ng-container>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">
|
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { Router, NavigationStart, NavigationEnd } from '@angular/router';
|
|||||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
import { THEMES_CONFIG } from '../themes';
|
import { THEMES_CONFIG } from '../themes';
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
|
|
||||||
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
||||||
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component';
|
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component';
|
||||||
import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component';
|
import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component';
|
||||||
@@ -42,8 +41,6 @@ export class AppComponent implements OnInit {
|
|||||||
allowThemeChange = null;
|
allowThemeChange = null;
|
||||||
allowSubscriptions = false;
|
allowSubscriptions = false;
|
||||||
enableDownloadsManager = false;
|
enableDownloadsManager = false;
|
||||||
// defaults to true to prevent attack
|
|
||||||
settingsPinRequired = true;
|
|
||||||
|
|
||||||
@ViewChild('sidenav') sidenav: MatSidenav;
|
@ViewChild('sidenav') sidenav: MatSidenav;
|
||||||
@ViewChild('hamburgerMenu', { read: ElementRef }) hamburgerMenuButton: ElementRef;
|
@ViewChild('hamburgerMenu', { read: ElementRef }) hamburgerMenuButton: ElementRef;
|
||||||
@@ -79,7 +76,6 @@ export class AppComponent implements OnInit {
|
|||||||
loadConfig() {
|
loadConfig() {
|
||||||
// loading config
|
// loading config
|
||||||
this.topBarTitle = this.postsService.config['Extra']['title_top'];
|
this.topBarTitle = this.postsService.config['Extra']['title_top'];
|
||||||
this.settingsPinRequired = this.postsService.config['Extra']['settings_pin_required'];
|
|
||||||
const themingExists = this.postsService.config['Themes'];
|
const themingExists = this.postsService.config['Themes'];
|
||||||
this.defaultTheme = themingExists ? this.postsService.config['Themes']['default_theme'] : 'default';
|
this.defaultTheme = themingExists ? this.postsService.config['Themes']['default_theme'] : 'default';
|
||||||
this.allowThemeChange = themingExists ? this.postsService.config['Themes']['allow_theme_change'] : true;
|
this.allowThemeChange = themingExists ? this.postsService.config['Themes']['allow_theme_change'] : true;
|
||||||
@@ -90,6 +86,13 @@ export class AppComponent implements OnInit {
|
|||||||
if (!localStorage.getItem('theme')) {
|
if (!localStorage.getItem('theme')) {
|
||||||
this.setTheme(themingExists ? this.defaultTheme : 'default');
|
this.setTheme(themingExists ? this.defaultTheme : 'default');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gets the subscriptions
|
||||||
|
if (this.allowSubscriptions) {
|
||||||
|
this.postsService.getAllSubscriptions().subscribe(res => {
|
||||||
|
this.postsService.subscriptions = res['subscriptions'];
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// theme stuff
|
// theme stuff
|
||||||
@@ -166,6 +169,11 @@ onSetTheme(theme, old_theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getSubscriptions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
goBack() {
|
goBack() {
|
||||||
if (!this.navigator) {
|
if (!this.navigator) {
|
||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
@@ -175,31 +183,11 @@ onSetTheme(theme, old_theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openSettingsDialog() {
|
openSettingsDialog() {
|
||||||
if (this.settingsPinRequired) {
|
|
||||||
this.openPinDialog();
|
|
||||||
} else {
|
|
||||||
this.actuallyOpenSettingsDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actuallyOpenSettingsDialog() {
|
|
||||||
const dialogRef = this.dialog.open(SettingsComponent, {
|
const dialogRef = this.dialog.open(SettingsComponent, {
|
||||||
width: '80vw'
|
width: '80vw'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openPinDialog() {
|
|
||||||
const dialogRef = this.dialog.open(CheckOrSetPinDialogComponent, {
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(res => {
|
|
||||||
if (res) {
|
|
||||||
this.actuallyOpenSettingsDialog();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
openAboutDialog() {
|
openAboutDialog() {
|
||||||
const dialogRef = this.dialog.open(AboutDialogComponent, {
|
const dialogRef = this.dialog.open(AboutDialogComponent, {
|
||||||
width: '80vw'
|
width: '80vw'
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ import { SubscriptionComponent } from './subscription//subscription/subscription
|
|||||||
import { SubscriptionFileCardComponent } from './subscription/subscription-file-card/subscription-file-card.component';
|
import { SubscriptionFileCardComponent } from './subscription/subscription-file-card/subscription-file-card.component';
|
||||||
import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dialog/subscription-info-dialog.component';
|
import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dialog/subscription-info-dialog.component';
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
|
|
||||||
import { MatChipsModule } from '@angular/material/chips';
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
import { NgxFileDropModule } from 'ngx-file-drop';
|
import { NgxFileDropModule } from 'ngx-file-drop';
|
||||||
|
import { AvatarModule } from 'ngx-avatar';
|
||||||
|
|
||||||
import es from '@angular/common/locales/es';
|
import es from '@angular/common/locales/es';
|
||||||
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
||||||
@@ -73,6 +73,11 @@ import { ManageRoleComponent } from './components/manage-role/manage-role.compon
|
|||||||
import { CookiesUploaderDialogComponent } from './dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
|
import { CookiesUploaderDialogComponent } from './dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
|
||||||
import { LogsViewerComponent } from './components/logs-viewer/logs-viewer.component';
|
import { LogsViewerComponent } from './components/logs-viewer/logs-viewer.component';
|
||||||
import { ModifyPlaylistComponent } from './dialogs/modify-playlist/modify-playlist.component';
|
import { ModifyPlaylistComponent } from './dialogs/modify-playlist/modify-playlist.component';
|
||||||
|
import { ConfirmDialogComponent } from './dialogs/confirm-dialog/confirm-dialog.component';
|
||||||
|
import { UnifiedFileCardComponent } from './components/unified-file-card/unified-file-card.component';
|
||||||
|
import { RecentVideosComponent } from './components/recent-videos/recent-videos.component';
|
||||||
|
import { EditSubscriptionDialogComponent } from './dialogs/edit-subscription-dialog/edit-subscription-dialog.component';
|
||||||
|
import { CustomPlaylistsComponent } from './components/custom-playlists/custom-playlists.component';
|
||||||
|
|
||||||
registerLocaleData(es, 'es');
|
registerLocaleData(es, 'es');
|
||||||
|
|
||||||
@@ -95,7 +100,6 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
SubscriptionFileCardComponent,
|
SubscriptionFileCardComponent,
|
||||||
SubscriptionInfoDialogComponent,
|
SubscriptionInfoDialogComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
CheckOrSetPinDialogComponent,
|
|
||||||
AboutDialogComponent,
|
AboutDialogComponent,
|
||||||
VideoInfoDialogComponent,
|
VideoInfoDialogComponent,
|
||||||
ArgModifierDialogComponent,
|
ArgModifierDialogComponent,
|
||||||
@@ -113,7 +117,12 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
ManageRoleComponent,
|
ManageRoleComponent,
|
||||||
CookiesUploaderDialogComponent,
|
CookiesUploaderDialogComponent,
|
||||||
LogsViewerComponent,
|
LogsViewerComponent,
|
||||||
ModifyPlaylistComponent
|
ModifyPlaylistComponent,
|
||||||
|
ConfirmDialogComponent,
|
||||||
|
UnifiedFileCardComponent,
|
||||||
|
RecentVideosComponent,
|
||||||
|
EditSubscriptionDialogComponent,
|
||||||
|
CustomPlaylistsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -154,6 +163,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
DragDropModule,
|
DragDropModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
NgxFileDropModule,
|
NgxFileDropModule,
|
||||||
|
AvatarModule,
|
||||||
VgCoreModule,
|
VgCoreModule,
|
||||||
VgControlsModule,
|
VgControlsModule,
|
||||||
VgOverlayPlayModule,
|
VgOverlayPlayModule,
|
||||||
@@ -167,8 +177,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
|||||||
CreatePlaylistComponent,
|
CreatePlaylistComponent,
|
||||||
SubscribeDialogComponent,
|
SubscribeDialogComponent,
|
||||||
SubscriptionInfoDialogComponent,
|
SubscriptionInfoDialogComponent,
|
||||||
SettingsComponent,
|
SettingsComponent
|
||||||
CheckOrSetPinDialogComponent
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
PostsService
|
PostsService
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div *ngIf="playlists && playlists.length > 0">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div *ngFor="let playlist of playlists; let i = index" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 mb-2 mt-2 file-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 mb-2 mt-2 file-col' : '' ]">
|
||||||
|
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" (goToFile)="goToPlaylist($event)" [file_obj]="playlist" [is_playlist]="true" (editPlaylist)="editPlaylistDialog($event)" (deleteFile)="deletePlaylist($event)"></app-unified-file-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="playlists && playlists.length === 0" style="text-align: center;">
|
||||||
|
No playlists available. Create one from your downloading files by clicking the blue plus button.
|
||||||
|
</div>
|
||||||
|
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog()" mat-fab><mat-icon>add</mat-icon></button></div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.add-playlist-button {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
bottom: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-col {
|
||||||
|
max-width: 240px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CustomPlaylistsComponent } from './custom-playlists.component';
|
||||||
|
|
||||||
|
describe('CustomPlaylistsComponent', () => {
|
||||||
|
let component: CustomPlaylistsComponent;
|
||||||
|
let fixture: ComponentFixture<CustomPlaylistsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CustomPlaylistsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CustomPlaylistsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component';
|
||||||
|
import { ModifyPlaylistComponent } from 'app/dialogs/modify-playlist/modify-playlist.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-custom-playlists',
|
||||||
|
templateUrl: './custom-playlists.component.html',
|
||||||
|
styleUrls: ['./custom-playlists.component.scss']
|
||||||
|
})
|
||||||
|
export class CustomPlaylistsComponent implements OnInit {
|
||||||
|
|
||||||
|
playlists = null;
|
||||||
|
playlists_received = false;
|
||||||
|
downloading_content = {'video': {}, 'audio': {}};
|
||||||
|
|
||||||
|
constructor(public postsService: PostsService, private router: Router, private dialog: MatDialog) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.postsService.service_initialized.subscribe(init => {
|
||||||
|
if (init) {
|
||||||
|
this.getAllPlaylists();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllPlaylists() {
|
||||||
|
this.playlists_received = false;
|
||||||
|
this.postsService.getAllFiles().subscribe(res => {
|
||||||
|
this.playlists = res['playlists'];
|
||||||
|
this.playlists_received = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating a playlist
|
||||||
|
openCreatePlaylistDialog() {
|
||||||
|
const dialogRef = this.dialog.open(CreatePlaylistComponent, {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.getAllPlaylists();
|
||||||
|
this.postsService.openSnackBar('Successfully created playlist!', '');
|
||||||
|
} else if (result === false) {
|
||||||
|
this.postsService.openSnackBar('ERROR: failed to create playlist!', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
goToPlaylist(playlist) {
|
||||||
|
const playlistID = playlist.id;
|
||||||
|
const type = playlist.type;
|
||||||
|
|
||||||
|
if (playlist) {
|
||||||
|
if (this.postsService.config['Extra']['download_only_mode']) {
|
||||||
|
this.downloading_content[type][playlistID] = true;
|
||||||
|
this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('player_navigator', this.router.url);
|
||||||
|
const fileNames = playlist.fileNames;
|
||||||
|
this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID, uid: playlistID}]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// playlist not found
|
||||||
|
console.error(`Playlist with ID ${playlistID} not found!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadPlaylist(fileNames, type, zipName = null, playlistID = null) {
|
||||||
|
this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => {
|
||||||
|
if (playlistID) { this.downloading_content[type][playlistID] = false };
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, zipName + '.zip');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePlaylist(args) {
|
||||||
|
const playlist = args.file;
|
||||||
|
const index = args.index;
|
||||||
|
const playlistID = playlist.id;
|
||||||
|
this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.playlists.splice(index, 1);
|
||||||
|
this.postsService.openSnackBar('Playlist successfully removed.', '');
|
||||||
|
}
|
||||||
|
this.getAllPlaylists();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editPlaylistDialog(args) {
|
||||||
|
const playlist = args.playlist;
|
||||||
|
const index = args.index;
|
||||||
|
const dialogRef = this.dialog.open(ModifyPlaylistComponent, {
|
||||||
|
data: {
|
||||||
|
playlist: playlist,
|
||||||
|
width: '65vw'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
|
// updates playlist in file manager if it changed
|
||||||
|
if (dialogRef.componentInstance.playlist_updated) {
|
||||||
|
this.playlists[index] = dialogRef.componentInstance.original_playlist;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button style="top: 15px;" (click)="clearDownloads(session_downloads.key)" mat-stroked-button color="warn">Clear all downloads</button>
|
<button style="top: 15px;" (click)="clearDownloads(session_downloads.key)" mat-stroked-button color="warn"><ng-container i18n="clear all downloads action button">Clear all downloads</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
<div style="height: 100%">
|
<div style="height: 275px;">
|
||||||
<div *ngIf="logs_loading" style="position: absolute; top: 40%; left: 50%">
|
<div *ngIf="logs_loading" style="z-index: 999; position: absolute; top: 40%; left: 50%">
|
||||||
<mat-spinner [diameter]="32"></mat-spinner>
|
<mat-spinner [diameter]="32"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
<textarea style="height: 275px" matInput readonly [(ngModel)]="logs" placeholder="Logs will appear here" i18n-placeholder="Logs placeholder"></textarea>
|
<!-- Virtual mode (fast, select text buggy) -->
|
||||||
|
<!--<cdk-virtual-scroll-viewport style="height: 274px;" itemSize="50" class="example-viewport">
|
||||||
|
<div *cdkVirtualFor="let log of logs; let i = index" class="example-item">
|
||||||
|
<span [ngStyle]="{'color':log.color}">{{log.text}}</span>
|
||||||
|
</div>
|
||||||
|
</cdk-virtual-scroll-viewport>-->
|
||||||
|
|
||||||
|
<!-- Non-virtual mode (slow, bug-free) -->
|
||||||
|
<div style="height: 274px; overflow-y: auto">
|
||||||
|
<div *ngFor="let log of logs; let i = index" class="example-item">
|
||||||
|
<span [ngStyle]="{'color':log.color}">{{log.text}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button style="margin-top: 12px;" [cdkCopyToClipboard]="logs" (click)="copiedLogsToClipboard()" mat-flat-button color="primary"><ng-container i18n="Copy to clipboard button text">Copy to clipboard</ng-container></button>
|
<button style="position: absolute; right: 0px; top: 12px;" [cdkCopyToClipboard]="logs_text" (click)="copiedLogsToClipboard()" mat-mini-fab color="primary"><mat-icon style="font-size: 22px !important;">content_copy</mat-icon></button>
|
||||||
<div style="float: right">
|
<div style="display: inline-block;">
|
||||||
<ng-container i18n="Label for lines select in logger view">Lines:</ng-container>
|
<ng-container i18n="Label for lines select in logger view">Lines:</ng-container>
|
||||||
<mat-form-field style="width: 75px;">
|
<mat-form-field style="width: 75px;">
|
||||||
<mat-select (selectionChange)="getLogs()" [(ngModel)]="requested_lines">
|
<mat-select (selectionChange)="getLogs()" [(ngModel)]="requested_lines">
|
||||||
@@ -17,5 +30,8 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button style="float: right; margin-top: 12px;" (click)="clearLogs()" mat-stroked-button color="warn"><ng-container i18n="Clear logs button">Clear logs</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.spacer {flex: 1 1 auto;}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Component, OnInit, AfterViewInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { PostsService } from '../../posts.services';
|
import { PostsService } from '../../posts.services';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-logs-viewer',
|
selector: 'app-logs-viewer',
|
||||||
@@ -8,10 +10,11 @@ import { PostsService } from '../../posts.services';
|
|||||||
})
|
})
|
||||||
export class LogsViewerComponent implements OnInit {
|
export class LogsViewerComponent implements OnInit {
|
||||||
|
|
||||||
logs: string = null;
|
logs: any = null;
|
||||||
|
logs_text: string = null;
|
||||||
requested_lines = 50;
|
requested_lines = 50;
|
||||||
logs_loading = false;
|
logs_loading = false;
|
||||||
constructor(private postsService: PostsService) { }
|
constructor(private postsService: PostsService, private dialog: MatDialog) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getLogs();
|
this.getLogs();
|
||||||
@@ -21,8 +24,24 @@ export class LogsViewerComponent implements OnInit {
|
|||||||
if (!this.logs) { this.logs_loading = true; } // only show loading spinner at the first load
|
if (!this.logs) { this.logs_loading = true; } // only show loading spinner at the first load
|
||||||
this.postsService.getLogs(this.requested_lines !== 0 ? this.requested_lines : null).subscribe(res => {
|
this.postsService.getLogs(this.requested_lines !== 0 ? this.requested_lines : null).subscribe(res => {
|
||||||
this.logs_loading = false;
|
this.logs_loading = false;
|
||||||
if (res['logs']) {
|
if (res['logs'] !== null || res['logs'] !== undefined) {
|
||||||
this.logs = res['logs'];
|
this.logs_text = res['logs'];
|
||||||
|
this.logs = [];
|
||||||
|
const logs_arr = res['logs'].split('\n');
|
||||||
|
logs_arr.forEach(log_line => {
|
||||||
|
let color = 'inherit'
|
||||||
|
if (log_line.includes('ERROR')) {
|
||||||
|
color = 'red';
|
||||||
|
} else if (log_line.includes('WARN')) {
|
||||||
|
color = 'yellow';
|
||||||
|
} else if (log_line.includes('VERBOSE')) {
|
||||||
|
color = 'gray';
|
||||||
|
}
|
||||||
|
this.logs.push({
|
||||||
|
text: log_line,
|
||||||
|
color: color
|
||||||
|
})
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.postsService.openSnackBar('Failed to retrieve logs!');
|
this.postsService.openSnackBar('Failed to retrieve logs!');
|
||||||
}
|
}
|
||||||
@@ -37,4 +56,30 @@ export class LogsViewerComponent implements OnInit {
|
|||||||
this.postsService.openSnackBar('Logs copied to clipboard!');
|
this.postsService.openSnackBar('Logs copied to clipboard!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearLogs() {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||||
|
data: {
|
||||||
|
dialogTitle: 'Clear logs',
|
||||||
|
dialogText: 'Would you like to clear your logs? This will delete all your current logs, permanently.',
|
||||||
|
submitText: 'Clear'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe(confirmed => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.postsService.clearAllLogs().subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.logs = [];
|
||||||
|
this.logs_text = '';
|
||||||
|
this.getLogs();
|
||||||
|
this.postsService.openSnackBar('Logs successfully cleared!');
|
||||||
|
} else {
|
||||||
|
this.postsService.openSnackBar('Failed to clear logs!');
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
this.postsService.openSnackBar('Failed to clear logs!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
||||||
<span matLine>
|
<span matLine>
|
||||||
<mat-radio-group [disabled]="permission === 'settings' && postsService.user.uid === user.uid" (change)="changeUserPermissions($event, permission)" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give user permission for ' + permission">
|
<mat-radio-group [disabled]="permission === 'settings' && postsService.user.uid === user.uid" (change)="changeUserPermissions($event, permission)" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give user permission for ' + permission">
|
||||||
<mat-radio-button value="default"><ng-container i18n="Use default">Use default</ng-container></mat-radio-button>
|
<mat-radio-button value="default"><ng-container i18n="Use role default">Use role default</ng-container></mat-radio-button>
|
||||||
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
||||||
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
||||||
</mat-radio-group>
|
</mat-radio-group>
|
||||||
@@ -27,5 +27,5 @@
|
|||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
<mat-dialog-actions>
|
<mat-dialog-actions>
|
||||||
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
<button style="margin-bottom: 5px;" mat-stroked-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="table table-responsive px-5 pb-4 pt-2">
|
<div class="table table-responsive px-5 pb-4 pt-2">
|
||||||
<div class="example-header">
|
<div class="example-header">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search">
|
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search" i18n-placeholder="search field description">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,22 +55,22 @@
|
|||||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Actions users table header"> Actions </ng-container></mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Actions users table header"> Actions </ng-container></mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row">
|
<mat-cell *matCellDef="let row">
|
||||||
<span *ngIf="editObject && editObject.uid === row.uid; else notediting">
|
<span *ngIf="editObject && editObject.uid === row.uid; else notediting">
|
||||||
<button mat-icon-button color="primary" (click)="finishEditing(row.uid)" matTooltip="Finish editing user">
|
<button mat-icon-button color="primary" (click)="finishEditing(row.uid)" matTooltip="Save" i18n-matTooltip="save user edit action button tooltip">
|
||||||
<mat-icon>done</mat-icon>
|
<mat-icon>done</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-icon-button (click)="disableEditMode()" matTooltip="Cancel editing user">
|
<button mat-icon-button (click)="disableEditMode()" matTooltip="Cancel" i18n-matTooltip="cancel user edit action button tooltip">
|
||||||
<mat-icon>cancel</mat-icon>
|
<mat-icon>cancel</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
<ng-template #notediting>
|
<ng-template #notediting>
|
||||||
<button mat-icon-button (click)="enableEditMode(row.uid)" matTooltip="Edit user">
|
<button mat-icon-button (click)="enableEditMode(row.uid)" matTooltip="Edit user" i18n-matTooltip="edit user action button tooltip">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<button (click)="manageUser(row.uid)" mat-icon-button [disabled]="editObject && editObject.uid === row.uid" matTooltip="Manage user">
|
<button (click)="manageUser(row.uid)" mat-icon-button [disabled]="editObject && editObject.uid === row.uid" matTooltip="Manage user" i18n-matTooltip="manage user action button tooltip">
|
||||||
<mat-icon>settings</mat-icon>
|
<mat-icon>settings</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<button mat-icon-button [disabled]="editObject && editObject.uid === row.uid || row.uid === postsService.user.uid" (click)="removeUser(row.uid)" matTooltip="Delete user">
|
<button mat-icon-button [disabled]="editObject && editObject.uid === row.uid || row.uid === postsService.user.uid" (click)="removeUser(row.uid)" matTooltip="Delete user" i18n-matTooltip="delete user action button tooltip">
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<div class="flex-grid">
|
||||||
|
<div class="col">
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<mat-form-field style="width: 132px;">
|
||||||
|
<mat-select [(ngModel)]="this.filterProperty" (selectionChange)="filterOptionChanged($event.value)">
|
||||||
|
<mat-option *ngFor="let filterOption of filterProperties | keyvalue" [value]="filterOption.value">
|
||||||
|
{{filterOption['value']['label']}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<button (click)="toggleModeChange()" mat-icon-button><mat-icon>{{descendingMode ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h4 style="text-align: center">My videos</h4>
|
||||||
|
</div>
|
||||||
|
<div class="col" style="top: 25px;">
|
||||||
|
<mat-form-field [ngClass]="searchIsFocused ? 'search-bar-focused' : 'search-bar-unfocused'" class="search-bar" color="accent">
|
||||||
|
<input (focus)="searchIsFocused = true" (blur)="searchIsFocused = false" class="search-input" type="text" placeholder="Search" i18n-placeholder="Files search placeholder" [(ngModel)]="search_text" (ngModelChange)="onSearchInputChanged($event)" matInput>
|
||||||
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div *ngFor="let file of filtered_files; let i = index" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 mb-2 mt-2 file-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 mb-2 mt-2 file-col' : '' ]">
|
||||||
|
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" (deleteFile)="deleteFile($event)"></app-unified-file-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
.file-col {
|
||||||
|
max-width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
transition: all .5s ease;
|
||||||
|
position: relative;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar-unfocused {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
transition: all .5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar-focused {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-grid {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
width: 33%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RecentVideosComponent } from './recent-videos.component';
|
||||||
|
|
||||||
|
describe('RecentVideosComponent', () => {
|
||||||
|
let component: RecentVideosComponent;
|
||||||
|
let fixture: ComponentFixture<RecentVideosComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ RecentVideosComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(RecentVideosComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
262
src/app/components/recent-videos/recent-videos.component.ts
Normal file
262
src/app/components/recent-videos/recent-videos.component.ts
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-recent-videos',
|
||||||
|
templateUrl: './recent-videos.component.html',
|
||||||
|
styleUrls: ['./recent-videos.component.scss']
|
||||||
|
})
|
||||||
|
export class RecentVideosComponent implements OnInit {
|
||||||
|
|
||||||
|
normal_files_received = false;
|
||||||
|
subscription_files_received = false;
|
||||||
|
files: any[] = null;
|
||||||
|
filtered_files: any[] = null;
|
||||||
|
downloading_content = {'video': {}, 'audio': {}};
|
||||||
|
search_mode = false;
|
||||||
|
search_text = '';
|
||||||
|
searchIsFocused = false;
|
||||||
|
descendingMode = true;
|
||||||
|
filterProperties = {
|
||||||
|
'registered': {
|
||||||
|
'key': 'registered',
|
||||||
|
'label': 'Download Date',
|
||||||
|
'property': 'registered'
|
||||||
|
},
|
||||||
|
'upload_date': {
|
||||||
|
'key': 'upload_date',
|
||||||
|
'label': 'Upload Date',
|
||||||
|
'property': 'upload_date'
|
||||||
|
},
|
||||||
|
'name': {
|
||||||
|
'key': 'name',
|
||||||
|
'label': 'Name',
|
||||||
|
'property': 'title'
|
||||||
|
},
|
||||||
|
'file_size': {
|
||||||
|
'key': 'file_size',
|
||||||
|
'label': 'File Size',
|
||||||
|
'property': 'size'
|
||||||
|
},
|
||||||
|
'duration': {
|
||||||
|
'key': 'duration',
|
||||||
|
'label': 'Duration',
|
||||||
|
'property': 'duration'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
filterProperty = this.filterProperties['upload_date'];
|
||||||
|
|
||||||
|
constructor(public postsService: PostsService, private router: Router) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.postsService.service_initialized.subscribe(init => {
|
||||||
|
if (init) {
|
||||||
|
this.getAllFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// set filter property to cached
|
||||||
|
const cached_filter_property = localStorage.getItem('filter_property');
|
||||||
|
if (cached_filter_property && this.filterProperties[cached_filter_property]) {
|
||||||
|
this.filterProperty = this.filterProperties[cached_filter_property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// search
|
||||||
|
|
||||||
|
onSearchInputChanged(newvalue) {
|
||||||
|
if (newvalue.length > 0) {
|
||||||
|
this.search_mode = true;
|
||||||
|
this.filterFiles(newvalue);
|
||||||
|
} else {
|
||||||
|
this.search_mode = false;
|
||||||
|
this.filtered_files = this.files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterFiles(value: string) {
|
||||||
|
const filterValue = value.toLowerCase();
|
||||||
|
this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
filterByProperty(prop) {
|
||||||
|
if (this.descendingMode) {
|
||||||
|
this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? -1 : 1));
|
||||||
|
} else {
|
||||||
|
this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? 1 : -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterOptionChanged(value) {
|
||||||
|
this.filterByProperty(value['property']);
|
||||||
|
localStorage.setItem('filter_property', value['key']);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleModeChange() {
|
||||||
|
this.descendingMode = !this.descendingMode;
|
||||||
|
this.filterByProperty(this.filterProperty['property']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get files
|
||||||
|
|
||||||
|
getAllFiles() {
|
||||||
|
this.normal_files_received = false;
|
||||||
|
this.postsService.getAllFiles().subscribe(res => {
|
||||||
|
this.files = res['files'];
|
||||||
|
this.files.forEach(file => {
|
||||||
|
file.duration = typeof file.duration !== 'string' ? file.duration : this.durationStringToNumber(file.duration);
|
||||||
|
});
|
||||||
|
this.files.sort(this.sortFiles);
|
||||||
|
if (this.search_mode) {
|
||||||
|
this.filterFiles(this.search_text);
|
||||||
|
} else {
|
||||||
|
this.filtered_files = this.files;
|
||||||
|
}
|
||||||
|
this.filterByProperty(this.filterProperty['property']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// navigation
|
||||||
|
|
||||||
|
goToFile(file) {
|
||||||
|
if (this.postsService.config['Extra']['download_only_mode']) {
|
||||||
|
this.downloadFile(file);
|
||||||
|
} else {
|
||||||
|
this.navigateToFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToFile(file) {
|
||||||
|
localStorage.setItem('player_navigator', this.router.url);
|
||||||
|
if (file.sub_id) {
|
||||||
|
const sub = this.postsService.getSubscriptionByID(file.sub_id)
|
||||||
|
if (sub.streamingOnly) {
|
||||||
|
this.router.navigate(['/player', {name: file.id,
|
||||||
|
url: file.requested_formats ? file.requested_formats[0].url : file.url}]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/player', {fileNames: file.id,
|
||||||
|
type: file.isAudio ? 'audio' : 'video', subscriptionName: sub.name,
|
||||||
|
subPlaylist: sub.isPlaylist}]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/player', {type: file.isAudio ? 'audio' : 'video', uid: file.uid}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goToSubscription(file) {
|
||||||
|
this.router.navigate(['/subscription', {id: file.sub_id}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloading
|
||||||
|
|
||||||
|
downloadFile(file) {
|
||||||
|
if (file.sub_id) {
|
||||||
|
this.downloadSubscriptionFile(file);
|
||||||
|
} else {
|
||||||
|
this.downloadNormalFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadSubscriptionFile(file) {
|
||||||
|
const type = file.isAudio ? 'audio' : 'video';
|
||||||
|
const ext = type === 'audio' ? '.mp3' : '.mp4'
|
||||||
|
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||||
|
console.log(sub.isPlaylist)
|
||||||
|
this.postsService.downloadFileFromServer(file.id, type, null, null, sub.name, sub.isPlaylist,
|
||||||
|
this.postsService.user ? this.postsService.user.uid : null, null).subscribe(res => {
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, file.id + ext);
|
||||||
|
}, err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadNormalFile(file) {
|
||||||
|
const type = file.isAudio ? 'audio' : 'video';
|
||||||
|
const ext = type === 'audio' ? '.mp3' : '.mp4'
|
||||||
|
const name = file.id;
|
||||||
|
this.downloading_content[type][name] = true;
|
||||||
|
this.postsService.downloadFileFromServer(name, type).subscribe(res => {
|
||||||
|
this.downloading_content[type][name] = false;
|
||||||
|
const blob: Blob = res;
|
||||||
|
saveAs(blob, decodeURIComponent(name) + ext);
|
||||||
|
|
||||||
|
if (!this.postsService.config.Extra.file_manager_enabled) {
|
||||||
|
// tell server to delete the file once downloaded
|
||||||
|
this.postsService.deleteFile(name, false).subscribe(delRes => {
|
||||||
|
// reload mp4s
|
||||||
|
this.getAllFiles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleting
|
||||||
|
|
||||||
|
deleteFile(args) {
|
||||||
|
const file = args.file;
|
||||||
|
const index = args.index;
|
||||||
|
const blacklistMode = args.blacklistMode;
|
||||||
|
|
||||||
|
if (file.sub_id) {
|
||||||
|
this.deleteSubscriptionFile(file, index, blacklistMode);
|
||||||
|
} else {
|
||||||
|
this.deleteNormalFile(file, index, blacklistMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteNormalFile(file, index, blacklistMode = false) {
|
||||||
|
this.postsService.deleteFile(file.uid, file.isAudio, blacklistMode).subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.postsService.openSnackBar('Delete success!', 'OK.');
|
||||||
|
this.files.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.postsService.openSnackBar('Delete failed!', 'OK.');
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
this.postsService.openSnackBar('Delete failed!', 'OK.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSubscriptionFile(file, index, blacklistMode = false) {
|
||||||
|
if (blacklistMode) {
|
||||||
|
this.deleteForever(file, index);
|
||||||
|
} else {
|
||||||
|
this.deleteAndRedownload(file, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAndRedownload(file, index) {
|
||||||
|
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||||
|
this.postsService.deleteSubscriptionFile(sub, file.id, false, file.uid).subscribe(res => {
|
||||||
|
this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`);
|
||||||
|
this.files.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteForever(file, index) {
|
||||||
|
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||||
|
this.postsService.deleteSubscriptionFile(sub, file.id, true, file.uid).subscribe(res => {
|
||||||
|
this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`);
|
||||||
|
this.files.splice(index, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// sorting and filtering
|
||||||
|
|
||||||
|
sortFiles(a, b) {
|
||||||
|
// uses the 'registered' flag as the timestamp
|
||||||
|
const result = b.registered - a.registered;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
durationStringToNumber(dur_str) {
|
||||||
|
let num_sum = 0;
|
||||||
|
const dur_str_parts = dur_str.split(':');
|
||||||
|
for (let i = dur_str_parts.length-1; i >= 0; i--) {
|
||||||
|
num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
|
||||||
|
}
|
||||||
|
return num_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<div (mouseover)="elevated=true" (mouseout)="elevated=false" style="position: relative; width: fit-content;">
|
||||||
|
<div class="download-time"><mat-icon class="audio-video-icon">{{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}}</mat-icon> {{file_obj.registered | date:'shortDate'}}</div>
|
||||||
|
<button [matMenuTriggerFor]="action_menu" class="menuButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||||
|
<mat-menu #action_menu="matMenu">
|
||||||
|
<ng-container *ngIf="!is_playlist">
|
||||||
|
<button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
||||||
|
<button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
|
||||||
|
<mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="file_obj.sub_id && use_youtubedl_archive" (click)="emitDeleteFile(true)" mat-menu-item>
|
||||||
|
<mat-icon>delete_forever</mat-icon><ng-container i18n="Delete forever subscription video button">Delete forever</ng-container>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
|
||||||
|
<button *ngIf="!file_obj.sub_id && use_youtubedl_archive" (click)="emitDeleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and blacklist video button">Delete and blacklist</ng-container></button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="is_playlist">
|
||||||
|
<button (click)="emitEditPlaylist()" mat-menu-item><mat-icon>edit</mat-icon><ng-container i18n="Playlist edit button">Edit</ng-container></button>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<button (click)="emitDeleteFile()" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete playlist">Delete</ng-container></button>
|
||||||
|
</ng-container>
|
||||||
|
</mat-menu>
|
||||||
|
<mat-card [matTooltip]="null" (click)="navigateToFile()" matRipple class="file-mat-card" [ngClass]="{'small-mat-card': card_size === 'small', 'file-mat-card': card_size === 'medium', 'mat-elevation-z4': !elevated, 'mat-elevation-z8': elevated}">
|
||||||
|
<div style="padding:5px">
|
||||||
|
<div *ngIf="file_obj.thumbnailURL" class="img-div">
|
||||||
|
<div style="position: relative">
|
||||||
|
<img [ngClass]="{'image-small': card_size === 'small', 'image': card_size === 'medium'}" [src]="file_obj.thumbnailURL" alt="Thumbnail">
|
||||||
|
<div class="duration-time">
|
||||||
|
{{file_length}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span [ngClass]="{'max-two-lines': card_size !== 'small', 'max-one-line': card_size === 'small' }"><strong>{{!is_playlist ? file_obj.title : file_obj.name}}</strong></span>
|
||||||
|
</div>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
.file-mat-card {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
padding: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-mat-card {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
padding: 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuButton {
|
||||||
|
right: 0px;
|
||||||
|
top: -1px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coerce the <span> icon container away from display:inline */
|
||||||
|
.mat-icon-button .mat-button-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 200px;
|
||||||
|
height: 112.5px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-small {
|
||||||
|
width: 150px;
|
||||||
|
height: 84.5px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-full-width-height {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
margin: 0 auto;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-div {
|
||||||
|
max-height: 80px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 32px 0px 0px -5px;
|
||||||
|
width: calc(100% + 5px + 5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-two-lines {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -moz-box;
|
||||||
|
max-height: 2.4em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
bottom: 5px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-one-line {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -moz-box;
|
||||||
|
max-height: 1.2em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
bottom: 5px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-time {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
right: 5px;
|
||||||
|
z-index: 99999;
|
||||||
|
background: rgba(255,255,255,0.6);
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-time {
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 5px;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-video-icon {
|
||||||
|
position: relative;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px){
|
||||||
|
|
||||||
|
// .example-card {
|
||||||
|
// width: 175px !important;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .image {
|
||||||
|
// width: 175px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UnifiedFileCardComponent } from './unified-file-card.component';
|
||||||
|
|
||||||
|
describe('UnifiedFileCardComponent', () => {
|
||||||
|
let component: UnifiedFileCardComponent;
|
||||||
|
let fixture: ComponentFixture<UnifiedFileCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ UnifiedFileCardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UnifiedFileCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-unified-file-card',
|
||||||
|
templateUrl: './unified-file-card.component.html',
|
||||||
|
styleUrls: ['./unified-file-card.component.scss']
|
||||||
|
})
|
||||||
|
export class UnifiedFileCardComponent implements OnInit {
|
||||||
|
|
||||||
|
// required info
|
||||||
|
file_title = '';
|
||||||
|
file_length = '';
|
||||||
|
file_thumbnail = '';
|
||||||
|
type = null;
|
||||||
|
elevated = false;
|
||||||
|
|
||||||
|
@Input() file_obj = null;
|
||||||
|
@Input() card_size = 'medium';
|
||||||
|
@Input() use_youtubedl_archive = false;
|
||||||
|
@Input() is_playlist = false;
|
||||||
|
@Input() index: number;
|
||||||
|
@Output() goToFile = new EventEmitter<any>();
|
||||||
|
@Output() goToSubscription = new EventEmitter<any>();
|
||||||
|
@Output() deleteFile = new EventEmitter<any>();
|
||||||
|
@Output() editPlaylist = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Planned sizes:
|
||||||
|
small: 150x175
|
||||||
|
medium: 200x200
|
||||||
|
big: 250x200
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(private dialog: MatDialog) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.file_length = fancyTimeFormat(this.file_obj.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitDeleteFile(blacklistMode = false) {
|
||||||
|
this.deleteFile.emit({
|
||||||
|
file: this.file_obj,
|
||||||
|
index: this.index,
|
||||||
|
blacklistMode: blacklistMode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToFile() {
|
||||||
|
this.goToFile.emit(this.file_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToSubscription() {
|
||||||
|
this.goToSubscription.emit(this.file_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
openFileInfoDialog() {
|
||||||
|
this.dialog.open(VideoInfoDialogComponent, {
|
||||||
|
data: {
|
||||||
|
file: this.file_obj,
|
||||||
|
},
|
||||||
|
minWidth: '50vw'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
emitEditPlaylist() {
|
||||||
|
this.editPlaylist.emit({
|
||||||
|
playlist: this.file_obj,
|
||||||
|
index: this.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function fancyTimeFormat(time) {
|
||||||
|
if (typeof time === 'string') {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
// Hours, minutes and seconds
|
||||||
|
const hrs = ~~(time / 3600);
|
||||||
|
const mins = ~~((time % 3600) / 60);
|
||||||
|
const secs = ~~time % 60;
|
||||||
|
|
||||||
|
// Output like "1:01" or "4:03:59" or "123:03:59"
|
||||||
|
let ret = '';
|
||||||
|
|
||||||
|
if (hrs > 0) {
|
||||||
|
ret += '' + hrs + ':' + (mins < 10 ? '0' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += '' + mins + ':' + (secs < 10 ? '0' : '');
|
||||||
|
ret += '' + secs;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
export const CURRENT_VERSION = 'v4.0';
|
export const CURRENT_VERSION = 'v4.1';
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
<h4 mat-dialog-title i18n="Create a playlist dialog title">Create a playlist</h4>
|
<h4 mat-dialog-title i18n="Create a playlist dialog title">Create a playlist</h4>
|
||||||
<form>
|
<form>
|
||||||
<div>
|
<div *ngIf="filesToSelectFrom || (audiosToSelectFrom && videosToSelectFrom)">
|
||||||
<mat-form-field color="accent">
|
<div>
|
||||||
<input [(ngModel)]="name" matInput placeholder="Name" i18n-placeholder="Playlist name placeholder" type="text" required aria-required [ngModelOptions]="{standalone: true}">
|
<mat-form-field color="accent">
|
||||||
</mat-form-field>
|
<input [(ngModel)]="name" matInput placeholder="Name" i18n-placeholder="Playlist name placeholder" type="text" required aria-required [ngModelOptions]="{standalone: true}">
|
||||||
</div>
|
</mat-form-field>
|
||||||
<div>
|
</div>
|
||||||
<mat-form-field color="accent">
|
<div *ngIf="!filesToSelectFrom">
|
||||||
<mat-label *ngIf="type === 'audio'"><ng-container i18n="Audio files title">Audio files</ng-container></mat-label>
|
<mat-form-field color="accent">
|
||||||
<mat-label *ngIf="type === 'video'"><ng-container i18n="Videos title">Videos</ng-container></mat-label>
|
<mat-select placeholder="Type" i18n-placeholder="Type select" [(ngModel)]="type" [ngModelOptions]="{standalone: true}">
|
||||||
<mat-select [formControl]="filesSelect" multiple required aria-required>
|
<mat-option value="audio"><ng-container i18n="Audio">Audio</ng-container></mat-option>
|
||||||
<mat-option *ngFor="let file of filesToSelectFrom" [value]="file.id">{{file.id}}</mat-option>
|
<mat-option value="video"><ng-container i18n="Video">Video</ng-container></mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field *ngIf="type && ((filesToSelectFrom && filesToSelectFrom.length > 0) || (type === 'audio' && audiosToSelectFrom && audiosToSelectFrom.length > 0) || (type === 'video' && videosToSelectFrom && videosToSelectFrom.length > 0))" color="accent">
|
||||||
|
<mat-label *ngIf="type === 'audio'"><ng-container i18n="Audio files title">Audio files</ng-container></mat-label>
|
||||||
|
<mat-label *ngIf="type === 'video'"><ng-container i18n="Videos title">Videos</ng-container></mat-label>
|
||||||
|
<mat-select [formControl]="filesSelect" multiple required aria-required>
|
||||||
|
<ng-container *ngIf="filesToSelectFrom"><mat-option *ngFor="let file of filesToSelectFrom" [value]="file.id">{{file.id}}</mat-option></ng-container>
|
||||||
|
<ng-container *ngIf="audiosToSelectFrom && type === 'audio'"><mat-option *ngFor="let file of audiosToSelectFrom" [value]="file.id">{{file.id}}</mat-option></ng-container>
|
||||||
|
<ng-container *ngIf="videosToSelectFrom && type === 'video'"><mat-option *ngFor="let file of videosToSelectFrom" [value]="file.id">{{file.id}}</mat-option></ng-container>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<!-- No videos available -->
|
||||||
|
<div style="margin-bottom: 15px;" *ngIf="type && ((filesToSelectFrom && filesToSelectFrom.length === 0) || (type === 'audio' && audiosToSelectFrom && audiosToSelectFrom.length === 0) || (type === 'video' && videosToSelectFrom && videosToSelectFrom.length === 0))">
|
||||||
|
No files available.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ export class CreatePlaylistComponent implements OnInit {
|
|||||||
filesToSelectFrom = null;
|
filesToSelectFrom = null;
|
||||||
type = null;
|
type = null;
|
||||||
filesSelect = new FormControl();
|
filesSelect = new FormControl();
|
||||||
|
audiosToSelectFrom = null;
|
||||||
|
videosToSelectFrom = null;
|
||||||
name = '';
|
name = '';
|
||||||
|
|
||||||
create_in_progress = false;
|
create_in_progress = false;
|
||||||
@@ -28,12 +30,30 @@ export class CreatePlaylistComponent implements OnInit {
|
|||||||
this.filesToSelectFrom = this.data.filesToSelectFrom;
|
this.filesToSelectFrom = this.data.filesToSelectFrom;
|
||||||
this.type = this.data.type;
|
this.type = this.data.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.filesToSelectFrom) {
|
||||||
|
this.getMp3s();
|
||||||
|
this.getMp4s();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMp3s() {
|
||||||
|
this.postsService.getMp3s().subscribe(result => {
|
||||||
|
this.audiosToSelectFrom = result['mp3s'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMp4s() {
|
||||||
|
this.postsService.getMp4s().subscribe(result => {
|
||||||
|
this.videosToSelectFrom = result['mp4s'];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlaylist() {
|
createPlaylist() {
|
||||||
const thumbnailURL = this.getThumbnailURL();
|
const thumbnailURL = this.getThumbnailURL();
|
||||||
|
const duration = this.calculateDuration();
|
||||||
this.create_in_progress = true;
|
this.create_in_progress = true;
|
||||||
this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL).subscribe(res => {
|
this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL, duration).subscribe(res => {
|
||||||
this.create_in_progress = false;
|
this.create_in_progress = false;
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
this.dialogRef.close(true);
|
this.dialogRef.close(true);
|
||||||
@@ -44,8 +64,12 @@ export class CreatePlaylistComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailURL() {
|
getThumbnailURL() {
|
||||||
for (let i = 0; i < this.filesToSelectFrom.length; i++) {
|
let properFilesToSelectFrom = this.filesToSelectFrom;
|
||||||
const file = this.filesToSelectFrom[i];
|
if (!this.filesToSelectFrom) {
|
||||||
|
properFilesToSelectFrom = this.type === 'audio' ? this.audiosToSelectFrom : this.videosToSelectFrom;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < properFilesToSelectFrom.length; i++) {
|
||||||
|
const file = properFilesToSelectFrom[i];
|
||||||
if (file.id === this.filesSelect.value[0]) {
|
if (file.id === this.filesSelect.value[0]) {
|
||||||
// different services store the thumbnail in different places
|
// different services store the thumbnail in different places
|
||||||
if (file.thumbnailURL) { return file.thumbnailURL };
|
if (file.thumbnailURL) { return file.thumbnailURL };
|
||||||
@@ -55,4 +79,35 @@ export class CreatePlaylistComponent implements OnInit {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDuration(file_id) {
|
||||||
|
let properFilesToSelectFrom = this.filesToSelectFrom;
|
||||||
|
if (!this.filesToSelectFrom) {
|
||||||
|
properFilesToSelectFrom = this.type === 'audio' ? this.audiosToSelectFrom : this.videosToSelectFrom;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < properFilesToSelectFrom.length; i++) {
|
||||||
|
const file = properFilesToSelectFrom[i];
|
||||||
|
if (file.id === file_id) {
|
||||||
|
return file.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateDuration() {
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < this.filesSelect.value.length; i++) {
|
||||||
|
const duration_val = this.getDuration(this.filesSelect.value[i]);
|
||||||
|
sum += typeof duration_val === 'string' ? this.durationStringToNumber(duration_val) : duration_val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
durationStringToNumber(dur_str) {
|
||||||
|
let num_sum = 0;
|
||||||
|
const dur_str_parts = dur_str.split(':');
|
||||||
|
for (let i = dur_str_parts.length-1; i >= 0; i--) {
|
||||||
|
num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
|
||||||
|
}
|
||||||
|
return num_sum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,43 @@
|
|||||||
<h5 style="margin-top: 10px;">Installation details:</h5>
|
<h5 style="margin-top: 10px;">Installation details:</h5>
|
||||||
<p>
|
<p>
|
||||||
<ng-container i18n="Version label">Installed version:</ng-container> {{current_version_tag}} - <span style="display: inline-block" *ngIf="checking_for_updates"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner> <ng-container i18n="Checking for updates text">Checking for updates...</ng-container></span>
|
<ng-container i18n="Version label">Installed version:</ng-container> {{current_version_tag}} - <span style="display: inline-block" *ngIf="checking_for_updates"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner> <ng-container i18n="Checking for updates text">Checking for updates...</ng-container></span>
|
||||||
<mat-icon *ngIf="!checking_for_updates" class="version-checked-icon">done</mat-icon> <a *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] !== current_version_tag" [href]="latestUpdateLink" target="_blank"><ng-container i18n="View latest update">Update available</ng-container> - {{latestGithubRelease['tag_name']}}</a>. <ng-container i18n="Update through settings menu hint">You can update from the settings menu.</ng-container>
|
<mat-icon *ngIf="!checking_for_updates" class="version-checked-icon">done</mat-icon> <ng-container *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] !== current_version_tag"><a [href]="latestUpdateLink" target="_blank"><ng-container i18n="View latest update">Update available</ng-container> - {{latestGithubRelease['tag_name']}}</a>. <ng-container i18n="Update through settings menu hint">You can update from the settings menu.</ng-container></ng-container>
|
||||||
<span *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] === current_version_tag">You are up to date.</span>
|
<span *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] === current_version_tag">You are up to date.</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<ng-container i18n="About bug prefix">Found a bug or have a suggestion?</ng-container> <a [href]="issuesLink" target="_blank"><ng-container i18n="About bug click here">Click here</ng-container></a> <ng-container i18n="About bug suffix">to create an issue!</ng-container>
|
<ng-container i18n="About bug prefix">Found a bug or have a suggestion?</ng-container> <a [href]="issuesLink" target="_blank"><ng-container i18n="About bug click here">Click here</ng-container></a> <ng-container i18n="About bug suffix">to create an issue!</ng-container>
|
||||||
</p>
|
</p>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
<h5>Personal settings:</h5>
|
||||||
|
<mat-form-field placeholder="Sidepanel mode">
|
||||||
|
<mat-select [(ngModel)]="sidepanel_mode" (selectionChange)="sidePanelModeChanged($event.value)">
|
||||||
|
<mat-option value="over">
|
||||||
|
Over
|
||||||
|
</mat-option>
|
||||||
|
<mat-option value="side">
|
||||||
|
Side
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<br/>
|
||||||
|
<mat-form-field placeholder="Card size">
|
||||||
|
<mat-select [(ngModel)]="card_size" (selectionChange)="cardSizeOptionChanged($event.value)">
|
||||||
|
<mat-option value="large" [disabled]="true">
|
||||||
|
Large
|
||||||
|
</mat-option>
|
||||||
|
<mat-option value="medium">
|
||||||
|
Medium
|
||||||
|
</mat-option>
|
||||||
|
<mat-option value="small">
|
||||||
|
Small
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
<mat-dialog-actions>
|
<mat-dialog-actions>
|
||||||
<button style="margin-bottom: 5px;" mat-stroked-button mat-dialog-close>Close</button>
|
<button style="margin-bottom: 5px;" mat-stroked-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
@@ -16,11 +16,13 @@ export class AboutDialogComponent implements OnInit {
|
|||||||
checking_for_updates = true;
|
checking_for_updates = true;
|
||||||
|
|
||||||
current_version_tag = CURRENT_VERSION;
|
current_version_tag = CURRENT_VERSION;
|
||||||
|
sidepanel_mode = this.postsService.sidepanel_mode;
|
||||||
|
card_size = this.postsService.card_size;
|
||||||
|
|
||||||
constructor(private postsService: PostsService) { }
|
constructor(private postsService: PostsService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getLatestGithubRelease()
|
this.getLatestGithubRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
getLatestGithubRelease() {
|
getLatestGithubRelease() {
|
||||||
@@ -30,4 +32,14 @@ export class AboutDialogComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sidePanelModeChanged(new_mode) {
|
||||||
|
localStorage.setItem('sidepanel_mode', new_mode);
|
||||||
|
this.postsService.sidepanel_mode = new_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardSizeOptionChanged(new_size) {
|
||||||
|
localStorage.setItem('card_size', new_size);
|
||||||
|
this.postsService.card_size = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<h4 *ngIf="pinSetChecked" mat-dialog-title>{{dialog_title}}</h4>
|
|
||||||
|
|
||||||
<mat-dialog-content>
|
|
||||||
<div style="position: relative">
|
|
||||||
<div *ngIf="pinSetChecked">
|
|
||||||
<mat-form-field color="accent">
|
|
||||||
<input type="password" (keyup.enter)="doAction()" matInput [(ngModel)]="input" [placeholder]="input_placeholder">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="spinner-div" *ngIf="!pinSetChecked">
|
|
||||||
<mat-spinner [diameter]="25"></mat-spinner>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-dialog-content>
|
|
||||||
|
|
||||||
<mat-dialog-actions>
|
|
||||||
<button [disabled]="input.length === 0" color="accent" style="margin-bottom: 12px;" (click)="doAction()" mat-raised-button>{{button_label}}</button>
|
|
||||||
</mat-dialog-actions>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
.spinner-div {
|
|
||||||
position: absolute;
|
|
||||||
margin: 0 auto;
|
|
||||||
top: 30%;
|
|
||||||
left: 42%;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { CheckOrSetPinDialogComponent } from './check-or-set-pin-dialog.component';
|
|
||||||
|
|
||||||
describe('CheckOrSetPinDialogComponent', () => {
|
|
||||||
let component: CheckOrSetPinDialogComponent;
|
|
||||||
let fixture: ComponentFixture<CheckOrSetPinDialogComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ CheckOrSetPinDialogComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(CheckOrSetPinDialogComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { Component, OnInit, Inject } from '@angular/core';
|
|
||||||
import { PostsService } from 'app/posts.services';
|
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-check-or-set-pin-dialog',
|
|
||||||
templateUrl: './check-or-set-pin-dialog.component.html',
|
|
||||||
styleUrls: ['./check-or-set-pin-dialog.component.scss']
|
|
||||||
})
|
|
||||||
export class CheckOrSetPinDialogComponent implements OnInit {
|
|
||||||
|
|
||||||
pinSetChecked = false;
|
|
||||||
pinSet = true;
|
|
||||||
resetMode = false;
|
|
||||||
dialog_title = '';
|
|
||||||
input_placeholder = null;
|
|
||||||
input = '';
|
|
||||||
button_label = '';
|
|
||||||
|
|
||||||
constructor(private postsService: PostsService, @Inject(MAT_DIALOG_DATA) public data: any,
|
|
||||||
public dialogRef: MatDialogRef<CheckOrSetPinDialogComponent>, private snackBar: MatSnackBar) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
if (this.data) {
|
|
||||||
this.resetMode = this.data.resetMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.resetMode) {
|
|
||||||
this.pinSetChecked = true;
|
|
||||||
this.notSetLogic();
|
|
||||||
} else {
|
|
||||||
this.isPinSet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isPinSet() {
|
|
||||||
this.postsService.isPinSet().subscribe(res => {
|
|
||||||
this.pinSetChecked = true;
|
|
||||||
if (res['is_set']) {
|
|
||||||
this.isSetLogic();
|
|
||||||
} else {
|
|
||||||
this.notSetLogic();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isSetLogic() {
|
|
||||||
this.pinSet = true;
|
|
||||||
this.dialog_title = 'Pin Required';
|
|
||||||
this.input_placeholder = 'Pin';
|
|
||||||
this.button_label = 'Submit'
|
|
||||||
}
|
|
||||||
|
|
||||||
notSetLogic() {
|
|
||||||
this.pinSet = false;
|
|
||||||
this.dialog_title = 'Set your pin';
|
|
||||||
this.input_placeholder = 'New pin';
|
|
||||||
this.button_label = 'Set Pin'
|
|
||||||
}
|
|
||||||
|
|
||||||
doAction() {
|
|
||||||
// pin set must have been checked, and input must not be empty
|
|
||||||
if (!this.pinSetChecked || this.input.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pinSet) {
|
|
||||||
this.postsService.checkPin(this.input).subscribe(res => {
|
|
||||||
if (res['success']) {
|
|
||||||
this.dialogRef.close(true);
|
|
||||||
} else {
|
|
||||||
this.dialogRef.close(false);
|
|
||||||
this.openSnackBar('Pin is incorrect!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.postsService.setPin(this.input).subscribe(res => {
|
|
||||||
if (res['success']) {
|
|
||||||
this.dialogRef.close(true);
|
|
||||||
this.openSnackBar('Pin successfully set!');
|
|
||||||
} else {
|
|
||||||
this.dialogRef.close(false);
|
|
||||||
this.openSnackBar('Failed to set pin!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public openSnackBar(message: string, action: string = '') {
|
|
||||||
this.snackBar.open(message, action, {
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
15
src/app/dialogs/confirm-dialog/confirm-dialog.component.html
Normal file
15
src/app/dialogs/confirm-dialog/confirm-dialog.component.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<h4 mat-dialog-title>{{dialogTitle}}</h4>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
{{dialogText}}
|
||||||
|
</div>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
|
||||||
|
<button color="primary" mat-flat-button type="submit" (click)="confirmClicked()">{{submitText}}</button>
|
||||||
|
<div class="mat-spinner" *ngIf="submitClicked">
|
||||||
|
<mat-spinner [diameter]="25"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
<span class="spacer"></span>
|
||||||
|
<button style="float: right;" mat-stroked-button mat-dialog-close>Cancel</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.spacer {flex: 1 1 auto;}
|
||||||
|
|
||||||
|
.mat-spinner {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ConfirmDialogComponent } from './confirm-dialog.component';
|
||||||
|
|
||||||
|
describe('ConfirmDialogComponent', () => {
|
||||||
|
let component: ConfirmDialogComponent;
|
||||||
|
let fixture: ComponentFixture<ConfirmDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ ConfirmDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConfirmDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
44
src/app/dialogs/confirm-dialog/confirm-dialog.component.ts
Normal file
44
src/app/dialogs/confirm-dialog/confirm-dialog.component.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Component, OnInit, Inject, EventEmitter } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-confirm-dialog',
|
||||||
|
templateUrl: './confirm-dialog.component.html',
|
||||||
|
styleUrls: ['./confirm-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class ConfirmDialogComponent implements OnInit {
|
||||||
|
|
||||||
|
dialogTitle = 'Confirm';
|
||||||
|
dialogText = 'Would you like to confirm?';
|
||||||
|
submitText = 'Yes'
|
||||||
|
submitClicked = false;
|
||||||
|
|
||||||
|
doneEmitter: EventEmitter<any> = null;
|
||||||
|
onlyEmitOnDone = false;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: any, public dialogRef: MatDialogRef<ConfirmDialogComponent>) {
|
||||||
|
if (this.data.dialogTitle) { this.dialogTitle = this.data.dialogTitle };
|
||||||
|
if (this.data.dialogText) { this.dialogText = this.data.dialogText };
|
||||||
|
if (this.data.submitText) { this.submitText = this.data.submitText };
|
||||||
|
|
||||||
|
// checks if emitter exists, if so don't autoclose as it should be handled by caller
|
||||||
|
if (this.data.doneEmitter) {
|
||||||
|
this.doneEmitter = this.data.doneEmitter;
|
||||||
|
this.onlyEmitOnDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmClicked() {
|
||||||
|
if (this.onlyEmitOnDone) {
|
||||||
|
this.doneEmitter.emit(true);
|
||||||
|
this.submitClicked = true;
|
||||||
|
} else {
|
||||||
|
this.dialogRef.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<h4 mat-dialog-title i18n="Edit subscription dialog title">Editing {{sub.name}}</h4>
|
||||||
|
|
||||||
|
<mat-dialog-content>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mt-3">
|
||||||
|
<mat-checkbox (change)="downloadAllToggled()" [(ngModel)]="download_all"><ng-container i18n="Download all uploads subscription setting">Download all uploads</ng-container></mat-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="col-12" *ngIf="!download_all && editor_initialized">
|
||||||
|
<ng-container i18n="Download time range prefix">Download videos uploaded in the last</ng-container>
|
||||||
|
<mat-form-field color="accent" style="width: 50px; text-align: center; margin-left: 10px;">
|
||||||
|
<input type="number" matInput [(ngModel)]="timerange_amount" (ngModelChange)="timerangeChanged($event, false)">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="unit-select">
|
||||||
|
<mat-select color="accent" [(ngModel)]="timerange_unit" (ngModelChange)="timerangeChanged($event, true)">
|
||||||
|
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
|
||||||
|
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div>
|
||||||
|
<mat-checkbox [disabled]="true" [(ngModel)]="audioOnlyMode"><ng-container i18n="Streaming-only mode">Audio-only mode</ng-container></mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div>
|
||||||
|
<mat-checkbox [disabled]="new_sub.type === 'audio'" [(ngModel)]="new_sub.streamingOnly"><ng-container i18n="Streaming-only mode">Streaming-only mode</ng-container></mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<input [(ngModel)]="new_sub.custom_args" matInput placeholder="Custom args" i18n-placeholder="Subscription custom args placeholder">
|
||||||
|
<button class="args-edit-button" (click)="openArgsModifierDialog()" mat-icon-button><mat-icon>edit</mat-icon></button>
|
||||||
|
<mat-hint>
|
||||||
|
<ng-container i18n="Custom args hint">These are added after the standard args.</ng-container>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<input [(ngModel)]="new_sub.custom_output" matInput placeholder="Custom file output" i18n-placeholder="Subscription custom file output placeholder">
|
||||||
|
<mat-hint>
|
||||||
|
<a target="_blank" href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#output-template">
|
||||||
|
<ng-container i18n="Custom output template documentation link">Documentation</ng-container></a>.
|
||||||
|
<ng-container i18n="Custom Output input hint">Path is relative to the config download path. Don't include extension.</ng-container>
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close><ng-container i18n="Subscribe cancel button">Cancel</ng-container></button>
|
||||||
|
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
|
||||||
|
<button mat-button [disabled]="updating || !subChanged()" type="submit" (click)="saveClicked()"><ng-container i18n="Save button">Save</ng-container></button>
|
||||||
|
<div class="mat-spinner" *ngIf="updating">
|
||||||
|
<mat-spinner [diameter]="25"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
</mat-dialog-actions>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.args-edit-button {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-select {
|
||||||
|
width: 75px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EditSubscriptionDialogComponent } from './edit-subscription-dialog.component';
|
||||||
|
|
||||||
|
describe('EditSubscriptionDialogComponent', () => {
|
||||||
|
let component: EditSubscriptionDialogComponent;
|
||||||
|
let fixture: ComponentFixture<EditSubscriptionDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ EditSubscriptionDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(EditSubscriptionDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { Component, OnInit, Inject, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
|
||||||
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { ArgModifierDialogComponent } from '../arg-modifier-dialog/arg-modifier-dialog.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-subscription-dialog',
|
||||||
|
templateUrl: './edit-subscription-dialog.component.html',
|
||||||
|
styleUrls: ['./edit-subscription-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class EditSubscriptionDialogComponent implements OnInit {
|
||||||
|
|
||||||
|
updating = false;
|
||||||
|
|
||||||
|
sub = null;
|
||||||
|
new_sub = null;
|
||||||
|
|
||||||
|
editor_initialized = false;
|
||||||
|
|
||||||
|
timerange_amount: number;
|
||||||
|
timerange_unit = 'days';
|
||||||
|
audioOnlyMode = null;
|
||||||
|
download_all = null;
|
||||||
|
|
||||||
|
|
||||||
|
time_units = [
|
||||||
|
'day',
|
||||||
|
'week',
|
||||||
|
'month',
|
||||||
|
'year'
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(@Inject(MAT_DIALOG_DATA) public data: any, private dialog: MatDialog, private postsService: PostsService) {
|
||||||
|
this.sub = this.data.sub;
|
||||||
|
this.new_sub = JSON.parse(JSON.stringify(this.sub));
|
||||||
|
|
||||||
|
this.audioOnlyMode = this.sub.type === 'audio';
|
||||||
|
this.download_all = !this.sub.timerange;
|
||||||
|
|
||||||
|
if (this.sub.timerange) {
|
||||||
|
const timerange_str = this.sub.timerange.split('-')[1];
|
||||||
|
console.log(timerange_str);
|
||||||
|
const number = timerange_str.replace(/\D/g,'');
|
||||||
|
let units = timerange_str.replace(/[0-9]/g, '');
|
||||||
|
|
||||||
|
console.log(units);
|
||||||
|
|
||||||
|
// // remove plural on units
|
||||||
|
// if (units[units.length-1] === 's') {
|
||||||
|
// units = units.substring(0, units.length-1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
this.timerange_amount = parseInt(number);
|
||||||
|
this.timerange_unit = units;
|
||||||
|
this.editor_initialized = true;
|
||||||
|
} else {
|
||||||
|
this.editor_initialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAllToggled() {
|
||||||
|
if (this.download_all) {
|
||||||
|
this.new_sub.timerange = null;
|
||||||
|
} else {
|
||||||
|
console.log('checking');
|
||||||
|
this.timerangeChanged(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSubscription() {
|
||||||
|
this.postsService.updateSubscription(this.sub).subscribe(res => {
|
||||||
|
this.sub = this.new_sub;
|
||||||
|
this.new_sub = JSON.parse(JSON.stringify(this.sub));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubscription() {
|
||||||
|
this.postsService.getSubscription(this.sub.id).subscribe(res => {
|
||||||
|
this.sub = res['subscription'];
|
||||||
|
this.new_sub = JSON.parse(JSON.stringify(this.sub));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
timerangeChanged(value, select_changed) {
|
||||||
|
console.log(this.timerange_amount);
|
||||||
|
console.log(this.timerange_unit);
|
||||||
|
|
||||||
|
if (this.timerange_amount && this.timerange_unit && !this.download_all) {
|
||||||
|
this.new_sub.timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit;
|
||||||
|
console.log(this.new_sub.timerange);
|
||||||
|
} else {
|
||||||
|
this.new_sub.timerange = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveClicked() {
|
||||||
|
this.saveSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify custom args
|
||||||
|
openArgsModifierDialog() {
|
||||||
|
if (!this.new_sub.custom_args) {
|
||||||
|
this.new_sub.custom_args = '';
|
||||||
|
}
|
||||||
|
const dialogRef = this.dialog.open(ArgModifierDialogComponent, {
|
||||||
|
data: {
|
||||||
|
initial_args: this.new_sub.custom_args
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe(new_args => {
|
||||||
|
if (new_args !== null && new_args !== undefined) {
|
||||||
|
this.new_sub.custom_args = new_args;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subChanged() {
|
||||||
|
return JSON.stringify(this.new_sub) !== JSON.stringify(this.sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,14 +24,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12" *ngIf="!download_all">
|
<div class="col-12" *ngIf="!download_all">
|
||||||
<ng-container i18n="Download time range prefix">Download videos uploaded in the last</ng-container>
|
<ng-container i18n="Download time range prefix">Download videos uploaded in the last</ng-container>
|
||||||
<mat-form-field color="accent" style="width: 50px; text-align: center">
|
<mat-form-field color="accent" style="width: 50px; text-align: center; margin-left: 10px;">
|
||||||
<input type="number" matInput [(ngModel)]="timerange_amount">
|
<input type="number" matInput [(ngModel)]="timerange_amount">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-select color="accent" class="unit-select" [(ngModel)]="timerange_unit">
|
<mat-form-field class="unit-select">
|
||||||
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
|
<mat-select color="accent" [(ngModel)]="timerange_unit">
|
||||||
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
|
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
|
||||||
</mat-option>
|
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
|
||||||
</mat-select>
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
<mat-grid-tile [colspan]="13">
|
<mat-grid-tile [colspan]="13">
|
||||||
<mat-progress-bar [value]="(download.complete || download.error) ? 100 : download.percent_complete" [mode]="(!download.complete && download.percent_complete === 0 && !download.error) ? 'indeterminate' : 'determinate'"></mat-progress-bar>
|
<mat-progress-bar [value]="(download.complete || download.error) ? 100 : download.percent_complete" [mode]="(!download.complete && download.percent_complete === 0 && !download.error) ? 'indeterminate' : 'determinate'"></mat-progress-bar>
|
||||||
<mat-icon *ngIf="download.complete" style="margin-left: 25px; cursor: default" matTooltip="The download is complete" matTooltip-i18n>done</mat-icon>
|
<mat-icon *ngIf="download.complete" style="margin-left: 25px; cursor: default" matTooltip="The download was successful" i18n-matTooltip="download successful tooltip">done</mat-icon>
|
||||||
<mat-icon *ngIf="download.error" style="margin-left: 25px; cursor: default" matTooltip="An error has occurred" matTooltip-i18n>error</mat-icon>
|
<mat-icon *ngIf="download.error" style="margin-left: 25px; cursor: default" matTooltip="An error has occurred" i18n-matTooltip="download error tooltip">error</mat-icon>
|
||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
<mat-grid-tile [colspan]="4">
|
<mat-grid-tile [colspan]="4">
|
||||||
<button style="margin-bottom: 2px;" (click)="cancelTheDownload()" mat-icon-button color="warn"><mat-icon fontSet="material-icons-outlined">cancel</mat-icon></button>
|
<button style="margin-bottom: 2px;" (click)="cancelTheDownload()" mat-icon-button color="warn"><mat-icon fontSet="material-icons-outlined">cancel</mat-icon></button>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<div class="big demo-basic">
|
<div class="big demo-basic">
|
||||||
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;" [ngClass]="(allowAdvancedDownload) ? 'no-border-radius-bottom' : null">
|
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;" [ngClass]="(allowAdvancedDownload) ? 'no-border-radius-bottom' : null">
|
||||||
<mat-card-title>
|
<mat-card-content style="padding: 0px 8px 0px 8px;">
|
||||||
<ng-container i18n="Youtube downloader home page label">Youtube Downloader</ng-container>
|
|
||||||
</mat-card-title>
|
|
||||||
<mat-card-content>
|
|
||||||
<div style="position: relative;">
|
<div style="position: relative;">
|
||||||
<form class="example-form">
|
<form class="example-form">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@@ -183,7 +180,13 @@
|
|||||||
<ng-template #nofile>
|
<ng-template #nofile>
|
||||||
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div style="margin: 20px" *ngIf="fileManagerEnabled && (!postsService.isLoggedIn || postsService.permissions.includes('filemanager'))">
|
|
||||||
|
<app-recent-videos></app-recent-videos>
|
||||||
|
<br/>
|
||||||
|
<h4 style="text-align: center">Custom playlists</h4>
|
||||||
|
<app-custom-playlists></app-custom-playlists>
|
||||||
|
|
||||||
|
<!--<div style="margin: 20px" *ngIf="fileManagerEnabled && (!postsService.isLoggedIn || postsService.permissions.includes('filemanager'))">
|
||||||
<mat-accordion>
|
<mat-accordion>
|
||||||
<mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
|
<mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
|
||||||
<mat-expansion-panel-header>
|
<mat-expansion-panel-header>
|
||||||
@@ -260,7 +263,7 @@
|
|||||||
</mat-grid-tile>
|
</mat-grid-tile>
|
||||||
</mat-grid-list>
|
</mat-grid-list>
|
||||||
|
|
||||||
<!-- Add video playlist button -->
|
<!-- Add video playlist button --<
|
||||||
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog('video')" mat-fab><mat-icon>add</mat-icon></button></div>
|
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog('video')" mat-fab><mat-icon>add</mat-icon></button></div>
|
||||||
<div *ngIf="playlists.video.length === 0">
|
<div *ngIf="playlists.video.length === 0">
|
||||||
<ng-container i18n="No video playlists available text">
|
<ng-container i18n="No video playlists available text">
|
||||||
@@ -270,12 +273,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
</div>
|
</div>-->
|
||||||
|
|
||||||
<ng-template #nomp3s>
|
|
||||||
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #nomp4s>
|
|
||||||
|
|
||||||
</ng-template>
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
.video-styles {
|
.video-styles {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 75vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .mat-button-toggle-label-content {
|
::ng-deep .mat-button-toggle-label-content {
|
||||||
@@ -65,6 +66,13 @@
|
|||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
padding-left: 0.01px;
|
padding-left: 0.01px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-col {
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-icon {
|
.save-icon {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<div *ngIf="playlist.length > 0 && show_player">
|
<div *ngIf="playlist.length > 0 && show_player">
|
||||||
<div [ngClass]="(type === 'audio') ? null : 'container-video'" class="container">
|
<div [ngClass]="(type === 'audio') ? null : 'container-video'">
|
||||||
<div style="max-width: 100%; margin-left: 0px; height: 70vh" class="row">
|
<div style="max-width: 100%; margin-left: 0px; height: 70vh">
|
||||||
<div [ngClass]="(type === 'audio') ? 'my-2 px-1' : 'video-col'" class="col">
|
<div style="height: fit-content" [ngClass]="(type === 'audio') ? 'audio-col' : 'video-col'">
|
||||||
<vg-player (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(type === 'audio') ? 'transparent' : 'black'">
|
<vg-player style="height: fit-content; max-height: 75vh" (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(type === 'audio') ? 'transparent' : 'black'">
|
||||||
<video [ngClass]="(type === 'audio') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls>
|
<video [ngClass]="(type === 'audio') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls>
|
||||||
</video>
|
</video>
|
||||||
</vg-player>
|
</vg-player>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 my-2">
|
<div style="height: fit-content; width: 100%; margin-top: 10px;">
|
||||||
<mat-button-toggle-group cdkDropList [cdkDropListSortingDisabled]="!id" (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
<mat-button-toggle-group cdkDropList [cdkDropListSortingDisabled]="!id" (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, HostListener, EventEmitter } from '@angular/core';
|
import { Component, OnInit, HostListener, EventEmitter, OnDestroy } from '@angular/core';
|
||||||
import { VgAPI } from 'ngx-videogular';
|
import { VgAPI } from 'ngx-videogular';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
@@ -20,7 +20,7 @@ export interface IMedia {
|
|||||||
templateUrl: './player.component.html',
|
templateUrl: './player.component.html',
|
||||||
styleUrls: ['./player.component.css']
|
styleUrls: ['./player.component.css']
|
||||||
})
|
})
|
||||||
export class PlayerComponent implements OnInit {
|
export class PlayerComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
playlist: Array<IMedia> = [];
|
playlist: Array<IMedia> = [];
|
||||||
original_playlist: string = null;
|
original_playlist: string = null;
|
||||||
@@ -62,6 +62,7 @@ export class PlayerComponent implements OnInit {
|
|||||||
|
|
||||||
downloading = false;
|
downloading = false;
|
||||||
|
|
||||||
|
save_volume_timer = null;
|
||||||
original_volume = null;
|
original_volume = null;
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
@@ -94,6 +95,11 @@ export class PlayerComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
// prevents volume save feature from running in the background
|
||||||
|
clearInterval(this.save_volume_timer);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(public postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
constructor(public postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
||||||
public snackBar: MatSnackBar) {
|
public snackBar: MatSnackBar) {
|
||||||
|
|
||||||
@@ -241,7 +247,8 @@ export class PlayerComponent implements OnInit {
|
|||||||
if (localStorage.getItem('player_volume')) {
|
if (localStorage.getItem('player_volume')) {
|
||||||
this.api.volume = parseFloat(localStorage.getItem('player_volume'));
|
this.api.volume = parseFloat(localStorage.getItem('player_volume'));
|
||||||
}
|
}
|
||||||
setInterval(() => this.saveVolume(this.api), 2000)
|
|
||||||
|
this.save_volume_timer = setInterval(() => this.saveVolume(this.api), 2000)
|
||||||
|
|
||||||
this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this));
|
this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this));
|
||||||
this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this));
|
this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this));
|
||||||
@@ -323,7 +330,7 @@ export class PlayerComponent implements OnInit {
|
|||||||
this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist,
|
this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist,
|
||||||
this.is_shared ? this.db_file['uid'] : null, this.uuid).subscribe(res => {
|
this.is_shared ? this.db_file['uid'] : null, this.uuid).subscribe(res => {
|
||||||
this.downloading = false;
|
this.downloading = false;
|
||||||
const blob: Blob = res;
|
const blob = new Blob([res], {type: this.type === 'audio' ? 'audio/mp3' : 'video/mp4'})
|
||||||
saveAs(blob, filename + ext);
|
saveAs(blob, filename + ext);
|
||||||
}, err => {
|
}, err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export class PostsService implements CanActivate {
|
|||||||
handShakeComplete = false;
|
handShakeComplete = false;
|
||||||
THEMES_CONFIG = THEMES_CONFIG;
|
THEMES_CONFIG = THEMES_CONFIG;
|
||||||
theme;
|
theme;
|
||||||
|
card_size = 'medium';
|
||||||
|
sidepanel_mode = 'over';
|
||||||
settings_changed = new BehaviorSubject<boolean>(false);
|
settings_changed = new BehaviorSubject<boolean>(false);
|
||||||
auth_token = '4241b401-7236-493e-92b5-b72696b9d853';
|
auth_token = '4241b401-7236-493e-92b5-b72696b9d853';
|
||||||
session_id = null;
|
session_id = null;
|
||||||
@@ -47,6 +49,7 @@ export class PostsService implements CanActivate {
|
|||||||
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
config = null;
|
config = null;
|
||||||
|
subscriptions = null;
|
||||||
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document,
|
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document,
|
||||||
public snackBar: MatSnackBar) {
|
public snackBar: MatSnackBar) {
|
||||||
console.log('PostsService Initialized...');
|
console.log('PostsService Initialized...');
|
||||||
@@ -81,6 +84,7 @@ export class PostsService implements CanActivate {
|
|||||||
if (result) {
|
if (result) {
|
||||||
this.config = result['YoutubeDLMaterial'];
|
this.config = result['YoutubeDLMaterial'];
|
||||||
if (this.config['Advanced']['multi_user_mode']) {
|
if (this.config['Advanced']['multi_user_mode']) {
|
||||||
|
this.checkAdminCreationStatus();
|
||||||
// login stuff
|
// login stuff
|
||||||
if (localStorage.getItem('jwt_token') && localStorage.getItem('jwt_token') !== 'null') {
|
if (localStorage.getItem('jwt_token') && localStorage.getItem('jwt_token') !== 'null') {
|
||||||
this.token = localStorage.getItem('jwt_token');
|
this.token = localStorage.getItem('jwt_token');
|
||||||
@@ -100,6 +104,14 @@ export class PostsService implements CanActivate {
|
|||||||
this.reload_config.subscribe(yes_reload => {
|
this.reload_config.subscribe(yes_reload => {
|
||||||
if (yes_reload) { this.reloadConfig(); }
|
if (yes_reload) { this.reloadConfig(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (localStorage.getItem('sidepanel_mode')) {
|
||||||
|
this.sidepanel_mode = localStorage.getItem('sidepanel_mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStorage.getItem('card_size')) {
|
||||||
|
this.card_size = localStorage.getItem('card_size');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
canActivate(route, state): Promise<boolean> {
|
canActivate(route, state): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@@ -113,6 +125,15 @@ export class PostsService implements CanActivate {
|
|||||||
this.theme = this.THEMES_CONFIG[theme];
|
this.theme = this.THEMES_CONFIG[theme];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSubscriptionByID(sub_id) {
|
||||||
|
for (let i = 0; i < this.subscriptions.length; i++) {
|
||||||
|
if (this.subscriptions[i]['id'] === sub_id) {
|
||||||
|
return this.subscriptions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
startHandshake(url: string) {
|
startHandshake(url: string) {
|
||||||
return this.http.get(url + 'geturl');
|
return this.http.get(url + 'geturl');
|
||||||
}
|
}
|
||||||
@@ -163,12 +184,8 @@ export class PostsService implements CanActivate {
|
|||||||
ui_uid: ui_uid}, this.httpOptions);
|
ui_uid: ui_uid}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileStatusMp3(name: string) {
|
killAllDownloads() {
|
||||||
return this.http.post(this.path + 'fileStatusMp3', {name: name}, this.httpOptions);
|
return this.http.post(this.path + 'killAllDownloads', {}, this.httpOptions);
|
||||||
}
|
|
||||||
|
|
||||||
getFileStatusMp4(name: string) {
|
|
||||||
return this.http.post(this.path + 'fileStatusMp4', {name: name}, this.httpOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNavItems() {
|
loadNavItems() {
|
||||||
@@ -207,6 +224,10 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'getFile', {uid: uid, type: type, uuid: uuid}, this.httpOptions);
|
return this.http.post(this.path + 'getFile', {uid: uid, type: type, uuid: uuid}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllFiles() {
|
||||||
|
return this.http.post(this.path + 'getAllFiles', {}, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null,
|
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null,
|
||||||
uid = null, uuid = null) {
|
uid = null, uuid = null) {
|
||||||
return this.http.post(this.path + 'downloadFile', {fileNames: fileName,
|
return this.http.post(this.path + 'downloadFile', {fileNames: fileName,
|
||||||
@@ -238,16 +259,8 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'logs', {lines: lines}, this.httpOptions);
|
return this.http.post(this.path + 'logs', {lines: lines}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
isPinSet() {
|
clearAllLogs() {
|
||||||
return this.http.post(this.path + 'isPinSet', {}, this.httpOptions);
|
return this.http.post(this.path + 'clearAllLogs', {}, this.httpOptions);
|
||||||
}
|
|
||||||
|
|
||||||
setPin(unhashed_pin) {
|
|
||||||
return this.http.post(this.path + 'setPin', {pin: unhashed_pin}, this.httpOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPin(unhashed_pin) {
|
|
||||||
return this.http.post(this.path + 'checkPin', {input_pin: unhashed_pin}, this.httpOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generateNewAPIKey() {
|
generateNewAPIKey() {
|
||||||
@@ -262,11 +275,12 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'disableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions);
|
return this.http.post(this.path + 'disableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlaylist(playlistName, fileNames, type, thumbnailURL) {
|
createPlaylist(playlistName, fileNames, type, thumbnailURL, duration = null) {
|
||||||
return this.http.post(this.path + 'createPlaylist', {playlistName: playlistName,
|
return this.http.post(this.path + 'createPlaylist', {playlistName: playlistName,
|
||||||
fileNames: fileNames,
|
fileNames: fileNames,
|
||||||
type: type,
|
type: type,
|
||||||
thumbnailURL: thumbnailURL}, this.httpOptions);
|
thumbnailURL: thumbnailURL,
|
||||||
|
duration: duration}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaylist(playlistID, type, uuid = null) {
|
getPlaylist(playlistID, type, uuid = null) {
|
||||||
@@ -293,6 +307,10 @@ export class PostsService implements CanActivate {
|
|||||||
audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}, this.httpOptions);
|
audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSubscription(subscription) {
|
||||||
|
return this.http.post(this.path + 'updateSubscription', {subscription: subscription}, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
unsubscribe(sub, deleteMode = false) {
|
unsubscribe(sub, deleteMode = false) {
|
||||||
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
|
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
|
||||||
}
|
}
|
||||||
@@ -415,7 +433,6 @@ export class PostsService implements CanActivate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendToLogin() {
|
sendToLogin() {
|
||||||
this.checkAdminCreationStatus();
|
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
this.setInitialized();
|
this.setInitialized();
|
||||||
}
|
}
|
||||||
@@ -445,8 +462,8 @@ export class PostsService implements CanActivate {
|
|||||||
password: password}, this.httpOptions);
|
password: password}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAdminCreationStatus(skip_check = false) {
|
checkAdminCreationStatus(force_show = false) {
|
||||||
if (!skip_check && !this.config['Advanced']['multi_user_mode']) {
|
if (!force_show && !this.config['Advanced']['multi_user_mode']) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.adminExists().subscribe(res => {
|
this.adminExists().subscribe(res => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<mat-hint><ng-container i18n="URL setting input hint">URL this app will be accessed from, without the port.</ng-container></mat-hint>
|
<mat-hint><ng-container i18n="URL setting input hint">URL this app will be accessed from, without the port.</ng-container></mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 mb-4">
|
<div class="col-12 mb-4 mt-3">
|
||||||
<mat-form-field class="text-field" color="accent">
|
<mat-form-field class="text-field" color="accent">
|
||||||
<input [(ngModel)]="new_config['Host']['port']" matInput placeholder="Port" i18n-placeholder="Port input placeholder" required>
|
<input [(ngModel)]="new_config['Host']['port']" matInput placeholder="Port" i18n-placeholder="Port input placeholder" required>
|
||||||
<mat-hint><ng-container i18n="Port setting input hint">The desired port. Default is 17442.</ng-container></mat-hint>
|
<mat-hint><ng-container i18n="Port setting input hint">The desired port. Default is 17442.</ng-container></mat-hint>
|
||||||
@@ -79,11 +79,6 @@
|
|||||||
<mat-hint><ng-container i18n="Check interval setting input hint">Unit is seconds, only include numbers.</ng-container></mat-hint>
|
<mat-hint><ng-container i18n="Check interval setting input hint">Unit is seconds, only include numbers.</ng-container></mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 mt-4">
|
|
||||||
<mat-checkbox color="accent" [disabled]="!new_config['Subscriptions']['allow_subscriptions']" [(ngModel)]="new_config['Subscriptions']['subscriptions_use_youtubedl_archive']"><ng-container i18n="Use youtube-dl archive setting">Use youtube-dl archive</ng-container></mat-checkbox>
|
|
||||||
<p><a target="_blank" href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#how-do-i-download-only-new-videos-from-a-playlist"><ng-container i18n="youtube-dl archive explanation prefix link">With youtube-dl's archive</ng-container></a> <ng-container i18n="youtube-dl archive explanation middle">feature, downloaded videos from your subscriptions get recorded in a text file in the subscriptions archive sub-directory.</ng-container></p>
|
|
||||||
<p><ng-container i18n="youtube-dl archive explanation suffix">This enables the ability to permanently delete videos from your subscriptions without unsubscribing, and allows you to record which videos you downloaded in case of data loss.</ng-container></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
@@ -140,7 +135,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 mt-5">
|
<div class="col-12 mt-4">
|
||||||
<mat-form-field class="text-field" style="margin-right: 12px;" color="accent">
|
<mat-form-field class="text-field" style="margin-right: 12px;" color="accent">
|
||||||
<textarea matInput [(ngModel)]="new_config['Downloader']['custom_args']" placeholder="Custom args" i18n-placeholder="Custom args input placeholder"></textarea>
|
<textarea matInput [(ngModel)]="new_config['Downloader']['custom_args']" placeholder="Custom args" i18n-placeholder="Custom args input placeholder"></textarea>
|
||||||
<mat-hint><ng-container i18n="Custom args setting input hint">Global custom args for downloads on the home page. Args are delimited using two commas like so: ,,</ng-container></mat-hint>
|
<mat-hint><ng-container i18n="Custom args setting input hint">Global custom args for downloads on the home page. Args are delimited using two commas like so: ,,</ng-container></mat-hint>
|
||||||
@@ -148,14 +143,17 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 mt-4">
|
<div class="col-12 mt-5">
|
||||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['use_youtubedl_archive']"><ng-container i18n="Use youtubedl archive setting">Use youtube-dl archive</ng-container></mat-checkbox>
|
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['use_youtubedl_archive']"><ng-container i18n="Use youtubedl archive setting">Use youtube-dl archive</ng-container></mat-checkbox>
|
||||||
<p>Note: This setting only applies to downloads on the Home page. If you would like to use youtube-dl archive functionality in subscriptions, head down to the Subscriptions section.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 mt-3">
|
<div class="col-12 mt-2">
|
||||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['safe_download_override']"><ng-container i18n="Safe download override setting">Safe download override</ng-container></mat-checkbox>
|
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['safe_download_override']"><ng-container i18n="Safe download override setting">Safe download override</ng-container></mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-2">
|
||||||
|
<button (click)="killAllDownloads()" mat-stroked-button color="warn"><ng-container i18n="Kill all downloads button">Kill all downloads</ng-container></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -186,10 +184,6 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Extra']['allow_multi_download_mode']"><ng-container i18n="Allow multi-download mode setting">Allow multi-download mode</ng-container></mat-checkbox>
|
<mat-checkbox color="accent" [(ngModel)]="new_config['Extra']['allow_multi_download_mode']"><ng-container i18n="Allow multi-download mode setting">Allow multi-download mode</ng-container></mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
|
||||||
<mat-checkbox [disabled]="new_config['Advanced']['multi_user_mode']" color="accent" [(ngModel)]="new_config['Extra']['settings_pin_required']"><ng-container i18n="Require pin for settings setting">Require pin for settings</ng-container></mat-checkbox>
|
|
||||||
<button style="margin-left: 15px; margin-bottom: 10px;" mat-stroked-button (click)="setNewPin()" [disabled]="!new_config['Extra']['settings_pin_required']"><ng-container i18n="Set new pin button">Set New Pin</ng-container></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
@@ -206,7 +200,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="api-key-div">
|
<div class="api-key-div">
|
||||||
<button matTooltip-i18n matTooltip="This will delete your old API key!" mat-stroked-button (click)="generateAPIKey()"><ng-container i18n="Generate key button">Generate</ng-container></button>
|
<button matTooltip="This will delete your old API key!" i18n-matTooltip="delete api key tooltip" mat-stroked-button (click)="generateAPIKey()"><ng-container i18n="Generate key button">Generate</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -285,6 +279,18 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 mt-2 mb-1">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label><ng-container i18n="Login expiration select label">Login expiration</ng-container></mat-label>
|
||||||
|
<mat-select color="accent" [(ngModel)]="new_config['Advanced']['jwt_expiration']">
|
||||||
|
<mat-option [value]="3600">1 Hour</mat-option>
|
||||||
|
<mat-option [value]="86400">1 Day</mat-option>
|
||||||
|
<mat-option [value]="604800">1 Week</mat-option>
|
||||||
|
<mat-option [value]="2592000">1 Month</mat-option>
|
||||||
|
<mat-option [value]="31536000">1 Year</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['allow_advanced_download']"><ng-container i18n="Allow advanced downloading setting">Allow advanced download</ng-container></mat-checkbox>
|
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['allow_advanced_download']"><ng-container i18n="Allow advanced downloading setting">Allow advanced download</ng-container></mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, EventEmitter } from '@angular/core';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { CheckOrSetPinDialogComponent } from 'app/dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
|
|
||||||
import { isoLangs } from './locales_list';
|
import { isoLangs } from './locales_list';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import {DomSanitizer} from '@angular/platform-browser';
|
import {DomSanitizer} from '@angular/platform-browser';
|
||||||
@@ -9,6 +8,7 @@ import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-
|
|||||||
import { CURRENT_VERSION } from 'app/consts';
|
import { CURRENT_VERSION } from 'app/consts';
|
||||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||||
import { CookiesUploaderDialogComponent } from 'app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
|
import { CookiesUploaderDialogComponent } from 'app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
|
||||||
|
import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
@@ -77,14 +77,6 @@ export class SettingsComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setNewPin() {
|
|
||||||
const dialogRef = this.dialog.open(CheckOrSetPinDialogComponent, {
|
|
||||||
data: {
|
|
||||||
resetMode: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generateAPIKey() {
|
generateAPIKey() {
|
||||||
this.postsService.generateNewAPIKey().subscribe(res => {
|
this.postsService.generateNewAPIKey().subscribe(res => {
|
||||||
if (res['new_api_key']) {
|
if (res['new_api_key']) {
|
||||||
@@ -163,6 +155,34 @@ export class SettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
killAllDownloads() {
|
||||||
|
const done = new EventEmitter<any>();
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||||
|
data: {
|
||||||
|
dialogTitle: 'Kill downloads',
|
||||||
|
dialogText: 'Are you sure you want to kill all downloads? Any subscription and non-subscription downloads will end immediately, though this operation may take a minute or so to complete.',
|
||||||
|
submitText: 'Kill all downloads',
|
||||||
|
doneEmitter: done
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done.subscribe(confirmed => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.postsService.killAllDownloads().subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
dialogRef.close();
|
||||||
|
this.postsService.openSnackBar('Successfully killed all downloads!');
|
||||||
|
} else {
|
||||||
|
dialogRef.close();
|
||||||
|
this.postsService.openSnackBar('Failed to kill all downloads! Check logs for details.');
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
dialogRef.close();
|
||||||
|
this.postsService.openSnackBar('Failed to kill all downloads! Check logs for details.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// snackbar helper
|
// snackbar helper
|
||||||
public openSnackBar(message: string, action: string = '') {
|
public openSnackBar(message: string, action: string = '') {
|
||||||
this.snackBar.open(message, action, {
|
this.snackBar.open(message, action, {
|
||||||
|
|||||||
@@ -42,5 +42,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="edit-button" color="primary" (click)="editSubscription()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">edit</mat-icon></button>
|
||||||
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,6 +58,12 @@
|
|||||||
bottom: 25px;
|
bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-button {
|
||||||
|
left: 25px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
.save-icon {
|
.save-icon {
|
||||||
bottom: 1px;
|
bottom: 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subscription',
|
selector: 'app-subscription',
|
||||||
@@ -43,9 +45,21 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
filterProperty = this.filterProperties['upload_date'];
|
filterProperty = this.filterProperties['upload_date'];
|
||||||
downloading = false;
|
downloading = false;
|
||||||
|
|
||||||
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { }
|
initialized = false;
|
||||||
|
|
||||||
|
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router, private dialog: MatDialog) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.route.paramMap.subscribe((params: ParamMap) => {
|
||||||
|
this.id = params.get('id');
|
||||||
|
this.postsService.service_initialized.subscribe(init => {
|
||||||
|
if (init) {
|
||||||
|
this.initialized = true;
|
||||||
|
this.getConfig();
|
||||||
|
this.getSubscription();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
if (this.route.snapshot.paramMap.get('id')) {
|
if (this.route.snapshot.paramMap.get('id')) {
|
||||||
this.id = this.route.snapshot.paramMap.get('id');
|
this.id = this.route.snapshot.paramMap.get('id');
|
||||||
|
|
||||||
@@ -82,7 +96,7 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getConfig() {
|
getConfig() {
|
||||||
this.use_youtubedl_archive = this.postsService.config['Subscriptions']['subscriptions_use_youtubedl_archive'];
|
this.use_youtubedl_archive = this.postsService.config['Downloader']['use_youtubedl_archive'];
|
||||||
}
|
}
|
||||||
|
|
||||||
goToFile(emit_obj) {
|
goToFile(emit_obj) {
|
||||||
@@ -121,7 +135,6 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filterOptionChanged(value) {
|
filterOptionChanged(value) {
|
||||||
// this.filterProperty = value;
|
|
||||||
this.filterByProperty(value['property']);
|
this.filterByProperty(value['property']);
|
||||||
localStorage.setItem('filter_property', value['key']);
|
localStorage.setItem('filter_property', value['key']);
|
||||||
}
|
}
|
||||||
@@ -148,4 +161,12 @@ export class SubscriptionComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editSubscription() {
|
||||||
|
this.dialog.open(EditSubscriptionDialogComponent, {
|
||||||
|
data: {
|
||||||
|
sub: this.subscription
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"Downloader": {
|
"Downloader": {
|
||||||
"path-audio": "audio/",
|
"path-audio": "audio/",
|
||||||
"path-video": "video/",
|
"path-video": "video/",
|
||||||
"use_youtubedl_archive": false,
|
"use_youtubedl_archive": true,
|
||||||
"custom_args": "",
|
"custom_args": "",
|
||||||
"safe_download_override": false
|
"safe_download_override": false
|
||||||
},
|
},
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
"custom_downloading_agent": "",
|
"custom_downloading_agent": "",
|
||||||
"multi_user_mode": true,
|
"multi_user_mode": true,
|
||||||
"allow_advanced_download": true,
|
"allow_advanced_download": true,
|
||||||
|
"jwt_expiration": 86400,
|
||||||
"logger_level": "debug",
|
"logger_level": "debug",
|
||||||
"use_cookies": true
|
"use_cookies": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
||||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
||||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
||||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Benutzer-UID",
|
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Benutzer-UID:",
|
||||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Neues Passwort",
|
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Neues Passwort",
|
||||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Neues Passwort festlegen",
|
"6498fa1b8f563988f769654a75411bb8060134b9": "Neues Passwort festlegen",
|
||||||
"40da072004086c9ec00d125165da91eaade7f541": "Standard verwenden",
|
"40da072004086c9ec00d125165da91eaade7f541": "Standard verwenden",
|
||||||
|
|||||||
@@ -1772,7 +1772,7 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="29c97c8e76763bb15b6d515648fa5bd1eb0f7510" datatype="html">
|
<trans-unit id="29c97c8e76763bb15b6d515648fa5bd1eb0f7510" datatype="html">
|
||||||
<source>User UID:</source>
|
<source>User UID:</source>
|
||||||
<target xml:lang="de-DE">Benutzer-UID</target>
|
<target xml:lang="de-DE">Benutzer-UID:</target>
|
||||||
<note from="description" priority="1">User UID</note><context-group purpose="location">
|
<note from="description" priority="1">User UID</note><context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/components/manage-user/manage-user.component.html</context>
|
<context context-type="sourcefile">app/components/manage-user/manage-user.component.html</context>
|
||||||
<context context-type="linenumber">4</context>
|
<context context-type="linenumber">4</context>
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
<context context-type="sourcefile">app/create-playlist/create-playlist.component.html</context>
|
<context context-type="sourcefile">app/create-playlist/create-playlist.component.html</context>
|
||||||
<context context-type="linenumber">5</context>
|
<context context-type="linenumber">5</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/modify-playlist/modify-playlist.component.html</context>
|
||||||
|
<context context-type="linenumber">7</context>
|
||||||
|
</context-group>
|
||||||
<note priority="1" from="description">Playlist name placeholder</note>
|
<note priority="1" from="description">Playlist name placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f47e2d56dd8a145b2e9599da9730c049d52962a2" datatype="html">
|
<trans-unit id="f47e2d56dd8a145b2e9599da9730c049d52962a2" datatype="html">
|
||||||
@@ -102,7 +106,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">70</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Arg modifier cancel button</note>
|
<note priority="1" from="description">Arg modifier cancel button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -122,21 +126,13 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Youtube downloader home page label</note>
|
<note priority="1" from="description">Youtube downloader home page label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6d2ec8898344c8955542b0542c942038ef28bb80" datatype="html">
|
|
||||||
<source>Please enter a valid URL!</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
|
||||||
<context context-type="linenumber">16</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Enter valid URL error</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="a38ae1082fec79ba1f379978337385a539a28e73" datatype="html">
|
<trans-unit id="a38ae1082fec79ba1f379978337385a539a28e73" datatype="html">
|
||||||
<source>
|
<source>
|
||||||
Quality
|
Quality
|
||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">24</context>
|
<context context-type="linenumber">21</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Quality select label</note>
|
<note priority="1" from="description">Quality select label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -144,7 +140,7 @@
|
|||||||
<source>Use URL</source>
|
<source>Use URL</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">52</context>
|
<context context-type="linenumber">49</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">YT search Use URL button for searched video</note>
|
<note priority="1" from="description">YT search Use URL button for searched video</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -154,7 +150,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">55</context>
|
<context context-type="linenumber">52</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">YT search View button for searched video</note>
|
<note priority="1" from="description">YT search View button for searched video</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -164,7 +160,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">65</context>
|
<context context-type="linenumber">62</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Only Audio checkbox</note>
|
<note priority="1" from="description">Only Audio checkbox</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -174,7 +170,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">70</context>
|
<context context-type="linenumber">67</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Multi-download Mode checkbox</note>
|
<note priority="1" from="description">Multi-download Mode checkbox</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -184,7 +180,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">79</context>
|
<context context-type="linenumber">76</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Main download button</note>
|
<note priority="1" from="description">Main download button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -194,7 +190,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">84</context>
|
<context context-type="linenumber">81</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Cancel download button</note>
|
<note priority="1" from="description">Cancel download button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -204,7 +200,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">96</context>
|
<context context-type="linenumber">93</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Advanced download mode panel</note>
|
<note priority="1" from="description">Advanced download mode panel</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -214,7 +210,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">102</context>
|
<context context-type="linenumber">99</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Simulated command label</note>
|
<note priority="1" from="description">Simulated command label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -224,7 +220,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">110</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Use custom args checkbox</note>
|
<note priority="1" from="description">Use custom args checkbox</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -232,12 +228,16 @@
|
|||||||
<source>Custom args</source>
|
<source>Custom args</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">116</context>
|
<context context-type="linenumber">113</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">145</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">48</context>
|
||||||
|
</context-group>
|
||||||
<note priority="1" from="description">Custom args placeholder</note>
|
<note priority="1" from="description">Custom args placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a6911c2157f1b775284bbe9654ce5eb30cf45d7f" datatype="html">
|
<trans-unit id="a6911c2157f1b775284bbe9654ce5eb30cf45d7f" datatype="html">
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">118</context>
|
<context context-type="linenumber">115</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Custom Args input hint</note>
|
<note priority="1" from="description">Custom Args input hint</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -256,7 +256,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">126</context>
|
<context context-type="linenumber">123</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Use custom output checkbox</note>
|
<note priority="1" from="description">Use custom output checkbox</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -264,7 +264,7 @@
|
|||||||
<source>Custom output</source>
|
<source>Custom output</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">131</context>
|
<context context-type="linenumber">128</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Custom output placeholder</note>
|
<note priority="1" from="description">Custom output placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -272,7 +272,11 @@
|
|||||||
<source>Documentation</source>
|
<source>Documentation</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">133</context>
|
<context context-type="linenumber">130</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">60</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Youtube-dl output template documentation link</note>
|
<note priority="1" from="description">Youtube-dl output template documentation link</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -280,7 +284,11 @@
|
|||||||
<source>Path is relative to the config download path. Don't include extension.</source>
|
<source>Path is relative to the config download path. Don't include extension.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">134</context>
|
<context context-type="linenumber">131</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">61</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Custom Output input hint</note>
|
<note priority="1" from="description">Custom Output input hint</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -290,7 +298,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">140</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Use authentication checkbox</note>
|
<note priority="1" from="description">Use authentication checkbox</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -298,7 +306,7 @@
|
|||||||
<source>Username</source>
|
<source>Username</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">142</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">YT Username placeholder</note>
|
<note priority="1" from="description">YT Username placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -306,7 +314,7 @@
|
|||||||
<source>Password</source>
|
<source>Password</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">150</context>
|
<context context-type="linenumber">147</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html</context>
|
||||||
@@ -324,7 +332,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">194</context>
|
<context context-type="linenumber">191</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Audio files title</note>
|
<note priority="1" from="description">Audio files title</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -334,7 +342,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">199</context>
|
<context context-type="linenumber">196</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Audio files description</note>
|
<note priority="1" from="description">Audio files description</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -342,11 +350,11 @@
|
|||||||
<source>Playlists</source>
|
<source>Playlists</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">214</context>
|
<context context-type="linenumber">211</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">256</context>
|
<context context-type="linenumber">253</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/subscriptions/subscriptions.component.html</context>
|
<context context-type="sourcefile">app/subscriptions/subscriptions.component.html</context>
|
||||||
@@ -360,7 +368,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">225</context>
|
<context context-type="linenumber">222</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">No video playlists available text</note>
|
<note priority="1" from="description">No video playlists available text</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -370,7 +378,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">235</context>
|
<context context-type="linenumber">232</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Video files title</note>
|
<note priority="1" from="description">Video files title</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -380,7 +388,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">240</context>
|
<context context-type="linenumber">237</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Video files description</note>
|
<note priority="1" from="description">Video files description</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -390,7 +398,7 @@
|
|||||||
</source>
|
</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/main/main.component.html</context>
|
<context context-type="sourcefile">app/main/main.component.html</context>
|
||||||
<context context-type="linenumber">269</context>
|
<context context-type="linenumber">266</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">No video playlists available text</note>
|
<note priority="1" from="description">No video playlists available text</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -456,6 +464,10 @@
|
|||||||
<context context-type="sourcefile">app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
|
||||||
<context context-type="linenumber">31</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">40</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||||
<context context-type="linenumber">27</context>
|
<context context-type="linenumber">27</context>
|
||||||
@@ -486,6 +498,14 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Close subscription info button</note>
|
<note priority="1" from="description">Close subscription info button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1" datatype="html">
|
||||||
|
<source>Modify playlist</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/modify-playlist/modify-playlist.component.html</context>
|
||||||
|
<context context-type="linenumber">1</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Modify playlist dialog title</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1" datatype="html">
|
<trans-unit id="ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1" datatype="html">
|
||||||
<source>ID:</source>
|
<source>ID:</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@@ -510,11 +530,31 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Playlist video count</note>
|
<note priority="1" from="description">Playlist video count</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="28f86ffd419b869711aa13f5e5ff54be6d70731c" datatype="html">
|
||||||
|
<source>Edit</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
||||||
|
<context context-type="linenumber">19</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Playlist edit button</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" datatype="html">
|
||||||
|
<source>Delete</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
||||||
|
<context context-type="linenumber">20</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
||||||
|
<context context-type="linenumber">25</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Delete playlist</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5" datatype="html">
|
<trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5" datatype="html">
|
||||||
<source>Info</source>
|
<source>Info</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">24</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/subscription/subscription-file-card/subscription-file-card.component.html</context>
|
<context context-type="sourcefile">app/subscription/subscription-file-card/subscription-file-card.component.html</context>
|
||||||
@@ -522,22 +562,38 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Video info button</note>
|
<note priority="1" from="description">Video info button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" datatype="html">
|
|
||||||
<source>Delete</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
|
||||||
<context context-type="linenumber">21</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Delete video button</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="34504b488c24c27e68089be549f0eeae6ebaf30b" datatype="html">
|
<trans-unit id="34504b488c24c27e68089be549f0eeae6ebaf30b" datatype="html">
|
||||||
<source>Delete and blacklist</source>
|
<source>Delete and blacklist</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
<context context-type="sourcefile">app/file-card/file-card.component.html</context>
|
||||||
<context context-type="linenumber">22</context>
|
<context context-type="linenumber">26</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Delete and blacklist video button</note>
|
<note priority="1" from="description">Delete and blacklist video button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="ebadf946ae90f13ecd0c70f09edbc0f983af8a0f" datatype="html">
|
||||||
|
<source>Upload new cookies</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">1</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Cookies uploader dialog title</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="98a8a42e5efffe17ab786636ed0139b4c7032d0e" datatype="html">
|
||||||
|
<source>Drag and Drop</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">11</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Drag and Drop</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="85e0725c870b28458fd3bbba905397d890f00a69" datatype="html">
|
||||||
|
<source>NOTE: Uploading new cookies will overrride your previous cookies. Also note that cookies are instance-wide, not per-user.</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">20</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Cookies upload warning</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="121cc5391cd2a5115bc2b3160379ee5b36cd7716" datatype="html">
|
<trans-unit id="121cc5391cd2a5115bc2b3160379ee5b36cd7716" datatype="html">
|
||||||
<source>Settings</source>
|
<source>Settings</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@@ -802,6 +858,14 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Custom args setting input hint</note>
|
<note priority="1" from="description">Custom args setting input hint</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="d01715b75228878a773ae6d059acc639d4898a03" datatype="html">
|
||||||
|
<source>Safe download override</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
|
<context context-type="linenumber">157</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Safe download override setting</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="0ba25ad86a240576c4f20a2fada4722ebba77b1e" datatype="html">
|
<trans-unit id="0ba25ad86a240576c4f20a2fada4722ebba77b1e" datatype="html">
|
||||||
<source>Downloader</source>
|
<source>Downloader</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
@@ -814,7 +878,7 @@
|
|||||||
<source>Top title</source>
|
<source>Top title</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">166</context>
|
<context context-type="linenumber">170</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Top title input placeholder</note>
|
<note priority="1" from="description">Top title input placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -822,7 +886,7 @@
|
|||||||
<source>File manager enabled</source>
|
<source>File manager enabled</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">171</context>
|
<context context-type="linenumber">175</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">File manager enabled setting</note>
|
<note priority="1" from="description">File manager enabled setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -830,7 +894,7 @@
|
|||||||
<source>Downloads manager enabled</source>
|
<source>Downloads manager enabled</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">174</context>
|
<context context-type="linenumber">178</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Downloads manager enabled setting</note>
|
<note priority="1" from="description">Downloads manager enabled setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -838,7 +902,7 @@
|
|||||||
<source>Allow quality select</source>
|
<source>Allow quality select</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">177</context>
|
<context context-type="linenumber">181</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Allow quality seelct setting</note>
|
<note priority="1" from="description">Allow quality seelct setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -846,7 +910,7 @@
|
|||||||
<source>Download only mode</source>
|
<source>Download only mode</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">180</context>
|
<context context-type="linenumber">184</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Download only mode setting</note>
|
<note priority="1" from="description">Download only mode setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -854,7 +918,7 @@
|
|||||||
<source>Allow multi-download mode</source>
|
<source>Allow multi-download mode</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">183</context>
|
<context context-type="linenumber">187</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Allow multi-download mode setting</note>
|
<note priority="1" from="description">Allow multi-download mode setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -862,7 +926,7 @@
|
|||||||
<source>Require pin for settings</source>
|
<source>Require pin for settings</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">186</context>
|
<context context-type="linenumber">190</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Require pin for settings setting</note>
|
<note priority="1" from="description">Require pin for settings setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -870,7 +934,7 @@
|
|||||||
<source>Set New Pin</source>
|
<source>Set New Pin</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">187</context>
|
<context context-type="linenumber">191</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Set new pin button</note>
|
<note priority="1" from="description">Set new pin button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -878,7 +942,7 @@
|
|||||||
<source>Enable Public API</source>
|
<source>Enable Public API</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">195</context>
|
<context context-type="linenumber">199</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Enable Public API key setting</note>
|
<note priority="1" from="description">Enable Public API key setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -886,7 +950,7 @@
|
|||||||
<source>Public API Key</source>
|
<source>Public API Key</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">200</context>
|
<context context-type="linenumber">204</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Public API Key setting placeholder</note>
|
<note priority="1" from="description">Public API Key setting placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -894,7 +958,7 @@
|
|||||||
<source>View documentation</source>
|
<source>View documentation</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">205</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">View API docs setting hint</note>
|
<note priority="1" from="description">View API docs setting hint</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -902,7 +966,7 @@
|
|||||||
<source>Generate</source>
|
<source>Generate</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">205</context>
|
<context context-type="linenumber">209</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Generate key button</note>
|
<note priority="1" from="description">Generate key button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -910,7 +974,7 @@
|
|||||||
<source>Use YouTube API</source>
|
<source>Use YouTube API</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">214</context>
|
<context context-type="linenumber">218</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Use YouTube API setting</note>
|
<note priority="1" from="description">Use YouTube API setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -918,7 +982,7 @@
|
|||||||
<source>Youtube API Key</source>
|
<source>Youtube API Key</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">218</context>
|
<context context-type="linenumber">222</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Youtube API Key setting placeholder</note>
|
<note priority="1" from="description">Youtube API Key setting placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -926,7 +990,7 @@
|
|||||||
<source>Generating a key is easy!</source>
|
<source>Generating a key is easy!</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">219</context>
|
<context context-type="linenumber">223</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Youtube API Key setting hint</note>
|
<note priority="1" from="description">Youtube API Key setting hint</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -934,11 +998,11 @@
|
|||||||
<source>Click here</source>
|
<source>Click here</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">229</context>
|
<context context-type="linenumber">233</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">235</context>
|
<context context-type="linenumber">239</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/about-dialog/about-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/about-dialog/about-dialog.component.html</context>
|
||||||
@@ -950,7 +1014,7 @@
|
|||||||
<source>to download the official YoutubeDL-Material Chrome extension manually.</source>
|
<source>to download the official YoutubeDL-Material Chrome extension manually.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">229</context>
|
<context context-type="linenumber">233</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Chrome click here suffix</note>
|
<note priority="1" from="description">Chrome click here suffix</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -958,7 +1022,7 @@
|
|||||||
<source>You must manually load the extension and modify the extension's settings to set the frontend URL.</source>
|
<source>You must manually load the extension and modify the extension's settings to set the frontend URL.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">230</context>
|
<context context-type="linenumber">234</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Chrome setup suffix</note>
|
<note priority="1" from="description">Chrome setup suffix</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -966,7 +1030,7 @@
|
|||||||
<source>to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page.</source>
|
<source>to install the official YoutubeDL-Material Firefox extension right off the Firefox extensions page.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">235</context>
|
<context context-type="linenumber">239</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Firefox click here suffix</note>
|
<note priority="1" from="description">Firefox click here suffix</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -974,7 +1038,7 @@
|
|||||||
<source>Detailed setup instructions.</source>
|
<source>Detailed setup instructions.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">236</context>
|
<context context-type="linenumber">240</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Firefox setup prefix link</note>
|
<note priority="1" from="description">Firefox setup prefix link</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -982,7 +1046,7 @@
|
|||||||
<source>Not much is required other than changing the extension's settings to set the frontend URL.</source>
|
<source>Not much is required other than changing the extension's settings to set the frontend URL.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">236</context>
|
<context context-type="linenumber">240</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Firefox setup suffix</note>
|
<note priority="1" from="description">Firefox setup suffix</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -990,7 +1054,7 @@
|
|||||||
<source>Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark.</source>
|
<source>Drag the link below to your bookmarks, and you're good to go! Just navigate to the YouTube video you'd like to download, and click the bookmark.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">241</context>
|
<context context-type="linenumber">245</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Bookmarklet instructions</note>
|
<note priority="1" from="description">Bookmarklet instructions</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -998,7 +1062,7 @@
|
|||||||
<source>Generate 'audio only' bookmarklet</source>
|
<source>Generate 'audio only' bookmarklet</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">242</context>
|
<context context-type="linenumber">246</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Generate audio only bookmarklet checkbox</note>
|
<note priority="1" from="description">Generate audio only bookmarklet checkbox</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1006,7 +1070,7 @@
|
|||||||
<source>Extra</source>
|
<source>Extra</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">160</context>
|
<context context-type="linenumber">164</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Extra settings label</note>
|
<note priority="1" from="description">Extra settings label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1014,7 +1078,7 @@
|
|||||||
<source>Use default downloading agent</source>
|
<source>Use default downloading agent</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">256</context>
|
<context context-type="linenumber">260</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Use default downloading agent setting</note>
|
<note priority="1" from="description">Use default downloading agent setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1022,27 +1086,47 @@
|
|||||||
<source>Select a downloader</source>
|
<source>Select a downloader</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">260</context>
|
<context context-type="linenumber">264</context>
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
|
||||||
<context context-type="linenumber">274</context>
|
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Custom downloader select label</note>
|
<note priority="1" from="description">Custom downloader select label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="00e274c496b094a019f0679c3fab3945793f3335" datatype="html">
|
||||||
|
<source>Select a logger level</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
|
<context context-type="linenumber">278</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Logger level select label</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8" datatype="html">
|
<trans-unit id="dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8" datatype="html">
|
||||||
<source>Allow advanced download</source>
|
<source>Allow advanced download</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">285</context>
|
<context context-type="linenumber">289</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Allow advanced downloading setting</note>
|
<note priority="1" from="description">Allow advanced downloading setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="431e5f3a0dde88768d1074baedd65266412b3f02" datatype="html">
|
||||||
|
<source>Use Cookies</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
|
<context context-type="linenumber">297</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Use cookies setting</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="80651a7ad1229ea6613557d3559f702cfa5aecf5" datatype="html">
|
||||||
|
<source>Set Cookies</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
|
<context context-type="linenumber">298</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Set cookies button</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="bc2e854e111ecf2bd7db170da5e3c2ed08181d88" datatype="html">
|
<trans-unit id="bc2e854e111ecf2bd7db170da5e3c2ed08181d88" datatype="html">
|
||||||
<source>Advanced</source>
|
<source>Advanced</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">251</context>
|
<context context-type="linenumber">255</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Host settings label</note>
|
<note priority="1" from="description">Host settings label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1050,7 +1134,7 @@
|
|||||||
<source>Allow user registration</source>
|
<source>Allow user registration</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">297</context>
|
<context context-type="linenumber">310</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Allow registration setting</note>
|
<note priority="1" from="description">Allow registration setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1058,15 +1142,23 @@
|
|||||||
<source>Users</source>
|
<source>Users</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">295</context>
|
<context context-type="linenumber">308</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Users settings label</note>
|
<note priority="1" from="description">Users settings label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="eb3d5aefff38a814b76da74371cbf02c0789a1ef" datatype="html">
|
||||||
|
<source>Logs</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
|
<context context-type="linenumber">314</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Logs settings label</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="52c9a103b812f258bcddc3d90a6e3f46871d25fe" datatype="html">
|
<trans-unit id="52c9a103b812f258bcddc3d90a6e3f46871d25fe" datatype="html">
|
||||||
<source>Save</source>
|
<source>Save</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">307</context>
|
<context context-type="linenumber">327</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Settings save button</note>
|
<note priority="1" from="description">Settings save button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1074,7 +1166,7 @@
|
|||||||
<source>{VAR_SELECT, select, true {Close} false {Cancel} other {otha} }</source>
|
<source>{VAR_SELECT, select, true {Close} false {Cancel} other {otha} }</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
<context context-type="sourcefile">app/settings/settings.component.html</context>
|
||||||
<context context-type="linenumber">310</context>
|
<context context-type="linenumber">330</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Settings cancel and close button</note>
|
<note priority="1" from="description">Settings cancel and close button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1188,6 +1280,10 @@
|
|||||||
<context context-type="sourcefile">app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">20</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/app.component.html</context>
|
||||||
|
<context context-type="linenumber">44</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/components/login/login.component.html</context>
|
<context context-type="sourcefile">app/components/login/login.component.html</context>
|
||||||
<context context-type="linenumber">15</context>
|
<context context-type="linenumber">15</context>
|
||||||
@@ -1254,7 +1350,7 @@
|
|||||||
<source>Subscriptions</source>
|
<source>Subscriptions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/app.component.html</context>
|
<context context-type="sourcefile">app/app.component.html</context>
|
||||||
<context context-type="linenumber">44</context>
|
<context context-type="linenumber">45</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Navigation menu Subscriptions Page title</note>
|
<note priority="1" from="description">Navigation menu Subscriptions Page title</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1262,7 +1358,7 @@
|
|||||||
<source>Downloads</source>
|
<source>Downloads</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/app.component.html</context>
|
<context context-type="sourcefile">app/app.component.html</context>
|
||||||
<context context-type="linenumber">45</context>
|
<context context-type="linenumber">46</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Navigation menu Downloads Page title</note>
|
<note priority="1" from="description">Navigation menu Downloads Page title</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1320,6 +1416,10 @@
|
|||||||
<context context-type="sourcefile">app/dialogs/share-media-dialog/share-media-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/share-media-dialog/share-media-dialog.component.html</context>
|
||||||
<context context-type="linenumber">24</context>
|
<context context-type="linenumber">24</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/components/logs-viewer/logs-viewer.component.html</context>
|
||||||
|
<context context-type="linenumber">7</context>
|
||||||
|
</context-group>
|
||||||
<note priority="1" from="description">Copy to clipboard button</note>
|
<note priority="1" from="description">Copy to clipboard button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f" datatype="html">
|
<trans-unit id="5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f" datatype="html">
|
||||||
@@ -1390,23 +1490,15 @@
|
|||||||
<source>Custom name</source>
|
<source>Custom name</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
<context context-type="linenumber">14</context>
|
<context context-type="linenumber">19</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Subscription custom name placeholder</note>
|
<note priority="1" from="description">Subscription custom name placeholder</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f3f62aa84d59f3a8b900cc9a7eec3ef279a7b4e7" datatype="html">
|
|
||||||
<source>This is optional</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
|
||||||
<context context-type="linenumber">15</context>
|
|
||||||
</context-group>
|
|
||||||
<note priority="1" from="description">Custom name input hint</note>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="ea30873bd3f0d5e4fb2378eec3f0a1db77634a28" datatype="html">
|
<trans-unit id="ea30873bd3f0d5e4fb2378eec3f0a1db77634a28" datatype="html">
|
||||||
<source>Download all uploads</source>
|
<source>Download all uploads</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
<context context-type="linenumber">19</context>
|
<context context-type="linenumber">23</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Download all uploads subscription setting</note>
|
<note priority="1" from="description">Download all uploads subscription setting</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1414,23 +1506,47 @@
|
|||||||
<source>Download videos uploaded in the last</source>
|
<source>Download videos uploaded in the last</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
<context context-type="linenumber">22</context>
|
<context context-type="linenumber">26</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Download time range prefix</note>
|
<note priority="1" from="description">Download time range prefix</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="c76a955642714b8949ff3e4b4990864a2e2cac95" datatype="html">
|
||||||
|
<source>Audio-only mode</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">38</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Streaming-only mode</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="408ca4911457e84a348cecf214f02c69289aa8f1" datatype="html">
|
<trans-unit id="408ca4911457e84a348cecf214f02c69289aa8f1" datatype="html">
|
||||||
<source>Streaming-only mode</source>
|
<source>Streaming-only mode</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
<context context-type="linenumber">34</context>
|
<context context-type="linenumber">43</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Streaming-only mode</note>
|
<note priority="1" from="description">Streaming-only mode</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="f432e1a8d6adb12e612127978ce2e0ced933959c" datatype="html">
|
||||||
|
<source>These are added after the standard args.</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">51</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Custom args hint</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="98b6ec9ec138186d663e64770267b67334353d63" datatype="html">
|
||||||
|
<source>Custom file output</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">57</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Subscription custom file output placeholder</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="d0336848b0c375a1c25ba369b3481ee383217a4f" datatype="html">
|
<trans-unit id="d0336848b0c375a1c25ba369b3481ee383217a4f" datatype="html">
|
||||||
<source>Subscribe</source>
|
<source>Subscribe</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
<context context-type="sourcefile">app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
|
||||||
<context context-type="linenumber">44</context>
|
<context context-type="linenumber">72</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Subscribe button</note>
|
<note priority="1" from="description">Subscribe button</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1594,7 +1710,7 @@
|
|||||||
<source>No downloads available!</source>
|
<source>No downloads available!</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">app/components/downloads/downloads.component.html</context>
|
<context context-type="sourcefile">app/components/downloads/downloads.component.html</context>
|
||||||
<context context-type="linenumber">22</context>
|
<context context-type="linenumber">25</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">No downloads label</note>
|
<note priority="1" from="description">No downloads label</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1726,6 +1842,22 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<note priority="1" from="description">Edit role</note>
|
<note priority="1" from="description">Edit role</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="fd59fb984749fcdb5e386ae85faec82f8e5ac098" datatype="html">
|
||||||
|
<source>Logs will appear here</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/components/logs-viewer/logs-viewer.component.html</context>
|
||||||
|
<context context-type="linenumber">5</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Logs placeholder</note>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5009630cdf32ab4f1c78737b9617b8773512c05a" datatype="html">
|
||||||
|
<source>Lines:</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">app/components/logs-viewer/logs-viewer.component.html</context>
|
||||||
|
<context context-type="linenumber">9</context>
|
||||||
|
</context-group>
|
||||||
|
<note priority="1" from="description">Label for lines select in logger view</note>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -196,5 +196,20 @@
|
|||||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": " Rol ",
|
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": " Rol ",
|
||||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": " Acciones ",
|
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": " Acciones ",
|
||||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Agregar Usuarios",
|
"4d92a0395dd66778a931460118626c5794a3fc7a": "Agregar Usuarios",
|
||||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Editar Rol"
|
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Editar Rol",
|
||||||
|
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modify playlist",
|
||||||
|
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Editar",
|
||||||
|
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Sube nuevas cookies",
|
||||||
|
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Arrastrar y soltar",
|
||||||
|
"85e0725c870b28458fd3bbba905397d890f00a69": "NOTA: Cargar nuevas cookies anulará sus cookies anteriores. También tenga en cuenta que las cookies son de toda la instancia, no por usuario.",
|
||||||
|
"d01715b75228878a773ae6d059acc639d4898a03": "Anulación de descarga segura",
|
||||||
|
"00e274c496b094a019f0679c3fab3945793f3335": "Seleccione un nivel de registrador",
|
||||||
|
"431e5f3a0dde88768d1074baedd65266412b3f02": "Utilizar Cookies",
|
||||||
|
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Establecer Cookies",
|
||||||
|
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Registros",
|
||||||
|
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Solo audio",
|
||||||
|
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Estos se agregan después de los argumentos estándar.",
|
||||||
|
"98b6ec9ec138186d663e64770267b67334353d63": "Salida de archivo personalizada",
|
||||||
|
"fd59fb984749fcdb5e386ae85faec82f8e5ac098": "Los registros aparecerán aquí",
|
||||||
|
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Líneas:"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user