Compare commits

..

34 Commits

Author SHA1 Message Date
Tzahi12345
e2baa30d9a updated ngx-avatars 2025-08-16 00:48:42 -04:00
Tzahi12345
a74bca05cc Improved download fail backup method 2024-09-21 23:08:42 -04:00
Tzahi12345
7d3458ea41 Testing using npm registry mirror 2023-12-16 20:16:56 -05:00
Tzahi12345
6a7c1c9d0b Set docker npm install timeout to 60s 2023-12-16 19:48:27 -05:00
Tzahi12345
e2e3dd280a Added back python and added flag to pip to force install pycryptodomex 2023-12-16 02:18:32 -05:00
Tzahi12345
25bf7a6fdd Trying removal of python 2023-12-15 22:24:57 -05:00
Tzahi12345
c56987ddd5 Downgraded ubuntu to 23.04 2023-12-15 22:17:57 -05:00
Tzahi12345
72399b09e4 Removed libicu70 from fetch-twitch-downloader 2023-12-15 22:12:33 -05:00
Tzahi12345
4258b82040 Removed libicu70 2023-12-15 22:10:15 -05:00
Tzahi12345
7ac6a50b41 Updated UID/GID to 1001 2023-12-15 22:08:38 -05:00
Tzahi12345
fb92975b73 Updated docker Ubuntu to 24.04 2023-12-15 21:45:00 -05:00
Tzahi12345
b4cf1e39b9 Removed version from docker npm install 2023-12-15 21:44:25 -05:00
Tzahi12345
026f24a327 Updated npm version in Dockerfile 2023-12-09 02:06:49 -05:00
Tzahi12345
1bf348f481 Cleaned up pm2 installcommand 2023-12-09 00:40:10 -05:00
Tzahi12345
eb8cd3fd06 Remove curl install from pm2 2023-12-09 00:22:38 -05:00
Tzahi12345
f96ffab530 Install pm2 without npm 2023-12-09 00:18:35 -05:00
Tzahi12345
dcb53691e3 mocha is now a backend dev dependency 2023-12-08 22:49:08 -05:00
Tzahi12345
2cf21541bb Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into angular-17 2023-12-06 20:46:14 -05:00
Tzahi12345
9b38c56528 Reverted using production as defaultConfiguration in angular.json 2023-12-05 22:15:35 -05:00
Isaac Abadi
3912655912 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into angular-17 2023-12-03 20:02:27 -05:00
Isaac Abadi
84464db0e0 Frontend dev environment now uses non-prod mode by default 2023-12-03 18:51:37 -05:00
Isaac Abadi
9556f9c94f Updated docker frontend build node version to 18 2023-12-03 02:02:38 -05:00
Isaac Abadi
4a97fa4ef5 Updates node version in CI 2023-12-02 15:58:32 -05:00
Isaac Abadi
2c155b74a9 Updates version info/requirements in README 2023-12-02 15:57:19 -05:00
Isaac Abadi
25e4c114e8 Updated templates to new Angular control flow 2023-12-02 15:50:56 -05:00
Isaac Abadi
6152df3486 Added missing saveAs imports 2023-12-02 03:11:22 -05:00
Isaac Abadi
7cf5d86fc3 Updated styles.scss to match new Angular syntax
Added back ngx-avatars

Made required dependency updates
2023-12-02 03:10:06 -05:00
Isaac Abadi
f57e0ab187 Updated Angular Material to v17 2023-12-02 01:35:08 -05:00
Isaac Abadi
517c9e169d Updated to Angular 17 2023-12-02 01:30:40 -05:00
Isaac Abadi
69d8751484 Updated Angular Material to v16 2023-12-02 01:23:15 -05:00
Isaac Abadi
c3c8f50a92 Updated to Angular 16 2023-12-02 01:17:40 -05:00
Isaac Abadi
caadf4f9d2 Temporarily removed ngx-avatars 2023-12-02 01:06:48 -05:00
Isaac Abadi
d10401cead Force update ngx-avatars 2023-12-01 16:35:31 -05:00
Isaac Abadi
d02d100001 Force ngx-avatars to use angular 16 2023-12-01 16:32:33 -05:00
83 changed files with 10027 additions and 36871 deletions

View File

@@ -15,9 +15,9 @@ jobs:
- name: checkout code
uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '18'
cache: 'npm'
- name: install dependencies
run: |
@@ -55,7 +55,7 @@ jobs:
Copy-Item -Path ./backend/*.js -Destination ./build/youtubedl-material
Copy-Item -Path ./backend/*.json -Destination ./build/youtubedl-material
- name: upload build artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: youtubedl-material
path: build
@@ -81,7 +81,7 @@ jobs:
draft: true
prerelease: false
- name: download build artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: youtubedl-material
path: ${{runner.temp}}/youtubedl-material

View File

@@ -24,9 +24,9 @@ jobs:
json: '{"type": "docker", "tag": "nightly", "commit": "${{ steps.vars.outputs.sha_short }}", "date": "${{ steps.date.outputs.date }}"}'
dir: 'backend/'
- name: setup platform emulator
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v2
- name: setup multi-arch docker build
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: build & push images
uses: docker/build-push-action@v5
with:

View File

@@ -57,10 +57,10 @@ jobs:
type=raw,value=latest
- name: setup platform emulator
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v2
- name: setup multi-arch docker build
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v3

View File

@@ -41,10 +41,10 @@ jobs:
dir: 'backend/'
- name: setup platform emulator
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v2
- name: setup multi-arch docker build
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: Generate Docker image metadata
id: docker-meta

View File

@@ -16,14 +16,14 @@ jobs:
strategy:
matrix:
node:
- 16
- 18
steps:
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: '${{ matrix.node }}'
- uses: actions/checkout@v4
- name: 'Cache node_modules'
uses: actions/cache@v4
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-v${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
@@ -33,7 +33,7 @@ jobs:
- uses: FedericoCarboni/setup-ffmpeg@v2
id: setup-ffmpeg
- name: Install Dependencies
run: npm install
run: npm install --dev
working-directory: ./backend
- name: Run All Node.js Tests
run: npm run test

View File

@@ -1,5 +1,5 @@
# Fetching our utils
FROM ubuntu:22.04 AS utils
FROM ubuntu:23.04 AS utils
ENV DEBIAN_FRONTEND=noninteractive
# Use script due local build compability
COPY docker-utils/*.sh .
@@ -8,13 +8,12 @@ RUN sh ./ffmpeg-fetch.sh
RUN sh ./fetch-twitchdownloader.sh
# Create our Ubuntu 22.04 with node 16.14.2 (that specific version is required as per: https://stackoverflow.com/a/72855258/8088021)
# Go to 20.04
FROM ubuntu:22.04 AS base
# Create our Ubuntu 22.04 with node 18.19.0
FROM ubuntu:23.04 AS base
ARG TARGETPLATFORM
ARG DEBIAN_FRONTEND=noninteractive
ENV UID=1000
ENV GID=1000
ENV UID=1001
ENV GID=1001
ENV USER=youtube
ENV NO_UPDATE_NOTIFIER=true
ENV PM2_HOME=/app/pm2
@@ -22,10 +21,10 @@ ENV ALLOW_CONFIG_MUTATIONS=true
ENV npm_config_cache=/app/.npm
# Use NVM to get specific node version
ENV NODE_VERSION=16.14.2
ENV NODE_VERSION=18.19.0
RUN groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
apt update && \
apt install -y --no-install-recommends curl ca-certificates tzdata libicu70 libatomic1 && \
apt install -y --no-install-recommends curl ca-certificates tzdata libatomic1 && \
apt clean && \
rm -rf /var/lib/apt/lists/*
@@ -37,9 +36,11 @@ RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
RUN npm install -g npm
# Build frontend
ARG BUILDPLATFORM
FROM --platform=${BUILDPLATFORM} node:16 as frontend
FROM --platform=${BUILDPLATFORM} node:18 as frontend
RUN npm install -g @angular/cli
WORKDIR /build
COPY [ "package.json", "package-lock.json", "angular.json", "tsconfig.json", "/build/" ]
@@ -56,6 +57,8 @@ FROM base as backend
WORKDIR /app
COPY [ "backend/","/app/" ]
RUN npm config set strict-ssl false && \
npm config set registry https://registry.npm.taobao.org && \
npm config set fetch-retry-maxtimeout 60000 && \
npm install --prod && \
ls -al
@@ -72,10 +75,10 @@ RUN npm config set strict-ssl false && \
# Final image
FROM base
RUN npm install -g pm2 && \
apt update && \
RUN apt update && \
curl -sL https://raw.githubusercontent.com/Unitech/pm2/master/packager/setup.deb.sh | bash && \
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley build-essential && \
pip install pycryptodomex && \
pip install pycryptodomex --break-system-packages && \
apt remove -y --purge build-essential && \
apt autoremove -y --purge && \
apt clean && \

View File

@@ -6,7 +6,7 @@
[![GitHub issues badge](https://img.shields.io/github/issues/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/issues)
[![License badge](https://img.shields.io/github/license/Tzahi12345/YoutubeDL-Material)](https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/LICENSE.md)
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 15](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend.
YoutubeDL-Material is a Material Design frontend for [youtube-dl](https://rg3.github.io/youtube-dl/). It's coded using [Angular 17](https://angular.io/) for the frontend, and [Node.js](https://nodejs.org/) on the backend.
Now with [Docker](#Docker) support!
@@ -30,7 +30,7 @@ NOTE: If you would like to use Docker, you can skip down to the [Docker](#Docker
Required dependencies:
* Node.js 16
* Node.js 18
* Python
Optional dependencies:
@@ -42,7 +42,7 @@ Optional dependencies:
<summary>Debian/Ubuntu</summary>
```bash
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install nodejs youtube-dl ffmpeg unzip python npm
```
@@ -57,7 +57,7 @@ sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfu
sudo yum install centos-release-scl-rh
sudo yum install rh-nodejs12
scl enable rh-nodejs12 bash
curl -fsSL https://rpm.nodesource.com/setup_16.x | sudo bash -
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -
sudo yum install nodejs youtube-dl ffmpeg ffmpeg-devel
```
@@ -136,7 +136,7 @@ Once you have enabled the API and have the key, you can start sending requests b
## iOS Shortcut
If you are using iOS, try YoutubeDL-Material more conveniently with a Shortcut. With this Shortcut, you can easily start downloading YouTube video with just two taps! (Or maybe three?)
If you are using iOS, try YoutubeDL-Material more conveniently with a Shortcut. With this Shorcut, you can easily start downloading YouTube video with just two taps! (Or maybe three?)
You can download Shortcut [here.](https://routinehub.co/shortcut/10283/)

View File

@@ -83,24 +83,24 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "youtube-dl-material:build"
"buildTarget": "youtube-dl-material:build"
},
"configurations": {
"production": {
"browserTarget": "youtube-dl-material:build:production"
"buildTarget": "youtube-dl-material:build:production"
},
"es": {
"browserTarget": "youtube-dl-material:build:es"
"buildTarget": "youtube-dl-material:build:es"
},
"codespaces": {
"browserTarget": "youtube-dl-material:build:codespaces"
"buildTarget": "youtube-dl-material:build:codespaces"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "youtube-dl-material:build"
"buildTarget": "youtube-dl-material:build"
}
},
"serve-electron": {

View File

@@ -521,6 +521,8 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
downloadConfig.push('--write-thumbnail');
}
downloadConfig.push('-i');
if (globalArgs && globalArgs !== '') {
// adds global args
if (downloadConfig.indexOf('-o') !== -1 && globalArgs.split(',,').indexOf('-o') !== -1) {
@@ -571,8 +573,8 @@ exports.getVideoInfoByURL = async (url, args = [], download_uid = null) => {
let {callback} = await youtubedl_api.runYoutubeDL(url, new_args);
const {parsed_output, err} = await callback;
if (!parsed_output || parsed_output.length === 0) {
let error_message = `Error while retrieving info on video with URL ${url} with the following message: ${err}`;
if (err.stderr) error_message += `\n\n${err.stderr}`;
let error_message = `Error while retrieving info on video with URL ${url}`;
if (err.stderr) error_message += ` with the following message: \n${err.stderr}`;
logger.error(error_message);
if (download_uid) {
await handleDownloadError(download_uid, error_message, 'info_retrieve_failed');

View File

@@ -30,7 +30,6 @@
"lodash": "^4.17.21",
"lowdb": "^1.0.0",
"md5": "^2.2.1",
"mocha": "^9.2.2",
"moment": "^2.29.4",
"mongodb": "^3.6.9",
"multer": "1.4.5-lts.1",
@@ -54,6 +53,9 @@
"winston": "^3.7.2",
"xmlbuilder2": "^3.0.2"
},
"devDependencies": {
"mocha": "^10.2.0"
},
"engines": {
"node": "^16",
"npm": "6.14.4"
@@ -363,11 +365,6 @@
"@types/node": "*"
}
},
"node_modules/@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q=="
},
"node_modules/@vladfrangu/async_event_emitter": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.1.tgz",
@@ -413,6 +410,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -421,6 +419,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -429,6 +428,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -443,6 +443,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -453,7 +454,8 @@
"node_modules/ansi-styles/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/any-promise": {
"version": "1.3.0",
@@ -464,6 +466,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -531,7 +534,8 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/array-buffer-byte-length": {
"version": "1.0.0",
@@ -707,6 +711,7 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -770,6 +775,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -780,7 +786,8 @@
"node_modules/browser-stdout": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"node_modules/bson": {
"version": "1.1.6",
@@ -935,6 +942,7 @@
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
@@ -945,6 +953,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -1253,6 +1262,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
"integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
"dev": true,
"engines": {
"node": ">=10"
},
@@ -1331,6 +1341,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
@@ -1524,6 +1535,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -1537,6 +1549,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"engines": {
"node": ">=10"
},
@@ -1797,6 +1810,7 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1825,6 +1839,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -1840,6 +1855,7 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"dev": true,
"bin": {
"flat": "cli.js"
}
@@ -1964,6 +1980,7 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -2021,6 +2038,7 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
@@ -2095,6 +2113,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -2181,14 +2200,6 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
},
"node_modules/growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"engines": {
"node": ">=4.x"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@@ -2280,6 +2291,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true,
"bin": {
"he": "bin/he"
}
@@ -2450,6 +2462,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -2506,6 +2519,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -2514,6 +2528,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -2551,6 +2566,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -2573,6 +2589,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -2671,6 +2688,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true,
"engines": {
"node": ">=10"
},
@@ -2708,6 +2726,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -2905,6 +2924,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -2949,6 +2969,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
@@ -2964,6 +2985,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -2979,6 +3001,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -2987,6 +3010,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -3178,41 +3202,39 @@
}
},
"node_modules/mocha": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz",
"integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==",
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
"integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
"dev": true,
"dependencies": {
"@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
"chokidar": "3.5.3",
"debug": "4.3.3",
"debug": "4.3.4",
"diff": "5.0.0",
"escape-string-regexp": "4.0.0",
"find-up": "5.0.0",
"glob": "7.2.0",
"growl": "1.10.5",
"he": "1.2.0",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "4.2.1",
"minimatch": "5.0.1",
"ms": "2.1.3",
"nanoid": "3.3.1",
"nanoid": "3.3.3",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
"which": "2.0.2",
"workerpool": "6.2.0",
"workerpool": "6.2.1",
"yargs": "16.2.0",
"yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0"
},
"bin": {
"_mocha": "bin/_mocha",
"mocha": "bin/mocha"
"mocha": "bin/mocha.js"
},
"engines": {
"node": ">= 12.0.0"
"node": ">= 14.0.0"
},
"funding": {
"type": "opencollective",
@@ -3223,6 +3245,7 @@
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
@@ -3246,9 +3269,10 @@
}
},
"node_modules/mocha/node_modules/debug": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -3264,12 +3288,14 @@
"node_modules/mocha/node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/mocha/node_modules/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -3289,6 +3315,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -3300,30 +3327,43 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/mocha/node_modules/minimatch": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz",
"integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/mocha/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/mocha/node_modules/nanoid": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz",
"integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
"integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -3335,6 +3375,7 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -3346,6 +3387,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"engines": {
"node": ">=8"
},
@@ -3357,6 +3399,7 @@
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -3761,6 +3804,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -3775,6 +3819,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -3884,6 +3929,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -3935,6 +3981,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true,
"engines": {
"node": ">=8.6"
},
@@ -4039,6 +4086,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
@@ -4245,6 +4293,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4391,6 +4440,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
@@ -4568,6 +4618,7 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -4580,12 +4631,14 @@
"node_modules/string-width/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/string-width/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -4594,6 +4647,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.0"
},
@@ -4726,6 +4780,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -5106,14 +5161,16 @@
}
},
"node_modules/workerpool": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz",
"integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A=="
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -5130,6 +5187,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -5220,6 +5278,7 @@
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"engines": {
"node": ">=10"
}
@@ -5233,6 +5292,7 @@
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"dev": true,
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
@@ -5250,6 +5310,7 @@
"version": "20.2.4",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
"dev": true,
"engines": {
"node": ">=10"
}
@@ -5258,6 +5319,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
"integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
"dev": true,
"dependencies": {
"camelcase": "^6.0.0",
"decamelize": "^4.0.0",
@@ -5272,6 +5334,7 @@
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"dev": true,
"engines": {
"node": ">=10"
},
@@ -5283,6 +5346,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"engines": {
"node": ">=10"
},

View File

@@ -44,7 +44,6 @@
"lodash": "^4.17.21",
"lowdb": "^1.0.0",
"md5": "^2.2.1",
"mocha": "^9.2.2",
"moment": "^2.29.4",
"mongodb": "^3.6.9",
"multer": "1.4.5-lts.1",
@@ -67,5 +66,8 @@
"uuid": "^9.0.1",
"winston": "^3.7.2",
"xmlbuilder2": "^3.0.2"
},
"devDependencies": {
"mocha": "^10.2.0"
}
}

View File

@@ -529,7 +529,9 @@ exports.parseOutputJSON = (output, err) => {
let split_output = [];
// const output_jsons = [];
if (err && !output) {
if (!err.stderr.includes('This video is unavailable') && !err.stderr.includes('Private video')) {
const attempt_backup_errs = ['This video is unavailable', 'Private video', 'unavailable video'];
const attempt_backup = err.stderr ? attempt_backup_errs.some(err_msg => err.stderr.includes(err_msg)) : false;
if (!attempt_backup) {
return null;
}
logger.info('An error was encountered with at least one video, backup method will be used.')

View File

@@ -67,7 +67,9 @@ const runYoutubeDLProcess = async (url, args, youtubedl_fork = config_api.getCon
const parsed_output = utils.parseOutputJSON(stdout.trim().split(/\r?\n/), stderr);
resolve({parsed_output, err: stderr});
} catch (e) {
resolve({parsed_output: null, err: e})
// Attempt to not fail
const parsed_output = utils.parseOutputJSON(e && e.stdout && e.stdout.trim().split(/\r?\n/), e && e.stderr);
resolve({parsed_output: parsed_output, err: parsed_output ? null : e});
}
});
return {child_process, callback}

View File

@@ -22,7 +22,7 @@ esac
echo "(INFO) Architecture detected: $ARCH"
echo "(1/5) READY - Install unzip"
apt-get update && apt-get -y install unzip curl jq libicu70
apt-get update && apt-get -y install unzip curl jq
VERSION=$(curl --silent "https://api.github.com/repos/lay295/TwitchDownloader/releases" | jq -r --arg arch "$ARCH" '[.[] | select(.assets | length > 0) | select(.assets[].name | contains("CLI") and contains($arch))] | max_by(.published_at) | .tag_name')
echo "(2/5) DOWNLOAD - Acquire twitchdownloader"
curl -o twitchdownloader.zip \

13585
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,23 +17,23 @@
"i18n-source": "ng extract-i18n --output-path=src/assets/i18n --out-file=messages.en.xlf"
},
"engines": {
"node": "12.3.1",
"npm": "6.10.3"
"node": "18.19.0",
"npm": "10.2.3"
},
"private": true,
"dependencies": {
"@angular-devkit/core": "^15.0.1",
"@angular/animations": "^15.0.1",
"@angular/cdk": "^15.0.0",
"@angular/common": "^15.0.1",
"@angular/compiler": "^15.0.1",
"@angular/core": "^15.0.1",
"@angular/forms": "^15.0.1",
"@angular/localize": "^15.0.1",
"@angular/material": "^15.0.0",
"@angular/platform-browser": "^15.0.1",
"@angular/platform-browser-dynamic": "^15.0.1",
"@angular/router": "^15.0.1",
"@angular-devkit/core": "^17.0.5",
"@angular/animations": "^17.0.5",
"@angular/cdk": "^17.0.2",
"@angular/common": "^17.0.5",
"@angular/compiler": "^17.0.5",
"@angular/core": "^17.0.5",
"@angular/forms": "^17.0.5",
"@angular/localize": "^17.0.5",
"@angular/material": "^17.0.2",
"@angular/platform-browser": "^17.0.5",
"@angular/platform-browser-dynamic": "^17.0.5",
"@angular/router": "^17.0.5",
"@fontsource/material-icons": "^4.5.4",
"@ngneat/content-loader": "^7.0.0",
"@videogular/ngx-videogular": "^6.0.0",
@@ -44,20 +44,19 @@
"fs-extra": "^10.0.0",
"material-icons": "^1.10.8",
"nan": "^2.14.1",
"ngx-avatars": "^1.4.1",
"ngx-avatars": "^1.10.0",
"ngx-file-drop": "^15.0.0",
"rxjs": "^6.6.3",
"rxjs-compat": "^6.6.7",
"tslib": "^2.0.0",
"typescript": "~4.8.4",
"xliff-to-json": "^1.0.4",
"zone.js": "~0.11.4"
"zone.js": "~0.14.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.0.1",
"@angular/cli": "^15.0.1",
"@angular/compiler-cli": "^15.0.1",
"@angular/language-service": "^15.0.1",
"@angular-devkit/build-angular": "^17.0.5",
"@angular/cli": "^17.0.5",
"@angular/compiler-cli": "^17.0.5",
"@angular/language-service": "^17.0.5",
"@types/core-js": "^2.5.2",
"@types/file-saver": "^2.0.1",
"@types/jasmine": "^4.3.1",
@@ -67,7 +66,7 @@
"ajv": "^7.2.4",
"codelyzer": "^6.0.0",
"eslint": "^7.32.0",
"jasmine-core": "~3.6.0",
"jasmine-core": "~3.8.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.4.2",
"karma-chrome-launcher": "~3.1.0",
@@ -78,12 +77,13 @@
"openapi-typescript-codegen": "^0.23.0",
"protractor": "~7.0.0",
"ts-node": "~3.0.4",
"tslint": "~6.1.0"
"tslint": "~6.1.0",
"typescript": "~5.2.0"
},
"overrides": {
"ngx-avatars": {
"@angular/common": "15.0.1",
"@angular/core": "15.0.1"
"@angular/common": "^17.0.0",
"@angular/core": "^17.0.0"
}
}
}

View File

@@ -5,13 +5,23 @@
<div class="row" width="100%" height="100%">
<div class="col-6" style="text-align: left; margin-top: 1px;">
<div style="display: flex; align-items: center;">
<button #hamburgerMenu style="outline: none" *ngIf="router.url.split(';')[0] !== '/player'" mat-icon-button aria-label="Toggle side navigation" (click)="toggleSidenav()"><mat-icon>menu</mat-icon></button>
<button (click)="goBack()" *ngIf="router.url.split(';')[0] === '/player'" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
@if (router.url.split(';')[0] !== '/player') {
<button #hamburgerMenu style="outline: none" mat-icon-button aria-label="Toggle side navigation" (click)="toggleSidenav()"><mat-icon>menu</mat-icon></button>
} @else {
<button (click)="goBack()" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
}
<div style="margin-left: 8px; display: inline-block;"><button mat-icon-button routerLink='/home'><img style="width: 32px;" src="assets/images/logo_128px.png"></button></div>
</div>
</div>
<div class="col-6" style="text-align: right; align-items: flex-end; display: inline-block">
<button *ngIf="postsService.config?.Extra.enable_notifications" [matMenuTriggerFor]="notificationsMenu" (menuOpened)="notificationMenuOpened()" mat-icon-button><mat-icon [matBadge]="notification_count" matBadgeColor="warn" matBadgeSize="small" *ngIf="notification_count > 0">notifications</mat-icon><mat-icon *ngIf="notification_count === 0">notifications_none</mat-icon></button>
@if (postsService.config?.Extra.enable_notifications) {
<button [matMenuTriggerFor]="notificationsMenu" (menuOpened)="notificationMenuOpened()" mat-icon-button>
@if (notification_count > 0) {
<mat-icon [matBadge]="notification_count" matBadgeColor="warn" matBadgeSize="small">notifications</mat-icon>
} @else {
<mat-icon>notifications_none</mat-icon>
}</button>
}
<mat-menu [classList]="'notifications-menu'" (close)="notificationMenuClosed()" #notificationsMenu="matMenu">
<app-notifications #notifications (notificationCount)="notificationCountUpdate($event)" (click)="$event.stopPropagation()"></app-notifications>
</mat-menu>
@@ -21,15 +31,19 @@
<mat-icon>person</mat-icon>
<span i18n="Profile menu label">Profile</span>
</button>
<button *ngIf="!postsService.config?.Advanced.multi_user_mode || postsService.isLoggedIn" class="top-menu-button" (click)="openArchivesDialog()" mat-menu-item>
@if (!postsService.config?.Advanced.multi_user_mode || postsService.isLoggedIn) {
<button class="top-menu-button" (click)="openArchivesDialog()" mat-menu-item>
<mat-icon>topic</mat-icon>
<span i18n="Archives menu label">Archives</span>
</button>
<button class="top-menu-button" (click)="themeMenuItemClicked($event)" *ngIf="allowThemeChange" mat-menu-item>
}
@if (allowThemeChange) {
<button class="top-menu-button" (click)="themeMenuItemClicked($event)" mat-menu-item>
<mat-icon>{{(postsService.theme.key === 'default') ? 'brightness_5' : 'brightness_2'}}</mat-icon>
<span i18n="Dark mode toggle label">Dark</span>
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
</button>
}
<button class="top-menu-button" (click)="openAboutDialog()" mat-menu-item>
<mat-icon>info</mat-icon>
<span i18n="About menu label">About</span>
@@ -44,19 +58,33 @@
<mat-sidenav-container style="height: 100%">
<mat-sidenav [opened]="postsService.sidepanel_mode === 'side' && !window.location.href.includes('/player')" [mode]="postsService.sidepanel_mode" #sidenav>
<mat-nav-list>
<a *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn)" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" 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 && allowSubscriptions && postsService.hasPermission('subscriptions')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
<a *ngIf="postsService.config && postsService.hasPermission('tasks_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
@if (postsService.config && (!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn)) {
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
}
@if (postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn) {
<a mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
}
@if (postsService.config && allowSubscriptions && postsService.hasPermission('subscriptions')) {
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
}
@if (postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')) {
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
}
@if (postsService.config && postsService.hasPermission('tasks_manager')) {
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
}
@if (postsService.config && postsService.hasPermission('settings')) {
<mat-divider></mat-divider>
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/settings'><ng-container i18n="Settings menu label">Settings</ng-container></a>
</ng-container>
<ng-container *ngIf="postsService.config && allowSubscriptions && postsService.subscriptions && postsService.hasPermission('subscriptions')">
<mat-divider *ngIf="postsService.subscriptions.length > 0"></mat-divider>
<a *ngFor="let subscription of postsService.subscriptions" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" [routerLink]="['/subscription', { id: subscription.id }]"><ngx-avatars [style.display]="'inline-block'" [style.margin-right]="'10px'" size="32" [name]="subscription.name"></ngx-avatars>{{subscription.name}}</a>
</ng-container>
}
@if (postsService.config && allowSubscriptions && postsService.subscriptions && postsService.hasPermission('subscriptions')) {
@if (postsService.subscriptions.length > 0) {
<mat-divider></mat-divider>
}
@for (subscription of postsService.subscriptions; track subscription) {
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" [routerLink]="['/subscription', { id: subscription.id }]"><ngx-avatars [style.display]="'inline-block'" [style.margin-right]="'10px'" size="32" [name]="subscription.name"></ngx-avatars>{{subscription.name}}</a>
}
}
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">

View File

@@ -3,11 +3,9 @@
<mat-label i18n="Filter">Filter</mat-label>
<input matInput [(ngModel)]="text_filter" (keyup)="applyFilter($event)" #input>
</mat-form-field>
<div [hidden]="!(archives && archives.length > 0)">
<div class="mat-elevation-z8">
<mat-table matSort [dataSource]="dataSource">
<!-- Select Column -->
<!-- Checkbox Column -->
<ng-container matColumnDef="select">
@@ -25,13 +23,11 @@
<mat-icon class="audio-video-icon">{{(row.type === 'audio') ? 'audiotrack' : 'movie'}}</mat-icon>
</mat-cell>
</ng-container>
<!-- Date Column -->
<ng-container matColumnDef="timestamp">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Date">Date</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.timestamp*1000 | date: 'short'}} </mat-cell>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
@@ -41,7 +37,6 @@
</span>
</mat-cell>
</ng-container>
<!-- ID Column -->
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="ID">ID</ng-container> </mat-header-cell>
@@ -51,7 +46,6 @@
</span>
</mat-cell>
</ng-container>
<!-- Extractor Column -->
<ng-container matColumnDef="extractor">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Extractor">Extractor</ng-container> </mat-header-cell>
@@ -61,17 +55,16 @@
</span>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>
</div>
<div *ngIf="(!archives || archives.length === 0)">
@if ((!archives || archives.length === 0)) {
<div>
<h4 style="text-align: center; margin-top: 10px;" i18n="Archives empty">Archives empty</h4>
</div>
}
<div style="margin: 10px 10px 10px 0px; display: flex;">
<span style="flex-grow: 1;" class="flex-items">
<button [disabled]="selection.selected.length === 0" color="warn" style="margin: 10px;" mat-stroked-button i18n="Delete selected" (click)="openDeleteSelectedArchivesDialog()">Delete selected</button>
@@ -82,7 +75,9 @@
<mat-label i18n="Subscription">Subscription</mat-label>
<mat-select [ngModel]="sub_id" (ngModelChange)="subFilterSelectionChanged($event)">
<mat-option [value]="'none'" i18n="None">None</mat-option>
<mat-option *ngFor="let sub of postsService.subscriptions" [value]="sub.id">{{sub.name}}</mat-option>
@for (sub of postsService.subscriptions; track sub) {
<mat-option [value]="sub.id">{{sub.name}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field style="width: 100px; margin-bottom: -1.25em; margin-left: 10px;">
@@ -95,7 +90,6 @@
</mat-form-field>
</span>
</div>
<div class="file-drop-parent">
<ngx-file-drop [multiple]="false" accept=".txt" dropZoneLabel="Drop file here" (onFileDrop)="dropped($event)">
<ng-template class="file-drop" ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
@@ -110,11 +104,11 @@
</ng-template>
</ngx-file-drop>
</div>
<div style="margin-top: 10px; color: white">
<table class="table">
<tbody class="upload-name-style">
<tr *ngFor="let item of files; let i=index">
@for (item of files; track item; let i = $index) {
<tr>
<td style="vertical-align: middle; border-top: unset">
<strong>{{ item.relativePath }}</strong>
</td>
@@ -124,7 +118,9 @@
<mat-label i18n="Subscription">Subscription</mat-label>
<mat-select [ngModel]="upload_sub_id" (ngModelChange)="subUploadFilterSelectionChanged($event)">
<mat-option [value]="'none'" i18n="None">None</mat-option>
<mat-option *ngFor="let sub of postsService.subscriptions" [value]="sub.id">{{sub.name}}</mat-option>
@for (sub of postsService.subscriptions; track sub) {
<mat-option [value]="sub.id">{{sub.name}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field style="width: 100px; margin-left: 10px">
@@ -134,10 +130,15 @@
<mat-option [value]="'audio'" i18n="Audio">Audio</mat-option>
</mat-select>
</mat-form-field>
<button style="margin-left: 10px" [disabled]="uploading_archive || uploaded_archive" (click)="importArchive()" matTooltip="Upload" i18n-matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon><mat-spinner *ngIf="uploading_archive" class="spinner" [diameter]="38"></mat-spinner></button>
<button style="margin-left: 10px" [disabled]="uploading_archive || uploaded_archive" (click)="importArchive()" matTooltip="Upload" i18n-matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon>
@if (uploading_archive) {
<mat-spinner class="spinner" [diameter]="38"></mat-spinner>
}
</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>

View File

@@ -8,6 +8,7 @@ import { Archive } from 'api-types/models/Archive';
import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component';
import { PostsService } from 'app/posts.services';
import { NgxFileDropEntry } from 'ngx-file-drop';
import { saveAs } from 'file-saver';
@Component({
selector: 'app-archive-viewer',

View File

@@ -1,6 +1,14 @@
<div class="buttons-container">
<button (click)="startWatching()" *ngIf="!watch_together_clicked" mat-flat-button>Watch together</button>
<button (click)="startServer()" *ngIf="watch_together_clicked && !started && server_mode && server_already_exists === false" mat-flat-button>Start stream</button>
<button (click)="startClient()" *ngIf="watch_together_clicked && !started && server_already_exists === true" mat-flat-button>Join stream</button>
<button style="margin-left: 10px;" (click)="stop()" *ngIf="watch_together_clicked" mat-flat-button>Stop</button>
@if (!watch_together_clicked) {
<button (click)="startWatching()" mat-flat-button>Watch together</button>
} @else {
@if (!started) {
@if (server_already_exists) {
<button (click)="startClient()" mat-flat-button>Join stream</button>
} @else if (server_mode) {
<button (click)="startServer()" mat-flat-button>Start stream</button>
}
}
<button style="margin-left: 10px;" (click)="stop()" mat-flat-button>Stop</button>
}
</div>

View File

@@ -1,13 +1,18 @@
<div *ngIf="playlists && playlists.length > 0">
@if (playlists) {
<div>
<div class="container">
<div class="row justify-content-center">
<div *ngFor="let playlist of playlists; let i = index" class="mb-2 mt-2" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
@for (playlist of playlists; track playlist; let i = $index) {
<div class="mb-2 mt-2" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToPlaylist($event)" [file_obj]="playlist" [is_playlist]="true" (editPlaylist)="editPlaylistDialog($event)" (deleteFile)="deletePlaylist($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? this.postsService.token : ''" [loading]="false"></app-unified-file-card>
</div>
</div>
</div>
</div>
<div *ngIf="playlists && playlists.length === 0" style="text-align: center;">
} @empty {
<div style="text-align: center;">
No playlists available. Create one from your downloading files by clicking the blue plus button.
</div>
}
</div>
</div>
</div>
}
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog()" mat-fab><mat-icon>add</mat-icon></button></div>

View File

@@ -4,6 +4,7 @@ import { Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component';
import { Playlist } from 'api-types';
import { saveAs } from 'file-saver';
@Component({
selector: 'app-custom-playlists',

View File

@@ -1,13 +1,11 @@
<div [hidden]="!(downloads && downloads.length > 0)">
<div style="overflow: hidden;" [ngClass]="uids ? 'rounded mat-elevation-z2' : 'mat-elevation-z8'">
<mat-table style="overflow: hidden" [ngClass]="uids ? 'rounded-top' : null" matSort [dataSource]="dataSource">
<!-- Date Column -->
<ng-container matColumnDef="timestamp_start">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Date">Date</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.timestamp_start | date: 'short'}} </mat-cell>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header style="flex: 2"> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
@@ -17,81 +15,88 @@
</span>
</mat-cell>
</ng-container>
<!-- Subscription Column -->
<ng-container matColumnDef="sub_name">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Subscription">Subscription</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element">
<ng-container *ngIf="element.sub_name">
@if (element.sub_name) {
{{element.sub_name}}
</ng-container>
<ng-container *ngIf="!element.sub_name">
N/A
</ng-container>
} @else {
<ng-container i18n="N/A">N/A</ng-container>
}
</mat-cell>
</ng-container>
<!-- Progress Column -->
<ng-container matColumnDef="percent_complete">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Progress">Progress</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element">
<ng-container *ngIf="!element.error && element.step_index !== 2">
@if (!element.error) {
@if (element.step_index !== 2) {
{{STEP_INDEX_TO_LABEL[element.step_index]}}
</ng-container>
<ng-container *ngIf="!element.error && element.step_index === 2">
<ng-container *ngIf="element.percent_complete">
} @else {
@if (element.percent_complete) {
{{+(element.percent_complete) > 100 ? '100' : element.percent_complete}}%
</ng-container>
<ng-container *ngIf="!element.percent_complete">
N/A
</ng-container>
</ng-container>
<ng-container *ngIf="element.error" i18n="Error">Error</ng-container>
} @else {
<ng-container i18n="N/A">N/A</ng-container>
}
}
} @else {
<ng-container i18n="Error">Error</ng-container>
}
</mat-cell>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef [ngStyle]="{flex: actionsFlex}"> <ng-container i18n="Actions">Actions</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element" [ngStyle]="{flex: actionsFlex}">
<div *ngIf="!minimizeButtons">
<ng-container *ngFor="let downloadAction of downloadActions">
@if (!minimizeButtons) {
<div>
@for (downloadAction of downloadActions; track downloadAction) {
<span class="button-span">
<mat-spinner [diameter]="28" *ngIf="downloadAction.loading && downloadAction.loading(element)" class="icon-button-spinner"></mat-spinner>
<button *ngIf="downloadAction.show(element)" (click)="downloadAction.action(element)" [disabled]="downloadAction.loading && downloadAction.loading(element)" [matTooltip]="downloadAction.tooltip" mat-icon-button><mat-icon>{{downloadAction.icon}}</mat-icon></button>
@if (downloadAction.loading && downloadAction.loading(element)) {
<mat-spinner [diameter]="28" class="icon-button-spinner"></mat-spinner>
}
@if (downloadAction.show(element)) {
<button (click)="downloadAction.action(element)" [disabled]="downloadAction.loading && downloadAction.loading(element)" [matTooltip]="downloadAction.tooltip" mat-icon-button><mat-icon>{{downloadAction.icon}}</mat-icon></button>
}
</span>
</ng-container>
}
</div>
<div *ngIf="minimizeButtons">
} @else {
<div>
<button [matMenuTriggerFor]="download_actions" mat-icon-button><mat-icon>more_vert</mat-icon></button>
<mat-menu #download_actions="matMenu">
<ng-container *ngFor="let downloadAction of downloadActions">
<button *ngIf="downloadAction.show(element)" (click)="downloadAction.action(element)" [disabled]="downloadAction.loading && downloadAction.loading(element)" mat-menu-item>
@for (downloadAction of downloadActions; track downloadAction) {
@if (downloadAction.show(element)) {
<button (click)="downloadAction.action(element)" [disabled]="downloadAction.loading && downloadAction.loading(element)" mat-menu-item>
<mat-icon>{{downloadAction.icon}}</mat-icon>
<span>{{downloadAction.tooltip}}</span>
</button>
</ng-container>
}
}
</mat-menu>
</div>
}
</mat-cell>
</ng-container>
<mat-header-row [ngClass]="uids ? 'rounded-top' : null" *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-paginator [ngClass]="uids ? 'rounded-bottom' : null" [pageSizeOptions]="[5, 10, 20]"
showFirstLastButtons
aria-label="Select page of downloads">
</mat-paginator>
</div>
<div *ngIf="!uids" class="downloads-action-button-div">
@if (!uids) {
<div class="downloads-action-button-div">
<button class="downloads-action-button" [disabled]="!running_download_exists" mat-stroked-button (click)="pauseAllDownloads()"><ng-container i18n="Pause all downloads">Pause all downloads</ng-container></button>
<button class="downloads-action-button" [disabled]="!paused_download_exists" mat-stroked-button (click)="resumeAllDownloads()"><ng-container i18n="Resume all downloads">Resume all downloads</ng-container></button>
<button class="downloads-action-button" color="warn" mat-stroked-button (click)="clearDownloadsByType()"><ng-container i18n="Clear downloads">Clear downloads</ng-container></button>
</div>
}
</div>
<div *ngIf="(!downloads || downloads.length === 0) && downloads_retrieved && !uids">
@if ((!downloads || downloads.length === 0) && downloads_retrieved && !uids) {
<div>
<h4 style="text-align: center; margin-top: 10px;" i18n="No downloads label">No downloads available!</h4>
</div>
}

View File

@@ -14,7 +14,8 @@
</mat-form-field>
</div>
</mat-tab>
<mat-tab *ngIf="registrationEnabled" label="Register" i18n-label="Register">
@if (registrationEnabled) {
<mat-tab label="Register" i18n-label="Register">
<div style="margin-top: 10px;">
<mat-form-field style="width: 100%">
<mat-label i18n="User name">User name</mat-label>
@@ -34,13 +35,21 @@
</mat-form-field>
</div>
</mat-tab>
}
</mat-tab-group>
<div *ngIf="selectedTabIndex === 0" class="login-button-div">
@if (selectedTabIndex === 0) {
<div class="login-button-div">
<button [disabled]="loggingIn" color="primary" (click)="login()" mat-raised-button><ng-container i18n="Login">Login</ng-container></button>
<mat-progress-bar *ngIf="loggingIn" class="login-progress-bar" mode="indeterminate"></mat-progress-bar>
@if (loggingIn) {
<mat-progress-bar class="login-progress-bar" mode="indeterminate"></mat-progress-bar>
}
</div>
<div *ngIf="selectedTabIndex === 1" class="login-button-div">
} @else {
<div class="login-button-div">
<button [disabled]="registering" color="primary" (click)="register()" mat-raised-button><ng-container i18n="Register">Register</ng-container></button>
<mat-progress-bar *ngIf="registering" class="login-progress-bar" mode="indeterminate"></mat-progress-bar>
@if (registering) {
<mat-progress-bar class="login-progress-bar" mode="indeterminate"></mat-progress-bar>
}
</div>
}
</mat-card>

View File

@@ -1,21 +1,23 @@
<div style="height: 100%;">
<div *ngIf="logs_loading" style="z-index: 999; position: absolute; top: 40%; left: 50%">
@if (logs_loading) {
<div style="z-index: 999; position: absolute; top: 40%; left: 50%">
<mat-spinner [diameter]="32"></mat-spinner>
</div>
}
<!-- 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: 100%; overflow-y: auto">
<div *ngFor="let log of logs; let i = index" class="example-item">
@for (log of logs; track log) {
<div class="example-item">
<span [ngStyle]="{'color':log.color}">{{log.text}}</span>
</div>
}
</div>
<div>
<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="display: inline-block;">
@@ -33,5 +35,4 @@
<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>

View File

@@ -1,7 +1,8 @@
<h4 *ngIf="role" mat-dialog-title><ng-container i18n="Manage role dialog title">Manage role</ng-container>&nbsp;-&nbsp;{{role.key}}</h4>
<mat-dialog-content *ngIf="role">
<div *ngFor="let permission of available_permissions">
@if (role) {
<h4 mat-dialog-title><ng-container i18n="Manage role dialog title">Manage role</ng-container>&nbsp;-&nbsp;{{role.key}}</h4>
<mat-dialog-content>
@for (permission of available_permissions; track permission) {
<div>
<div matListItemTitle>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</div>
<div matListItemLine>
<mat-radio-group [disabled]="permission === 'settings' && role.key === 'admin'" (change)="changeRolePermissions($event, permission)" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give role permission for ' + permission">
@@ -10,8 +11,9 @@
</mat-radio-group>
</div>
</div>
}
</mat-dialog-content>
}
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
</mat-dialog-actions>

View File

@@ -1,8 +1,7 @@
<h4 *ngIf="user" mat-dialog-title><ng-container i18n="Manage user dialog title">Manage user</ng-container>&nbsp;-&nbsp;{{user.name}}</h4>
<mat-dialog-content *ngIf="user">
@if (user) {
<h4 mat-dialog-title><ng-container i18n="Manage user dialog title">Manage user</ng-container>&nbsp;-&nbsp;{{user.name}}</h4>
<mat-dialog-content>
<p><ng-container i18n="User UID">User UID:</ng-container>&nbsp;{{user.uid}}</p>
<div>
<mat-form-field style="margin-right: 15px;">
<mat-label i18n="New password">New password</mat-label>
@@ -10,9 +9,9 @@
</mat-form-field>
<button mat-raised-button color="accent" (click)="setNewPassword()" [disabled]="newPasswordInput.length === 0"><ng-container i18n="Set new password">Set new password</ng-container></button>
</div>
<div>
<div *ngFor="let permission of available_permissions">
@for (permission of available_permissions; track permission) {
<div>
<div matListItemTitle>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</div>
<div matListItemLine>
<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">
@@ -22,9 +21,10 @@
</mat-radio-group>
</div>
</div>
}
</div>
</mat-dialog-content>
}
<mat-dialog-actions>
<button style="margin-bottom: 5px;" mat-stroked-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
</mat-dialog-actions>

View File

@@ -1,4 +1,5 @@
<div *ngIf="dataSource; else loading">
@if (dataSource) {
<div>
<div style="padding: 15px">
<div class="row">
<div class="table table-responsive pb-4 pt-4">
@@ -8,34 +9,31 @@
<input matInput (keyup)="applyFilter($event)">
</mat-form-field>
</div>
<div class="mat-elevation-z8" style="margin-right: 15px;">
<mat-table #table [dataSource]="dataSource" matSort>
<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Username users table header"> User name </ng-container></mat-header-cell>
<mat-cell *matCellDef="let row">
<span *ngIf="editObject && editObject.uid === row.uid; else noteditingname">
@if (editObject && editObject.uid === row.uid) {
<span>
<span style="width: 80%;">
<mat-form-field>
<input matInput [(ngModel)]="constructedObject['name']" type="text" style="font-size: 12px">
</mat-form-field>
</span>
</span>
<ng-template #noteditingname>
} @else {
{{row.name}}
</ng-template>
}
</mat-cell>
</ng-container>
<!-- Email Column -->
<ng-container matColumnDef="role">
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Role users table header"> Role </ng-container></mat-header-cell>
<mat-cell *matCellDef="let row">
<span *ngIf="editObject && editObject.uid === row.uid; else noteditingemail">
@if (editObject && editObject.uid === row.uid) {
<span>
<span style="width: 80%;">
<mat-form-field>
<mat-select [(ngModel)]="constructedObject['role']">
@@ -45,17 +43,17 @@
</mat-form-field>
</span>
</span>
<ng-template #noteditingemail>
} @else {
{{row.role}}
</ng-template>
}
</mat-cell>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<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">
<span *ngIf="editObject && editObject.uid === row.uid; else notediting">
@if (editObject && editObject.uid === row.uid) {
<span>
<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>
</button>
@@ -63,11 +61,11 @@
<mat-icon>cancel</mat-icon>
</button>
</span>
<ng-template #notediting>
} @else {
<button mat-icon-button (click)="enableEditMode(row.uid)" matTooltip="Edit user" i18n-matTooltip="edit user action button tooltip">
<mat-icon>edit</mat-icon>
</button>
</ng-template>
}
<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>
</button>
@@ -76,17 +74,14 @@
</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;">
</mat-row>
</mat-table>
<mat-paginator #paginator [length]="length"
[pageSize]="pageSize"
[pageSizeOptions]="pageSizeOptions">
</mat-paginator>
<button color="primary" [disabled]="!this.users" mat-raised-button (click)="openAddUserDialog()" style="float: left; top: -45px; left: 15px">
<ng-container i18n="Add users button">Add Users</ng-container>
</button>
@@ -95,14 +90,14 @@
</div>
<button color="primary" [matMenuTriggerFor]="edit_roles_menu" class="edit-role" mat-raised-button><ng-container i18n="Edit role">Edit Role</ng-container></button>
<mat-menu #edit_roles_menu="matMenu">
<button (click)="openModifyRole(role)" mat-menu-item *ngFor="let role of roles">{{role.key}}</button>
@for (role of roles; track role) {
<button (click)="openModifyRole(role)" mat-menu-item>{{role.key}}</button>
}
</mat-menu>
</div>
</div>
} @else {
<div style="position: absolute" class="centered">
<ng-template #loading>
<mat-spinner></mat-spinner>
</ng-template>
</div>
}

View File

@@ -8,25 +8,31 @@
</div>
</mat-card-subtitle>
<mat-card-title>
<ng-container *ngIf="NOTIFICATION_PREFIX[notification.type]">
@if (NOTIFICATION_PREFIX[notification.type]) {
{{NOTIFICATION_PREFIX[notification.type]}}
</ng-container>
}
</mat-card-title>
</mat-card-header>
<mat-card-content>
<ng-container *ngIf="NOTIFICATION_SUFFIX_KEY[notification.type]">
@if (NOTIFICATION_SUFFIX_KEY[notification.type]) {
<div style="word-break: break-word">
{{notification['data'][NOTIFICATION_SUFFIX_KEY[notification.type]]}}
</div>
</ng-container>
}
</mat-card-content>
<mat-card-actions class="notification-actions" *ngIf="notification.actions?.length > 0">
@if (notification.actions?.length > 0) {
<mat-card-actions class="notification-actions">
<button matTooltip="Remove" i18n-matTooltip="Remove" (click)="emitDeleteNotification(notification.uid)" mat-icon-button><mat-icon>close</mat-icon></button>
<span *ngFor="let action of notification.actions">
@for (action of notification.actions; track action) {
<span>
<button [matTooltip]="NOTIFICATION_ACTION_TO_STRING[action]" (click)="emitNotificationAction(notification, action)" mat-icon-button><mat-icon>{{NOTIFICATION_ICON[action]}}</mat-icon></button>
</span>
}
</mat-card-actions>
<span *ngIf="!notification.read" class="dot"></span>
}
@if (!notification.read) {
<span class="dot"></span>
}
</mat-card>
</div>
</cdk-virtual-scroll-viewport>

View File

@@ -1,10 +1,16 @@
<div *ngIf="notifications !== null && notifications.length === 0" style="text-align: center; margin: 10px;" i18n="No notifications available">No notifications available</div>
<div *ngIf="notifications?.length > 0">
@if (notifications !== null && notifications.length === 0) {
<div style="text-align: center; margin: 10px;" i18n="No notifications available">No notifications available</div>
}
@if (notifications?.length > 0) {
<div>
<div class="notifications-list-parent">
<mat-chip-listbox [value]="selectedFilters" [multiple]="true" (change)="selectedFiltersChanged($event)">
<mat-chip-option *ngFor="let filter of notificationFilters | keyvalue: originalOrder" [value]="filter.key" [selected]="selectedFilters.includes(filter.key)" color="accent">{{filter.value.label}}</mat-chip-option>
@for (filter of notificationFilters | keyvalue: originalOrder; track filter) {
<mat-chip-option [value]="filter.key" [selected]="selectedFilters.includes(filter.key)" color="accent">{{filter.value.label}}</mat-chip-option>
}
</mat-chip-listbox>
<app-notifications-list class="notifications-list" [style.height]="list_height" (notificationAction)="notificationAction($event)" (deleteNotification)="deleteNotification($event)" [notifications]="filtered_notifications"></app-notifications-list>
</div>
<button style="margin: 10px 0px 2px 10px;" *ngIf="notifications?.length > 0" color="warn" (click)="deleteAllNotifications()" mat-stroked-button>Remove all</button>
<button style="margin: 10px 0px 2px 10px;" color="warn" (click)="deleteAllNotifications()" mat-stroked-button>Remove all</button>
</div>
}

View File

@@ -6,8 +6,11 @@
</div>
<!-- Files title -->
<div class="col-12 order-1 col-sm-4 order-sm-2 d-flex justify-content-center">
<h4 *ngIf="!customHeader" class="my-videos-title" i18n="My files title">My files</h4>
<h4 *ngIf="customHeader" class="my-videos-title">{{customHeader}}</h4>
@if (!customHeader) {
<h4 class="my-videos-title" i18n="My files title">My files</h4>
} @else {
<h4 class="my-videos-title">{{customHeader}}</h4>
}
</div>
<!-- Search -->
<div class="col-12 order-3 col-sm-4 order-sm-3 d-flex justify-content-center">
@@ -21,57 +24,77 @@
<!-- Filters -->
<div class="row justify-content-center">
<mat-chip-listbox class="filter-list" [value]="selectedFilters" [multiple]="true" (change)="selectedFiltersChanged($event)">
<mat-chip-option *ngFor="let filter of fileFilters | keyvalue: originalOrder" [value]="filter.key" [selected]="selectedFilters.includes(filter.key)" color="accent">{{filter.value.label}}</mat-chip-option>
@for (filter of fileFilters | keyvalue: originalOrder; track filter) {
<mat-chip-option [value]="filter.key" [selected]="selectedFilters.includes(filter.key)" color="accent">{{filter.value.label}}</mat-chip-option>
}
</mat-chip-listbox>
</div>
</div>
<div>
<!-- Files -->
<div *ngIf="!selectMode" class="container" style="margin-bottom: 16px">
@if (!selectMode) {
<div class="container" style="margin-bottom: 16px">
<div class="row justify-content-center">
<!-- Real cards -->
<ng-container *ngIf="normal_files_received && paged_data">
<div style="display: flex; align-items: center;" *ngFor="let file of paged_data; let i = index" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
@if (normal_files_received && paged_data) {
@for (file of paged_data; track file; let i = $index) {
<div style="display: flex; align-items: center;" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
<app-unified-file-card [ngClass]="downloading_content[file.uid] ? 'blurred' : ''" [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" (toggleFavorite)="toggleFavorite($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [availablePlaylists]="playlists" (addFileToPlaylist)="addFileToPlaylist($event)" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? this.postsService.token : ''"></app-unified-file-card>
<mat-spinner *ngIf="downloading_content[file.uid]" class="downloading-spinner" [diameter]="32"></mat-spinner>
@if (downloading_content[file.uid]) {
<mat-spinner class="downloading-spinner" [diameter]="32"></mat-spinner>
}
</div>
<div *ngIf="paged_data.length === 0">
} @empty {
<div>
<ng-container i18n="No files found">No files found.</ng-container>
</div>
</ng-container>
}
}
<!-- Fake cards -->
<ng-container>
<div *ngFor="let file of loading_files; let i = index" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[normal_files_received ? 'hide' : '', postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
@for (file of loading_files; track file; let i = $index) {
<div class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[normal_files_received ? 'hide' : '', postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" [loading]="true" [theme]="postsService.theme"></app-unified-file-card>
</div>
}
</ng-container>
</div>
</div>
<div *ngIf="selectMode">
} @else {
<div>
<!-- If selected files e.g. for creating a playlist -->
<mat-tab-group [(selectedIndex)]="selectedIndex">
<mat-tab label="Order" i18n-label="Order">
<div *ngIf="selected_data.length">
<span *ngIf="reverse_order === false" i18n="Normal order">Normal order&nbsp;</span>
<span *ngIf="reverse_order === true" i18n="Reverse order">Reverse order&nbsp;</span>
@if (selected_data.length) {
<div>
@if (reverse_order === false) {
<span i18n="Normal order">Normal order&nbsp;</span>
}
@if (reverse_order === true) {
<span i18n="Reverse order">Reverse order&nbsp;</span>
}
<button (click)="toggleSelectionOrder()" mat-icon-button><mat-icon>{{!reverse_order ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
</div>
}
<!-- Selection order -->
<mat-button-toggle-group *ngIf="selected_data.length" class="media-list" cdkDropList (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical #group="matButtonToggleGroup">
@if (selected_data.length) {
<mat-button-toggle-group class="media-list" cdkDropList (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical #group="matButtonToggleGroup">
<!-- The following for loop can be optimized but it requires a pipe https://stackoverflow.com/a/35703364/8088021 -->
<mat-button-toggle class="media-box" cdkDrag *ngFor="let file of (reverse_order ? selected_data_objs.slice().reverse() : selected_data_objs); let i = index" [checked]="false"><div><div class="playlist-item-text">{{file.title}}</div> <button (click)="removeSelectedFile(i)" class="remove-item-button" mat-icon-button><mat-icon>cancel</mat-icon></button></div></mat-button-toggle>
@for (file of (reverse_order ? selected_data_objs.slice().reverse() : selected_data_objs); track file; let i = $index) {
<mat-button-toggle class="media-box" cdkDrag [checked]="false"><div><div class="playlist-item-text">{{file.title}}</div> <button (click)="removeSelectedFile(i)" class="remove-item-button" mat-icon-button><mat-icon>cancel</mat-icon></button></div></mat-button-toggle>
}
</mat-button-toggle-group>
<div style="margin-top: 20px;" *ngIf="!selected_data.length">
} @else {
<div style="margin-top: 20px;">
<h4 style="text-align: center;">No files selected!</h4>
</div>
}
</mat-tab>
<mat-tab label="Select files" i18n-label="Select files">
<mat-selection-list *ngIf="normal_files_received" (selectionChange)="fileSelectionChanged($event)">
<mat-list-option [selected]="selected_data.includes(file.uid)" *ngFor="let file of paged_data" [value]="file">
@if (normal_files_received) {
<mat-selection-list (selectionChange)="fileSelectionChanged($event)">
@for (file of paged_data; track file) {
<mat-list-option [selected]="selected_data.includes(file.uid)" [value]="file">
<div class="container">
<div class="row justify-content-center">
<div class="col-10 select-file-title">
@@ -81,25 +104,31 @@
<div class="col-2">{{file.registered | date:'shortDate'}}</div>
</div>
</div>
</mat-list-option>
}
</mat-selection-list>
<ng-container *ngIf="!normal_files_received && loading_files && loading_files.length > 0">
<mat-selection-list *ngIf="!normal_files_received">
<mat-list-option *ngFor="let file of paged_data">
}
@if (!normal_files_received && loading_files && loading_files.length > 0) {
@if (!normal_files_received) {
<mat-selection-list>
@for (file of paged_data; track file) {
<mat-list-option>
<content-loader class="list-ghosts" [backgroundColor]="postsService.theme.ghost_primary" [foregroundColor]="postsService.theme.ghost_secondary" viewBox="0 0 250 8"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="8" /></content-loader>
</mat-list-option>
}
</mat-selection-list>
</ng-container>
}
}
</mat-tab>
</mat-tab-group>
</div>
<div style="position: relative;" *ngIf="usePaginator && selectedIndex > 0">
}
@if (usePaginator && selectedIndex > 0) {
<div style="position: relative;">
<mat-paginator class="paginator" #paginator (page)="pageChangeEvent($event)" [length]="file_count"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 25, 100, this.paged_data && this.paged_data.length > 100 ? this.paged_data.length : 250]">
</mat-paginator>
</div>
}
</div>

View File

@@ -8,6 +8,7 @@ import { distinctUntilChanged } from 'rxjs/operators';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { MatChipListboxChange } from '@angular/material/chips';
import { MatSelectionListChange } from '@angular/material/list';
import { saveAs } from 'file-saver';
@Component({
selector: 'app-recent-videos',
@@ -380,8 +381,8 @@ export class RecentVideosComponent implements OnInit {
fileSelectionChanged(event: MatSelectionListChange): void {
// TODO: make sure below line is possible (_selected is private)
const adding = event.option['_selected'];
const value = event.option.value;
const adding = event.options['_selected'];
const value = adding.value;
if (adding) {
this.selected_data.push(value.uid);
this.selected_data_objs.push(value);

View File

@@ -1,11 +1,14 @@
<span class="text" [ngStyle]="{'-webkit-line-clamp': !see_more_active ? line_limit : null}" [innerHTML]="text | linkify"></span>
<span>
<a [routerLink]="[]" (click)="toggleSeeMore()">
<ng-container *ngIf="!see_more_active" i18n="See more">
@if (!see_more_active) {
<ng-container i18n="See more">
See more.
</ng-container>
<ng-container *ngIf="see_more_active" i18n="See less">
} @else {
<ng-container i18n="See less">
See less.
</ng-container>
}
</a>
</span>

View File

@@ -1 +1,3 @@
<button *ngIf="show_skip_ad_button" (click)="skipAdButtonClicked()" mat-flat-button><ng-container i18n="Skip ad button">Skip ad</ng-container></button>
@if (show_skip_ad_button) {
<button (click)="skipAdButtonClicked()" mat-flat-button><ng-container i18n="Skip ad button">Skip ad</ng-container></button>
}

View File

@@ -2,9 +2,11 @@
<div style="display: inline-block;">
<mat-form-field appearance="outline" style="width: 165px;">
<mat-select [(ngModel)]="this.sortProperty" (selectionChange)="emitSortOptionChanged()">
<mat-option *ngFor="let sortOption of sortProperties | keyvalue" [value]="sortOption.key">
@for (sortOption of sortProperties | keyvalue; track sortOption) {
<mat-option [value]="sortOption.key">
{{sortOption['value']['label']}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>

View File

@@ -1,7 +1,7 @@
<h4 mat-dialog-title><ng-container i18n="Task settings">Task settings - {{task.title}}</ng-container></h4>
<mat-dialog-content>
<div *ngIf="task_key === 'delete_old_files'">
@if (task_key === 'delete_old_files') {
<div>
<mat-form-field color="accent">
<mat-label i18n="Delete files older than">Delete files older than</mat-label>
<input [(ngModel)]="new_options['threshold_days']" matInput onlyNumber required>
@@ -14,16 +14,18 @@
<mat-checkbox [disabled]="new_options['blacklist_files']" [(ngModel)]="new_options['blacklist_subscription_files']" i18n="Blacklist deleted subscription files" placeholder="Archive mode must be enabled" placeholder-i18n>Blacklist deleted subscription files</mat-checkbox>
</div>
</div>
}
<div>
<mat-checkbox [(ngModel)]="new_options['auto_confirm']" i18n="Do not ask for confirmation">Do not ask for confirmation</mat-checkbox>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>
<ng-container *ngIf="optionsChanged()" i18n="Task settings cancel button">Cancel</ng-container>
<ng-container *ngIf="!optionsChanged()" i18n="Task settings close button">Close</ng-container>
@if (optionsChanged()) {
<ng-container i18n="Task settings cancel button">Cancel</ng-container>
} @else {
<ng-container i18n="Task settings close button">Close</ng-container>
}
</button>
<button mat-button [disabled]="!optionsChanged()" (click)="saveSettings()"><ng-container i18n="Save button">Save</ng-container></button>
</mat-dialog-actions>

View File

@@ -10,64 +10,74 @@
</span>
</mat-cell>
</ng-container>
<!-- Last Ran Column -->
<ng-container matColumnDef="last_ran">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Last ran">Last ran</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element">
<ng-container *ngIf="element.last_ran">{{element.last_ran*1000 | date: 'short'}}</ng-container>
<ng-container i18n="N/A" *ngIf="!element.last_ran">N/A</ng-container>
@if (element.last_ran) {
{{element.last_ran*1000 | date: 'short'}}
} @else {
<ng-container i18n="N/A">N/A</ng-container>
}
</mat-cell>
</ng-container>
<!-- Last Confirmed Column -->
<ng-container matColumnDef="last_confirmed">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Last confirmed">Last confirmed</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element">
<ng-container *ngIf="element.last_confirmed">{{element.last_confirmed*1000 | date: 'short'}}</ng-container>
<ng-container i18n="N/A" *ngIf="!element.last_confirmed">N/A</ng-container>
@if (element.last_confirmed) {
{{element.last_confirmed*1000 | date: 'short'}}
} @else {
<ng-container i18n="N/A">N/A</ng-container>
}
</mat-cell>
</ng-container>
<!-- Status Column -->
<ng-container matColumnDef="status">
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Status">Status</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element">
<span *ngIf="element.running || element.confirming"><mat-spinner matTooltip="Busy" i18n-matTooltip="Busy" [diameter]="25"></mat-spinner></span>
<span *ngIf="!(element.running || element.confirming) && element.schedule" style="display: flex">
@if (element.running || element.confirming) {
<span><mat-spinner matTooltip="Busy" i18n-matTooltip="Busy" [diameter]="25"></mat-spinner></span>
} @else if (element.schedule) {
<span style="display: flex">
<ng-container i18n="Scheduled">Scheduled for</ng-container>
{{element.next_invocation | date: 'short'}}<mat-icon style="font-size: 16px; display: inline-flex; align-items: center; padding-left: 5px; padding-bottom: 6px;" *ngIf="element.schedule.type === 'recurring'">repeat</mat-icon>
{{element.next_invocation | date: 'short'}}
@if (element.schedule.type === 'recurring') {
<mat-icon style="font-size: 16px; display: inline-flex; align-items: center; padding-left: 5px; padding-bottom: 6px;">repeat</mat-icon>
}
</span>
<span *ngIf="!(element.running || element.confirming) && !element.schedule">
} @else {
<span>
<ng-container i18n="Not scheduled">Not scheduled</ng-container>
</span>
}
</mat-cell>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Actions">Actions</ng-container> </mat-header-cell>
<mat-cell *matCellDef="let element">
<div class="container">
<div class="row justify-content-start">
<div *ngIf="element.data?.uids?.length > 0 || (!element.data?.uids && element.data)" class="col-12 mt-2" style="display: flex; justify-content: center;">
@if (element.data?.uids?.length > 0 || (!element.data?.uids && element.data)) {
<div class="col-12 mt-2" style="display: flex; justify-content: center;">
<ng-container>
<button (click)="confirmTask(element.key)" [disabled]="element.running || element.confirming" mat-stroked-button>
<ng-container *ngIf="element.key == 'missing_files_check'">
@switch(element.key) {
@case ('missing_files_check') {
<ng-container i18n="Clear missing files from DB">Clear missing files from DB:</ng-container>{{element.data.uids.length}}
</ng-container>
<ng-container *ngIf="element.key == 'duplicate_files_check'">
} @case ('duplicate_files_check') {
<ng-container i18n="Clear duplicate files from DB">Clear duplicate files from DB:</ng-container>&nbsp;{{element.data.uids.length}}
</ng-container>
<ng-container *ngIf="element.key == 'youtubedl_update_check'">
} @case ('youtubedl_update_check') {
<ng-container i18n="Update binary to">Update binary to:</ng-container>&nbsp;{{element.data}}
</ng-container>
<ng-container *ngIf="element.key == 'delete_old_files'">
} @case ('delete_old_files') {
<ng-container i18n="Delete old files">Delete old files:</ng-container>&nbsp;{{element.data.files_to_remove.length}}
</ng-container>
}
}
</button>
</ng-container>
</div>
}
<div class="col-3">
<button (click)="runTask(element.key)" [disabled]="element.running || element.confirming" mat-icon-button matTooltip="Run" i18n-matTooltip="Run"><mat-icon>play_arrow</mat-icon></button>
</div>
@@ -77,28 +87,28 @@
<div class="col-3">
<button (click)="openTaskSettings(element)" mat-icon-button matTooltip="Settings" i18n-matTooltip="Settings"><mat-icon>settings</mat-icon></button>
</div>
<div *ngIf="element.error" class="col-3">
@if (element.error) {
<div class="col-3">
<button (click)="showError(element)" mat-icon-button matTooltip="Show error" i18n-matTooltip="Show error"><mat-icon>warning</mat-icon></button>
</div>
}
</div>
</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-paginator [pageSizeOptions]="[10, 20]"
showFirstLastButtons
aria-label="Select page of tasks">
</mat-paginator>
</div>
<button style="margin-top: 10px; margin-left: 5px;" mat-stroked-button (click)="openRestoreDBBackupDialog()" i18n="Restore DB from backup button">Restore DB from backup</button>
<button style="margin-top: 10px; margin-left: 5px;" mat-stroked-button (click)="resetTasks()" color="warn" i18n="Reset tasks button">Reset tasks</button>
</div>
<div *ngIf="(!tasks || tasks.length === 0) && tasks_retrieved">
@if ((!tasks || tasks.length === 0) && tasks_retrieved) {
<div>
<h4 style="text-align: center; margin-top: 10px;" i18n="No tasks label">No tasks available!</h4>
</div>
}

View File

@@ -1,12 +1,17 @@
<div class="chat-container" #scrollContainer *ngIf="visible_chat">
@if (visible_chat) {
<div class="chat-container" #scrollContainer>
<div style="width: 250px; text-align: center;"><strong>Twitch Chat</strong></div>
<div #chat style="max-width: 250px" *ngFor="let chat of visible_chat; let last = last">
@for (chat of visible_chat; track chat; let last = $last) {
<div #chat style="max-width: 250px">
{{chat.timestamp_str}} - <strong [style.color]="chat.user_color ? chat.user_color : null">{{chat.name}}</strong>: {{chat.message}}
{{last ? scrollToBottom() : ''}}
</div>
}
</div>
<ng-container *ngIf="chat_response_received && !full_chat">
}
@if (chat_response_received && !full_chat) {
<button [disabled]="downloading_chat" (click)="downloadTwitchChat()" class="download-button" mat-raised-button color="accent"><ng-container i18n="Download Twitch Chat button">Download Twitch Chat</ng-container></button>
<mat-spinner *ngIf="downloading_chat" class="downloading-spinner" [diameter]="30"></mat-spinner>
</ng-container>
@if (downloading_chat) {
<mat-spinner class="downloading-spinner" [diameter]="30"></mat-spinner>
}
}

View File

@@ -1,73 +1,103 @@
<div (mouseenter)="onMouseOver()" (mouseleave)="onMouseOut()" (contextmenu)="onRightClick($event)" style="position: relative; width: fit-content;">
<div *ngIf="!loading" class="download-time">
@if (!loading) {
<div class="download-time">
<mat-icon class="audio-video-icon">{{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}}</mat-icon>
&nbsp;&nbsp;
<ng-container i18n="Auto-generated label" *ngIf="file_obj.auto">Auto-generated</ng-container>
<ng-container *ngIf="!file_obj.auto">{{file_obj.registered | date:'shortDate' : undefined : locale.ngID}}</ng-container>
@if (file_obj.auto) {
<ng-container i18n="Auto-generated label">Auto-generated</ng-container>
}
@else {
{{file_obj.registered | date:'shortDate' : undefined : locale.ngID}}
}
</div>
<div *ngIf="loading" class="download-time" style="width: 75%; margin-top: 5px;"><content-loader [backgroundColor]="theme.ghost_primary" [foregroundColor]="theme.ghost_secondary" viewBox="0 0 250 30"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="30" /></content-loader></div>
}
@else {
<div class="download-time" style="width: 75%; margin-top: 5px;"><content-loader [backgroundColor]="theme.ghost_primary" [foregroundColor]="theme.ghost_secondary" viewBox="0 0 250 30"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="30" /></content-loader></div>
}
<!-- The context menu trigger must be kept above the "more info" menu -->
<div style="visibility: hidden; position: fixed"
[style.left]="contextMenuPosition.x"
[style.top]="contextMenuPosition.y"
[matMenuTriggerFor]="context_menu">
</div>
<button *ngIf="!file_obj || !file_obj.auto" [disabled]="loading" [matMenuTriggerFor]="action_menu" class="menuButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
@if (!file_obj || !file_obj.auto) {
<button [disabled]="loading" [matMenuTriggerFor]="action_menu" class="menuButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
}
<mat-menu #context_menu>
<ng-container *ngIf="!loading">
@if (!loading) {
<button (click)="navigateToFile($event)" mat-menu-item><mat-icon>open_in_browser</mat-icon><ng-container i18n="Open file button">Open file</ng-container></button>
<button (click)="navigateToFile({ctrlKey: true})" mat-menu-item><mat-icon>open_in_new</mat-icon><ng-container i18n="Open file in new tab">Open file in new tab</ng-container></button>
</ng-container>
}
</mat-menu>
<mat-menu #action_menu="matMenu">
<ng-container *ngIf="!is_playlist && !loading">
@if (!is_playlist && !loading) {
<button (click)="emitToggleFavorite()" mat-menu-item>
<mat-icon>{{file_obj.favorite ? 'favorite_filled' : 'favorite_outline'}}</mat-icon>
<ng-container *ngIf="!file_obj.favorite" i18n="Favorite button">Favorite</ng-container>
<ng-container *ngIf="file_obj.favorite" i18n="Unfavorite button">Unfavorite</ng-container>
@if (!file_obj.favorite) {
<ng-container i18n="Favorite button">Favorite</ng-container>
}
@else {
<ng-container i18n="Unfavorite button">Unfavorite</ng-container>
}
</button>
<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>&nbsp;<ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
@if (file_obj.sub_id) {
<button (click)="navigateToSubscription()" mat-menu-item><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon>&nbsp;<ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
}
<button [disabled]="!availablePlaylists || availablePlaylists.length === 0" [matMenuTriggerFor]="addtoplaylist" mat-menu-item><mat-icon>playlist_add</mat-icon>&nbsp;<ng-container i18n="Add to playlist menu item">Add to playlist</ng-container></button>
<mat-menu #addtoplaylist="matMenu">
<ng-container *ngFor="let playlist of availablePlaylists">
<button *ngIf="(playlist.type === 'audio') === file_obj.isAudio" [disabled]="playlist.uids?.includes(file_obj.uid)" (click)="emitAddFileToPlaylist(playlist.id)" mat-menu-item>{{playlist.name}}</button>
</ng-container>
@for (playlist of availablePlaylists; track playlist) {
@if ((playlist.type === 'audio') === file_obj.isAudio) {
<button [disabled]="playlist.uids?.includes(file_obj.uid)" (click)="emitAddFileToPlaylist(playlist.id)" mat-menu-item>{{playlist.name}}</button>
}
}
</mat-menu>
<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" (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 don't download again">Delete and don't download again</ng-container></button>
</ng-container>
<ng-container *ngIf="is_playlist && !loading">
@if (file_obj.sub_id) {
<button (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>
} @else {
<button (click)="emitDeleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
}
@if (file_obj.sub_id || use_youtubedl_archive) {
<button (click)="emitDeleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and don't download again">Delete and don't download again</ng-container></button>
}
}
@if (is_playlist && !loading) {
<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>
<ng-container *ngIf="loading">
} @else if (loading) {
<button mat-menu-item>Placeholder</button>
</ng-container>
}
</mat-menu>
<mat-card [matTooltip]="null" (click)="navigateToFile($event)" matRipple class="file-mat-card" [ngClass]="{'small-mat-card': card_size === 'small', 'file-mat-card': card_size === 'medium', 'large-mat-card': card_size === 'large', 'mat-elevation-z4': !elevated, 'mat-elevation-z8': elevated}">
<div style="padding:5px">
<div *ngIf="!loading && file_obj.thumbnailURL" class="img-div">
@if (!loading && file_obj.thumbnailURL) {
<div class="img-div">
<div [ngClass]="{'image-small': card_size === 'small', 'image': card_size === 'medium', 'image-large': card_size === 'large'}" style="position: relative">
<img *ngIf="!hide_image || is_playlist || (file_obj.type === 'audio' || file_obj.isAudio)" [ngClass]="{'image-small': card_size === 'small', 'image': card_size === 'medium', 'image-large': card_size === 'large'}" [src]="file_obj.thumbnailPath ? thumbnailBlobURL : file_obj.thumbnailURL" alt="Thumbnail">
<video *ngIf="elevated && !is_playlist && !(file_obj.type === 'audio' || file_obj.isAudio)" autoplay loop muted [muted]="true" [ngClass]="{'video-small': card_size === 'small', 'video': card_size === 'medium', 'video-large': card_size === 'large'}" [src]="streamURL">
@if (!hide_image || is_playlist || (file_obj.type === 'audio' || file_obj.isAudio)) {
<img [ngClass]="{'image-small': card_size === 'small', 'image': card_size === 'medium', 'image-large': card_size === 'large'}" [src]="file_obj.thumbnailPath ? thumbnailBlobURL : file_obj.thumbnailURL" alt="Thumbnail">
}
@if (elevated && !is_playlist && !(file_obj.type === 'audio' || file_obj.isAudio)) {
<video autoplay loop muted [muted]="true" [ngClass]="{'video-small': card_size === 'small', 'video': card_size === 'medium', 'video-large': card_size === 'large'}" [src]="streamURL">
</video>
}
<div class="duration-time">
{{file_length}}
</div>
</div>
</div>
<div *ngIf="loading" class="img-div">
}
@if (loading) {
<div class="img-div">
<content-loader [backgroundColor]="theme.ghost_primary" [foregroundColor]="theme.ghost_secondary" viewBox="0 0 100 55"><svg:rect x="0" y="0" rx="0" ry="0" width="100" height="55" /></content-loader>
</div>
<span *ngIf="!loading" [ngClass]="{'max-two-lines': card_size !== 'small', 'max-one-line': card_size === 'small' }">{{card_size === 'large' && file_obj.uploader ? file_obj.uploader + ' - ' : ''}}<strong>{{!is_playlist ? file_obj.title : file_obj.name}}</strong></span>
<span *ngIf="loading" class="title-loading"><content-loader [backgroundColor]="theme.ghost_primary" [foregroundColor]="theme.ghost_secondary" viewBox="0 0 250 30"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="30" /></content-loader></span>
} @else {
<span [ngClass]="{'max-two-lines': card_size !== 'small', 'max-one-line': card_size === 'small' }">{{card_size === 'large' && file_obj.uploader ? file_obj.uploader + ' - ' : ''}}<strong>{{!is_playlist ? file_obj.title : file_obj.name}}</strong></span>
}
@if (loading) {
<span class="title-loading"><content-loader [backgroundColor]="theme.ghost_primary" [foregroundColor]="theme.ghost_secondary" viewBox="0 0 250 30"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="30" /></content-loader></span>
}
</div>
</mat-card>
</div>

View File

@@ -1,9 +1,12 @@
<h4 mat-dialog-title *ngIf="create_mode" ><ng-container i18n="Create a playlist dialog title">Create a playlist</ng-container></h4>
<h4 mat-dialog-title *ngIf="!create_mode"><ng-container i18n="Modify playlist dialog title">Modify playlist</ng-container></h4>
@if (create_mode) {
<h4 mat-dialog-title ><ng-container i18n="Create a playlist dialog title">Create a playlist</ng-container></h4>
} @else {
<h4 mat-dialog-title><ng-container i18n="Modify playlist dialog title">Modify playlist</ng-container></h4>
}
<mat-dialog-content style="max-height: 85vh;">
<form>
<div *ngIf="create_mode || playlist">
@if (create_mode || playlist) {
<div>
<div>
<mat-form-field color="accent">
<mat-label i18n="Playlist name">Name</mat-label>
@@ -12,17 +15,21 @@
</div>
<app-recent-videos [selectMode]="true" [defaultSelected]="preselected_files" [customHeader]="'Select files'" (fileSelectionEmitter)="fileSelectionChanged($event)" [selectedIndex]="create_mode ? 1 : 0"></app-recent-videos>
</div>
}
</form>
</mat-dialog-content>
<div class="spacer"></div>
<mat-dialog-actions>
<button *ngIf="create_mode" (click)="createPlaylist()" [disabled]="!name || !filesSelect.value || filesSelect.value.length === 0" color="primary" style="float: right" mat-button>
@if (create_mode) {
<button (click)="createPlaylist()" [disabled]="!name || !filesSelect.value || filesSelect.value.length === 0" color="primary" style="float: right" mat-button>
<ng-container i18n="Create button">Create</ng-container>
</button>
<button *ngIf="!create_mode" (click)="updatePlaylist()" [disabled]="!name || !playlistChanged()" color="primary" style="float: right" mat-button>
} @else {
<button (click)="updatePlaylist()" [disabled]="!name || !playlistChanged()" color="primary" style="float: right" mat-button>
<ng-container i18n="Save button">Save</ng-container>
</button>
<div *ngIf="create_in_progress" style="margin-left: 10px"><mat-spinner [diameter]="25"></mat-spinner></div>
}
@if (create_in_progress) {
<div style="margin-left: 10px"><mat-spinner [diameter]="25"></mat-spinner></div>
}
</mat-dialog-actions>

View File

@@ -17,17 +17,25 @@
<mat-divider></mat-divider>
<h5 style="margin-top: 10px;">Installation details:</h5>
<p>
<ng-container i18n="Version label">Installed version:</ng-container>&nbsp;{{current_version_tag}} - <span style="display: inline-block" *ngIf="checking_for_updates"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner>&nbsp;<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>&nbsp;&nbsp;<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>
<ng-container i18n="Version label">Installed version:</ng-container>&nbsp;{{current_version_tag}} -
@if (checking_for_updates) {
<span style="display: inline-block"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner>&nbsp;<ng-container i18n="Checking for updates text">Checking for updates...</ng-container></span>
} @else {
<mat-icon class="version-checked-icon">done</mat-icon>&nbsp;&nbsp;
@if (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>
} @else {
<span>You are up to date.</span>
}
}
</p>
<p>
<ng-container i18n="Installation type">Installation type:</ng-container>&nbsp;{{postsService.version_info.type}}
<br>
<ng-container *ngIf="postsService.version_info.type === 'docker'">
@if (postsService.version_info.type === 'docker') {
<ng-container i18n="Docker tag">Docker tag:</ng-container>&nbsp;{{postsService.version_info.tag}}
<br>
</ng-container>
}
<ng-container i18n="Commit hash">Commit hash:</ng-container>&nbsp;{{postsService.version_info.commit}}
<br>
<ng-container i18n="Build date">Build date:</ng-container>&nbsp;{{postsService.version_info.date}}
@@ -37,7 +45,7 @@
</p>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button style="margin-bottom: 5px;" mat-stroked-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
</mat-dialog-actions>

View File

@@ -1,5 +1,4 @@
<h4 i18n="Modify args title" mat-dialog-title>Modify youtube-dl args</h4>
<mat-dialog-content>
<div class="container">
<div class="row">
@@ -10,23 +9,28 @@
<mat-chip-grid class="example-chip" #chipList aria-label="Args array" cdkDropList cdkDropListDisabled
cdkDropListOrientation="horizontal"
(cdkDropListDropped)="drop($event)">
<mat-chip-row [matTooltip]="argsByKey[arg] ? argsByKey[arg]['description'] : null" *ngFor="let arg of args_array; let i = index;" [removable]="removable" (removed)="remove(i)" cdkDrag>
@for (arg of args_array; track arg; let i = $index) {
<mat-chip-row [matTooltip]="argsByKey[arg] ? argsByKey[arg]['description'] : null" [removable]="removable" (removed)="remove(i)" cdkDrag>
{{arg}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
@if (removable) {
<mat-icon matChipRemove>cancel</mat-icon>
}
</mat-chip-row>
}
</mat-chip-grid>
<mat-form-field style="width: 100%" color="accent">
<input #chipper style="width: 100%;" [formControl]="chipCtrl" matInput [matAutocomplete]="autochip" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)">
</mat-form-field>
<mat-autocomplete #autochip="matAutocomplete">
<mat-option *ngFor="let arg of filteredChipOptions | async" [value]="arg.key">
@for (arg of filteredChipOptions | async; track arg) {
<mat-option [value]="arg.key">
<span [innerHTML]="arg.key | highlight : chipCtrl.value"></span>
<button class="info-autocomplete-icon" [matTooltip]="arg.description" mat-icon-button><mat-icon>info</mat-icon></button>
</mat-option>
}
</mat-autocomplete>
</mat-card-content>
</mat-card>
@@ -42,37 +46,38 @@
<input matInput [matAutocomplete]="auto" [formControl]="stateCtrl">
</mat-form-field>
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let arg of filteredOptions | async" [value]="arg.key">
@for (arg of filteredOptions | async; track arg) {
<mat-option [value]="arg.key">
<span [innerHTML]="arg.key | highlight : stateCtrl.value"></span>
<button class="info-autocomplete-icon" [matTooltip]="arg.description" mat-icon-button><mat-icon>info</mat-icon></button>
</mat-option>
}
</mat-autocomplete>
<div>
<mat-menu #argsByCategoryMenu="matMenu">
<ng-container *ngFor="let argsInCategory of argsByCategory | keyvalue">
@for (argsInCategory of argsByCategory | keyvalue; track argsInCategory) {
<button mat-menu-item [matMenuTriggerFor]="subMenu">{{argsInfo[argsInCategory.key].label}}</button>
<mat-menu #subMenu="matMenu">
<button mat-menu-item *ngFor="let arg of argsInCategory.value" (click)="setFirstArg(arg.key)"><div style="display: inline-block;">{{arg.key}}</div>&nbsp;&nbsp;<div class="info-menu-icon"><mat-icon [matTooltip]="arg.description">info</mat-icon></div></button>
@for (arg of argsInCategory.value; track arg) {
<button mat-menu-item (click)="setFirstArg(arg.key)"><div style="display: inline-block;">{{arg.key}}</div>&nbsp;&nbsp;<div class="info-menu-icon"><mat-icon [matTooltip]="arg.description">info</mat-icon></div></button>
}
</mat-menu>
</ng-container>
}
</mat-menu>
<button style="margin-bottom: 15px" mat-stroked-button [matMenuTriggerFor]="argsByCategoryMenu"><ng-container i18n="Search args by category button">Search by category</ng-container></button>
</div>
</div>
<div>
<mat-checkbox color="accent" [ngModelOptions]="{standalone: true}" [(ngModel)]="secondArgEnabled"><ng-container i18n="Use arg value checkbox">Use arg value</ng-container></mat-checkbox>
</div>
<div *ngIf="secondArgEnabled">
@if (secondArgEnabled) {
<div>
<mat-form-field style="width: 75%" color="accent">
<mat-label i18n="Arg value">Arg value</mat-label>
<input [ngModelOptions]="{standalone: true}" matInput [disabled]="!secondArgEnabled" [(ngModel)]="secondArg">
</mat-form-field>
</div>
}
</form>
<div>
<button (click)="addArg()" [disabled]="!canAddArg()" mat-stroked-button color="accent"><ng-container i18n="Search args by category button">Add arg</ng-container></button>
@@ -82,10 +87,7 @@
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="null"><ng-container i18n="Arg modifier cancel button">Cancel</ng-container></button>
<button mat-button color="accent" [mat-dialog-close]="modified_args"><ng-container i18n="Arg modifier modify button">Modify</ng-container></button>

View File

@@ -2,24 +2,27 @@
<mat-dialog-content>
<div style="margin-bottom: 10px;">
<!-- We can support text dialogs or dialogs where users must select items from a list -->
<ng-container *ngIf="dialogType === 'text'">
@if (dialogType === 'text') {
{{dialogText}}
</ng-container>
<ng-container *ngIf="dialogType === 'selection_list'">
} @else if (dialogType === 'selection_list') {
<mat-selection-list [(ngModel)]="selected_items">
<mat-list-option *ngFor="let item of list" [value]="item.key">
@for (item of list; track item) {
<mat-list-option [value]="item.key">
{{item.title}}
</mat-list-option>
}
</mat-selection-list>
</ng-container>
}
</div>
</mat-dialog-content>
<mat-dialog-actions>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button [disabled]="dialogType === 'selection_list' && selected_items.length === 0" [color]="warnSubmitColor ? 'warn' : 'primary'" mat-flat-button type="submit" (click)="confirmClicked()">{{submitText}}</button>
<div class="mat-spinner" *ngIf="submitClicked">
@if (submitClicked) {
<div class="mat-spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
<span class="spacer"></span>
<button style="float: right;" mat-stroked-button mat-dialog-close>
{{cancelText}}

View File

@@ -1,5 +1,4 @@
<h4 mat-dialog-title i18n="Cookies uploader dialog title">Upload new cookies</h4>
<mat-dialog-content>
<div>
<div class="center">
@@ -22,19 +21,24 @@
<div style="margin-top: 10px;">
<table class="table">
<tbody class="upload-name-style">
<tr *ngFor="let item of files; let i=index">
@for (item of files; track item; let i = $index) {
<tr>
<td style="vertical-align: middle;">
<strong>{{ item.relativePath }}</strong>
</td>
<td>
<button [disabled]="uploading || uploaded" (click)="uploadFile()" style="float: right" matTooltip="Upload" i18n-matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon><mat-spinner *ngIf="uploading" class="spinner" [diameter]="38"></mat-spinner></button>
<button [disabled]="uploading || uploaded" (click)="uploadFile()" style="float: right" matTooltip="Upload" i18n-matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon>
@if (uploading) {
<mat-spinner class="spinner" [diameter]="38"></mat-spinner>
}
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions><button style="margin-bottom: 5px;" mat-dialog-close mat-stroked-button><ng-container i18n="Close">Close</ng-container></button></mat-dialog-actions>

View File

@@ -1,48 +1,47 @@
<h4 mat-dialog-title><ng-container i18n="Editing category dialog title">Editing category</ng-container>&nbsp;{{category['name']}}</h4>
<mat-dialog-content style="max-height: 50vh">
<mat-form-field style="width: 250px; margin-bottom: 5px;">
<mat-label i18n="Category name">Name</mat-label>
<input matInput [(ngModel)]="category['name']" required>
</mat-form-field>
<mat-divider></mat-divider>
<h6 style="margin-top: 20px;" i18n="Rules">Rules</h6>
<mat-list>
<mat-list-item *ngFor="let rule of category['rules']; let i = index">
<mat-form-field [style.visibility]="i === 0 ? 'hidden' : null" class="operator-select">
<mat-select [disabled]="i === 0" [(ngModel)]="rule['preceding_operator']">
@for (rule of category['rules']; track rule) {
<mat-list-item>
<mat-form-field [style.visibility]="$index === 0 ? 'hidden' : null" class="operator-select">
<mat-select [disabled]="$index === 0" [(ngModel)]="rule['preceding_operator']">
<mat-option value="or">OR</mat-option>
<mat-option value="and">AND</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="property-select">
<mat-select [(ngModel)]="rule['property']">
<mat-option *ngFor="let propertyOption of propertyOptions" [value]="propertyOption.value">{{propertyOption.label}}</mat-option>
@for (propertyOption of propertyOptions; track propertyOption) {
<mat-option [value]="propertyOption.value">{{propertyOption.label}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="comparator-select">
<mat-select [(ngModel)]="rule['comparator']">
<mat-option *ngFor="let comparatorOption of comparatorOptions" [value]="comparatorOption.value">{{comparatorOption.label}}</mat-option>
@for (comparatorOption of comparatorOptions; track comparatorOption) {
<mat-option [value]="comparatorOption.value">{{comparatorOption.label}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="value-input">
<input matInput [(ngModel)]="rule['value']">
</mat-form-field>
<span class="rule-buttons">
<button [disabled]="i === category['rules'].length-1" (click)="swapRules(i, i+1)" mat-icon-button><mat-icon>arrow_downward</mat-icon></button>
<button [disabled]="i === 0" (click)="swapRules(i, i-1)" mat-icon-button><mat-icon>arrow_upward</mat-icon></button>
<button (click)="removeRule(i)" mat-icon-button><mat-icon>cancel</mat-icon></button>
<button [disabled]="$index === category['rules'].length-1" (click)="swapRules($index, $index+1)" mat-icon-button><mat-icon>arrow_downward</mat-icon></button>
<button [disabled]="$index === 0" (click)="swapRules($index, $index-1)" mat-icon-button><mat-icon>arrow_upward</mat-icon></button>
<button (click)="removeRule($index)" mat-icon-button><mat-icon>cancel</mat-icon></button>
</span>
</mat-list-item>
}
</mat-list>
<button style="margin-bottom: 8px;" mat-icon-button (click)="addNewRule()" matTooltip="Add new rule" i18n-matTooltip="Add new rule tooltip"><mat-icon>add</mat-icon></button>
<mat-divider></mat-divider>
<mat-form-field style="width: 250px; margin-top: 10px;">
<mat-label i18n="Custom file output">Custom file output</mat-label>
<input matInput [(ngModel)]="category['custom_output']">
@@ -53,12 +52,12 @@
</mat-hint>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Cancel">Cancel</ng-container></button>
<button mat-button [disabled]="categoryChanged()" type="submit" (click)="saveClicked()"><ng-container i18n="Save button">Save</ng-container></button>
<div class="mat-spinner" *ngIf="updating">
@if (updating) {
<div class="mat-spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
</mat-dialog-actions>

View File

@@ -1,5 +1,8 @@
<h4 mat-dialog-title><ng-container i18n="Edit subscription dialog title prefix">Editing</ng-container>&nbsp;{{sub.name}}&nbsp;<ng-container *ngIf="sub.paused" i18n="Paused suffix">(Paused)</ng-container></h4>
<h4 mat-dialog-title><ng-container i18n="Edit subscription dialog title prefix">Editing</ng-container>&nbsp;{{sub.name}}
@if (sub.paused) {
&nbsp;<ng-container i18n="Paused suffix">(Paused)</ng-container>
}
</h4>
<mat-dialog-content>
<div class="container-fluid">
<div class="row">
@@ -9,19 +12,23 @@
<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="editor_initialized">
@if (editor_initialized) {
<div class="col-12">
<ng-container i18n="Download time range prefix">Download videos uploaded in the last</ng-container>
<mat-form-field color="accent" class="amount-select">
<input type="number" matInput [(ngModel)]="timerange_amount" (ngModelChange)="timerangeChanged($event, false)" [disabled]="download_all">
</mat-form-field>
<mat-form-field class="unit-select">
<mat-select color="accent" [(ngModel)]="timerange_unit" (ngModelChange)="timerangeChanged($event, true)" [disabled]="download_all">
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
@for (time_unit of time_units; track time_unit) {
<mat-option [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>
@@ -31,7 +38,9 @@
<mat-form-field>
<mat-label i18n="Max quality">Max quality</mat-label>
<mat-select [disabled]="new_sub.type === 'audio'" [(ngModel)]="new_sub.maxQuality">
<mat-option *ngFor="let available_quality of available_qualities" [value]="available_quality['value']">{{available_quality['label']}}</mat-option>
@for (available_quality of available_qualities; track available_quality) {
<mat-option [value]="available_quality['value']">{{available_quality['label']}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
@@ -59,12 +68,13 @@
</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">
@if (updating) {
<div class="mat-spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
</mat-dialog-actions>

View File

@@ -1,5 +1,4 @@
<h4 mat-dialog-title><ng-container i18n="Generate RSS URL">Generate RSS URL</ng-container></h4>
<mat-dialog-content>
<div class="container-fluid">
<div class="row justify-content-center">
@@ -25,7 +24,9 @@
<mat-label><ng-container i18n="User">User</ng-container></mat-label>
<mat-select color="accent" [(ngModel)]="userFilter" (selectionChange)="rebuildURL()" [disabled]="!usersList">
<mat-option [value]="''"><ng-container i18n="None">None</ng-container></mat-option>
<mat-option *ngFor="let user of usersList" [value]="user.uid"><ng-container>{{user.name}}</ng-container></mat-option>
@for (user of usersList; track user) {
<mat-option [value]="user.uid"><ng-container>{{user.name}}</ng-container></mat-option>
}
</mat-select>
</mat-form-field>
</div>
@@ -34,7 +35,9 @@
<mat-label><ng-container i18n="Subscription">Subscription</ng-container></mat-label>
<mat-select color="accent" [(ngModel)]="subscriptionFilter" (selectionChange)="rebuildURL()">
<mat-option [value]="''"><ng-container i18n="None">None</ng-container></mat-option>
<mat-option *ngFor="let sub of postsService.subscriptions" [value]="sub.id"><ng-container>{{sub.name}}</ng-container></mat-option>
@for (sub of postsService.subscriptions; track sub) {
<mat-option [value]="sub.id"><ng-container>{{sub.name}}</ng-container></mat-option>
}
</mat-select>
</mat-form-field>
</div>
@@ -52,7 +55,6 @@
</div>
</div>
</div>
<mat-form-field style="width: 100%;">
<mat-label i18n="URL">URL</mat-label>
<input readonly [(ngModel)]="url" matInput>
@@ -61,7 +63,6 @@
</button>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
</mat-dialog-actions>

View File

@@ -1,8 +1,8 @@
<h4 mat-dialog-title><ng-container i18n="Restore DB from backup">Restore DB from backup</ng-container></h4>
<mat-dialog-content>
<mat-selection-list [multiple]="false" [(ngModel)]="selected_backup">
<mat-list-option *ngFor="let db_backup of db_backups" [value]="db_backup.name" [matTooltip]="db_backup.name">
@for (db_backup of db_backups; track db_backup) {
<mat-list-option [value]="db_backup.name" [matTooltip]="db_backup.name">
<div class="container-fluid">
<div class="row">
<div class="col-4">
@@ -17,13 +17,15 @@
</div>
</div>
</mat-list-option>
}
</mat-selection-list>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Restore DB cancel button">Cancel</ng-container></button>
<button mat-button [disabled]="restoring" (click)="restoreClicked()" [disabled]="!selected_backup || selected_backup.length !== 1"><ng-container i18n="Restore button">Restore</ng-container></button>
<div class="mat-spinner" *ngIf="restoring">
@if (restoring) {
<div class="mat-spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
</mat-dialog-actions>

View File

@@ -1,5 +1,4 @@
<h4 mat-dialog-title><ng-container i18n="Create admin account dialog title">Create admin account</ng-container></h4>
<mat-dialog-content>
<div>
<p i18n="No default admin detected explanation">No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'.</p>
@@ -13,8 +12,11 @@
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button [disabled]="input.length === 0" color="accent" style="margin-bottom: 12px;" (click)="create()" mat-raised-button><ng-container i18n="Create">Create</ng-container></button>
<div class="spinner-div"><mat-spinner [diameter]="25" *ngIf="creating"></mat-spinner></div>
<div class="spinner-div">
@if (creating) {
<mat-spinner [diameter]="25"></mat-spinner>
}
</div>
</mat-dialog-actions>

View File

@@ -1,8 +1,10 @@
<h4 mat-dialog-title>
<ng-container *ngIf="is_playlist" i18n="Share playlist dialog title">Share playlist</ng-container>
<ng-container *ngIf="!is_playlist" i18n="Share video dialog title">Share file</ng-container>
@if (is_playlist) {
<ng-container i18n="Share playlist dialog title">Share playlist</ng-container>
} @else {
<ng-container i18n="Share video dialog title">Share file</ng-container>
}
</h4>
<mat-dialog-content>
<div>
<div>
@@ -25,7 +27,6 @@
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Close button">Close</ng-container></button>
</mat-dialog-actions>

View File

@@ -1,5 +1,4 @@
<h4 mat-dialog-title i18n="Subscribe dialog title">Subscribe to playlist or channel</h4>
<mat-dialog-content>
<div class="container-fluid">
<div class="row">
@@ -32,9 +31,11 @@
</mat-form-field>
<mat-form-field class="unit-select">
<mat-select color="accent" [(ngModel)]="timerange_unit" [disabled]="download_all">
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
@for (time_unit of time_units; track time_unit) {
<mat-option [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
@@ -43,7 +44,9 @@
<mat-form-field>
<mat-label i18n="Max quality">Max quality</mat-label>
<mat-select [disabled]="audioOnlyMode" [(ngModel)]="maxQuality">
<mat-option *ngFor="let available_quality of available_qualities" [value]="available_quality['value']">{{available_quality['label']}}</mat-option>
@for (available_quality of available_qualities; track available_quality) {
<mat-option [value]="available_quality['value']">{{available_quality['label']}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
@@ -76,12 +79,13 @@
</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]="!url" type="submit" (click)="subscribeClicked()"><ng-container i18n="Subscribe button">Subscribe</ng-container></button>
<div class="mat-spinner" *ngIf="subscribing">
@if (subscribing) {
<div class="mat-spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
</mat-dialog-actions>

View File

@@ -1,5 +1,8 @@
<h4 mat-dialog-title>{{sub.name}}&nbsp;<ng-container *ngIf="sub.paused" i18n="Paused suffix">(Paused)</ng-container></h4>
<h4 mat-dialog-title>{{sub.name}}
@if (sub.paused) {
&nbsp;<ng-container i18n="Paused suffix">(Paused)</ng-container>
}
</h4>
<mat-dialog-content>
<div class="info-item">
<strong><ng-container i18n="Subscription type property">Type:</ng-container>&nbsp;</strong>
@@ -13,12 +16,13 @@
<strong><ng-container i18n="Subscription ID property">ID:</ng-container>&nbsp;</strong>
<span class="info-item-value">{{sub.id}}</span>
</div>
<div class="info-item" *ngIf="sub.archive">
@if (sub.archive) {
<div class="info-item">
<strong><ng-container i18n="Subscription ID property">Archive:</ng-container>&nbsp;</strong>
<span class="info-item-value">{{sub.archive}}</span>
</div>
}
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Close subscription info button">Close</ng-container></button>
<button mat-stroked-button (click)="downloadArchive()" color="accent"><ng-container i18n="Export Archive button">Export Archive</ng-container></button>

View File

@@ -3,6 +3,7 @@ import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dial
import { PostsService } from 'app/posts.services';
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
import { Subscription } from 'api-types';
import { saveAs } from 'file-saver';
@Component({
selector: 'app-subscription-info-dialog',

View File

@@ -1,18 +1,26 @@
<h4 i18n="Update progress dialog title" mat-dialog-title>Updater</h4>
<mat-dialog-content>
<div *ngIf="updateStatus">
@if (updateStatus) {
<div>
<div style="margin-bottom: 8px;">
<h6 *ngIf="updateStatus['updating']">Update in progress</h6>
<h6 *ngIf="!updateStatus['updating'] && updateStatus['error']">Update failed</h6>
<h6 *ngIf="!updateStatus['updating'] && !updateStatus['error']">Update succeeded!</h6>
@if (updateStatus['updating']) {
<h6>Update in progress</h6>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
} @else {
@if (updateStatus['error']) {
<h6>Update failed</h6>
} @else {
<h6>Update succeeded!</h6>
}
<mat-progress-bar mode="determinate" value="100"></mat-progress-bar>
}
</div>
<mat-progress-bar *ngIf="updateStatus['updating']" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="!updateStatus['updating']" mode="determinate" value="100"></mat-progress-bar>
<p style="margin-top: 4px; font-size: 13px;" *ngIf="updateStatus['details']">{{updateStatus['details']}}</p>
@if (updateStatus['details']) {
<p style="margin-top: 4px; font-size: 13px;">{{updateStatus['details']}}</p>
}
</div>
}
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Close update progress dialog">Close</ng-container></button>
</mat-dialog-actions>

View File

@@ -1,5 +1,4 @@
<h4 mat-dialog-title><ng-container i18n="Update task schedule">Update task schedule</ng-container></h4>
<mat-dialog-content>
<div class="container-fluid">
<div class="row">
@@ -9,7 +8,8 @@
<div class="col-12 mt-2">
<mat-checkbox [(ngModel)]="recurring" [disabled]="!enabled"><ng-container i18n="Recurring">Recurring</ng-container></mat-checkbox>
</div>
<div class="col-12 mt-2" *ngIf="recurring">
@if (recurring) {
<div class="col-12 mt-2">
<mat-form-field>
<mat-select placeholder="Interval" [(ngModel)]="interval" [disabled]="!enabled">
<mat-option value="weekly">Weekly</mat-option>
@@ -17,15 +17,8 @@
</mat-select>
</mat-form-field>
</div>
<div *ngIf="!recurring" class="col-12 mt-2">
<mat-form-field>
<mat-label i18n="Choose a date">Choose a date</mat-label>
<input [(ngModel)]="date" [min]="today" matInput [matDatepicker]="picker" [disabled]="!enabled">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<div *ngIf="recurring && interval === 'weekly'" class="col-12 mt-2">
@if (interval === 'weekly') {
<div class="col-12 mt-2">
<mat-button-toggle-group [(ngModel)]="days_of_week" [multiple]="true" [disabled]="!enabled" aria-label="Week day">
<!-- TODO: support translation -->
<mat-button-toggle [value]="0">M</mat-button-toggle>
@@ -37,17 +30,29 @@
<mat-button-toggle [value]="6">S</mat-button-toggle>
</mat-button-toggle-group>
</div>
}
} @else {
<div class="col-12 mt-2">
<mat-form-field>
<mat-label i18n="Choose a date">Choose a date</mat-label>
<input [(ngModel)]="date" [min]="today" matInput [matDatepicker]="picker" [disabled]="!enabled">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
}
<div class="col-12 mt-2">
<mat-form-field>
<mat-label>Time</mat-label>
<input type="time" matInput [(ngModel)]="time" [disabled]="!enabled">
<mat-hint *ngIf="Intl?.DateTimeFormat().resolvedOptions().timeZone">{{Intl.DateTimeFormat().resolvedOptions().timeZone}}</mat-hint>
@if (Intl?.DateTimeFormat().resolvedOptions().timeZone) {
<mat-hint>{{Intl.DateTimeFormat().resolvedOptions().timeZone}}</mat-hint>
}
</mat-form-field>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Update task schedule cancel button">Cancel</ng-container></button>
<button mat-button (click)="updateTaskSchedule()"><ng-container i18n="Update button">Update</ng-container></button>

View File

@@ -1,7 +1,7 @@
<h4 mat-dialog-title i18n="User profile dialog title">Your Profile</h4>
<mat-dialog-content>
<div *ngIf="postsService.isLoggedIn && postsService.user">
@if (postsService.isLoggedIn && postsService.user) {
<div>
<div>
<strong><ng-container i18n="Name">Name:</ng-container></strong>&nbsp;{{postsService.user.name}}
</div>
@@ -15,14 +15,17 @@
</div>
<mat-divider style="margin-bottom: 20px"></mat-divider>
</div>
}
<mat-form-field color="accent">
<mat-label><ng-container i18n="Language select label">Language</ng-container></mat-label>
<mat-select (selectionChange)="localeSelectChanged($event.value)" [(value)]="initialLocale">
<mat-option *ngFor="let locale of supported_locales" [value]="locale">
<ng-container *ngIf="all_locales[locale]">
@for (locale of supported_locales; track locale) {
<mat-option [value]="locale">
@if (all_locales[locale]) {
{{all_locales[locale]['nativeName']}}
</ng-container>
}
</mat-option>
}
</mat-select>
</mat-form-field>
<br/>
@@ -53,12 +56,13 @@
</mat-select>
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<div style="width: 100%">
<div style="position: relative">
<button mat-stroked-button mat-dialog-close color="primary"><ng-container i18n="Close">Close</ng-container></button>
<button *ngIf="postsService.isLoggedIn" style="position: absolute; right: 0px;" (click)="logoutClicked()" mat-stroked-button color="warn"><ng-container i18n="Logout">Logout</ng-container></button>
@if (postsService.isLoggedIn) {
<button style="position: absolute; right: 0px;" (click)="logoutClicked()" mat-stroked-button color="warn"><ng-container i18n="Logout">Logout</ng-container></button>
}
</div>
</div>
</mat-dialog-actions>

View File

@@ -5,7 +5,6 @@
<button [disabled]="!initialized || retrieving_file || !write_access" (click)="toggleFavorite()" mat-icon-button ><mat-icon>{{file.favorite ? 'favorite_filled' : 'favorite_outline'}}</mat-icon></button>
</div>
</h4>
<mat-dialog-content>
<mat-form-field class="info-field">
<mat-label i18n="Name">Name</mat-label>
@@ -36,17 +35,21 @@
<mat-label i18n="Thumbnail URL">Thumbnail URL</mat-label>
<input [(ngModel)]="new_file.thumbnailURL" matInput [disabled]="!editing || new_file.thumbnailPath">
</mat-form-field>
<mat-form-field *ngIf="initialized && postsService.categories" class="info-field">
@if (initialized && postsService.categories) {
<mat-form-field class="info-field">
<mat-label i18n="Category">Category</mat-label>
<mat-select [value]="category" (selectionChange)="categoryChanged($event)" [compareWith]="categoryComparisonFunction" [disabled]="!editing">
<mat-option [value]="{}">
N/A
</mat-option>
<mat-option *ngFor="let available_category of postsService.categories | keyvalue" [value]="available_category.value">
@for (available_category of postsService.categories | keyvalue; track available_category) {
<mat-option [value]="available_category.value">
{{available_category.value['name']}}
</mat-option>
}
</mat-select>
</mat-form-field>
}
<mat-form-field class="info-field">
<mat-label i18n="View count">View count</mat-label>
<input type="number" [(ngModel)]="new_file.view_count" matInput [disabled]="!editing">
@@ -55,13 +58,13 @@
<mat-label i18n="Local view count">Local view count</mat-label>
<input type="number" [(ngModel)]="new_file.local_view_count" matInput [disabled]="!editing">
</mat-form-field>
<mat-divider style="margin-bottom: 16px;"></mat-divider>
<div *ngIf="!new_file.isAudio" class="info-item">
@if (!new_file.isAudio) {
<div class="info-item">
<div class="info-item-label"><strong><ng-container i18n="Video resolution property">Resolution:</ng-container>&nbsp;</strong></div>
<div class="info-item-value">{{new_file.height ? new_file.height + 'p' : 'N/A'}}</div>
</div>
}
<div class="info-item">
<div class="info-item-label"><strong><ng-container i18n="Video audio bitrate property">Audio bitrate:</ng-container>&nbsp;</strong></div>
<div class="info-item-value">{{new_file.abr ? new_file.abr + ' Kbps' : 'N/A'}}</div>
@@ -74,9 +77,7 @@
<div class="info-item-label"><strong><ng-container i18n="Video path property">Path:</ng-container>&nbsp;</strong></div>
<div class="info-item-value">{{new_file.path ? new_file.path : 'N/A'}}</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Close video info button">Close</ng-container></button>
<button mat-button [disabled]="!metadataChanged()" (click)="saveChanges()"><ng-container i18n="Save video info button">Save</ng-container></button>

View File

@@ -11,7 +11,9 @@
<button mat-button mat-dialog-close>Cancel</button>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button mat-button [disabled]="!inputText" type="submit" (click)="enterPressed()">{{submitText}}</button>
<div class="mat-spinner" *ngIf="inputSubmitted">
@if (inputSubmitted) {
<div class="mat-spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
</mat-dialog-actions>

View File

@@ -12,7 +12,8 @@
</mat-form-field>
<!--<button type="button" class="input-clear-button" mat-icon-button (click)="clearInput()"><mat-icon>clear</mat-icon></button>-->
</div>
<div *ngIf="allowQualitySelect" class="col-7 col-sm-3">
@if (allowQualitySelect) {
<div class="col-7 col-sm-3">
<mat-form-field color="accent" style="display: inline-block; width: inherit; min-width: 120px;">
<mat-label>
<ng-container i18n="Quality select label">
@@ -23,30 +24,37 @@
<mat-option i18n="Best" [value]="''">
Best
</mat-option>
<ng-container *ngIf="url && cachedAvailableFormats && cachedAvailableFormats[url]?.formats && !cachedAvailableFormats[url]?.formats_failed">
<ng-container *ngFor="let option of cachedAvailableFormats[url]['formats'][audioOnly ? 'audio' : 'video']">
<mat-option [matTooltip]="option.expected_filesize ? humanFileSize(option.expected_filesize) : null" *ngIf="option.key !== 'best_audio_format'" [value]="option">
@if (url && cachedAvailableFormats && cachedAvailableFormats[url]?.formats && !cachedAvailableFormats[url]?.formats_failed) {
@for (option of cachedAvailableFormats[url]['formats'][audioOnly ? 'audio' : 'video']; track option) {
@if (option.key !== 'best_audio_format') {
<mat-option [matTooltip]="option.expected_filesize ? humanFileSize(option.expected_filesize) : null" [value]="option">
{{option.key}}
</mat-option>
</ng-container>
</ng-container>
<ng-container *ngIf="url && cachedAvailableFormats && cachedAvailableFormats[url]?.formats_failed">
<ng-container *ngFor="let option of qualityOptions[audioOnly ? 'audio' : 'video']">
}
}
}
@if (url && cachedAvailableFormats && cachedAvailableFormats[url]?.formats_failed) {
@for (option of qualityOptions[audioOnly ? 'audio' : 'video']; track option) {
<mat-option [value]="option.value">
{{option.label}}
</mat-option>
</ng-container>
</ng-container>
}
}
</mat-select>
</mat-form-field>
<div class="spinner-div" *ngIf="url !== '' && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats_loading']">
@if (url !== '' && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats_loading']) {
<div class="spinner-div">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
}
</div>
}
</div>
</div>
</div>
<div class="results-div" *ngIf="results_showing">
<span *ngFor="let result of results; let i = index">
@if (results_showing) {
<div class="results-div">
@for (result of results; track result; let i = $index) {
<span>
<mat-card appearance="outlined" class="result-card mat-elevation-z7" [ngClass]="[(i === 0 && results.length > 1) ? 'first-result-card' : '', ((i === results.length-1) && results.length > 1) ? 'last-result-card' : '', (results.length === 1) ? 'only-result-card' : '']">
<div class="search-card-title">
{{result.title}}
@@ -64,7 +72,9 @@
</button>
</mat-card>
</span>
}
</div>
}
</form>
<br/>
<mat-checkbox [disabled]="autoplay && !!current_download" (change)="videoModeChanged($event)" [(ngModel)]="audioOnly" style="float: left; margin-top: -12px; margin-left: 4px;">
@@ -72,12 +82,13 @@
Only Audio
</ng-container>
</mat-checkbox>
<mat-checkbox *ngIf="!forceAutoplay" [disabled]="getURLArray(url).length > 1" (change)="autoplayChanged($event)" [(ngModel)]="autoplay" style="float: right; margin-top: -12px">
@if (!forceAutoplay) {
<mat-checkbox [disabled]="getURLArray(url).length > 1" (change)="autoplayChanged($event)" [(ngModel)]="autoplay" style="float: right; margin-top: -12px">
<ng-container i18n="Autoplay checkbox">
Autoplay
</ng-container>
</mat-checkbox>
}
</div>
</mat-card-content>
<mat-card-actions>
@@ -86,15 +97,18 @@
Download
</ng-container>
</button>
<button (click)="cancelDownload()" style="margin-left: 8px; margin-bottom: 8px" *ngIf="!!current_download" mat-stroked-button color="warn">
@if (!!current_download) {
<button (click)="cancelDownload()" style="margin-left: 8px; margin-bottom: 8px" mat-stroked-button color="warn">
<ng-container i18n="Cancel download button">
Cancel
</ng-container>
</button>
}
</mat-card-actions>
</mat-card>
</div>
<div *ngIf="allowAdvancedDownload" class="big demo-basic">
@if (allowAdvancedDownload) {
<div class="big demo-basic">
<form style="margin-left: 20px; margin-right: 20px;">
<mat-expansion-panel class="big no-border-radius-top">
<mat-expansion-panel-header>
@@ -104,11 +118,13 @@
</ng-container>
</mat-panel-title>
</mat-expansion-panel-header>
<p *ngIf="this.simulatedOutput">
@if (this.simulatedOutput) {
<p>
<ng-container i18n="Simulated command label">
Simulated command:
</ng-container>
&nbsp;<i>{{this.simulatedOutput}}</i></p>
}
<div class="container" style="padding-bottom: 20px;">
<div class="row">
<div class="col-12 col-sm-6">
@@ -148,69 +164,86 @@
</mat-hint>
</mat-form-field>
</div>
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-3">
@if (!youtubeAuthDisabledOverride) {
<div class="col-12 col-sm-6 mt-3">
<mat-checkbox color="accent" [disabled]="!!current_download" (change)="youtubeAuthEnabledChanged($event)" [(ngModel)]="youtubeAuthEnabled" style="z-index: 999" [ngModelOptions]="{standalone: true}">
<ng-container i18n="Use authentication checkbox">
Use authentication
</ng-container>
</mat-checkbox>
<mat-form-field *ngIf="youtubeAuthEnabled" color="accent" class="advanced-input">
@if (youtubeAuthEnabled) {
<mat-form-field color="accent" class="advanced-input">
<mat-label i18n="Username">Username</mat-label>
<input [(ngModel)]="youtubeUsername" [ngModelOptions]="{standalone: true}" matInput (ngModelChange)="argsChanged()">
</mat-form-field>
}
</div>
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-3">
<mat-form-field *ngIf="youtubeAuthEnabled" style="margin-top: 40px;" color="accent" class="advanced-input">
<div class="col-12 col-sm-6 mt-3">
@if (youtubeAuthEnabled) {
<mat-form-field style="margin-top: 40px;" color="accent" class="advanced-input">
<mat-label i18n="Password">Password</mat-label>
<input [(ngModel)]="youtubePassword" type="password" [ngModelOptions]="{standalone: true}" matInput (ngModelChange)="argsChanged()">
</mat-form-field>
}
</div>
}
<div class="col-12 col-sm-6 mt-3">
<mat-checkbox color="accent" [disabled]="!!current_download" [(ngModel)]="cropFile" style="z-index: 999" [ngModelOptions]="{standalone: true}">
<ng-container i18n="Crop video checkbox">
Crop file
</ng-container>
</mat-checkbox>
<mat-form-field *ngIf="cropFile" color="accent" class="advanced-input">
@if (cropFile) {
<mat-form-field color="accent" class="advanced-input">
<mat-label i18n="Crop from (seconds)">Crop from (seconds)</mat-label>
<input [(ngModel)]="cropFileStart" type="number" [ngModelOptions]="{standalone: true}" matInput>
</mat-form-field>
}
</div>
<div class="col-12 col-sm-6 mt-3">
<mat-form-field *ngIf="cropFile" style="margin-top: 40px;" color="accent" class="advanced-input">
@if (cropFile) {
<mat-form-field style="margin-top: 40px;" color="accent" class="advanced-input">
<mat-label i18n="Crop to (seconds)">Crop to (seconds)</mat-label>
<input [(ngModel)]="cropFileEnd" type="number" [ngModelOptions]="{standalone: true}" matInput>
</mat-form-field>
}
</div>
</div>
</div>
</mat-expansion-panel>
</form>
</div>
}
<br/>
<div class="centered big" id="bar_div" *ngIf="current_download && autoplay">
@if (current_download && autoplay) {
<div class="centered big" id="bar_div">
<div class="margined">
<div [ngClass]="(+percentDownloaded > 99)?'make-room-for-spinner':'equal-sizes'" style="display: inline-block; width: 100%; padding-left: 20px" *ngIf="current_download.percent_complete && current_download.percent_complete > 1;else indeterminateprogress">
@if (current_download.percent_complete && current_download.percent_complete > 1) {
<div [ngClass]="(+percentDownloaded > 99)?'make-room-for-spinner':'equal-sizes'" style="display: inline-block; width: 100%; padding-left: 20px">
<mat-progress-bar style="border-radius: 5px;" mode="determinate" value="{{percentDownloaded}}"></mat-progress-bar>
<br/>
</div>
<div *ngIf="+percentDownloaded > 99" class="spinner">
} @else {
<mat-progress-bar style="border-radius: 5px;" mode="indeterminate"></mat-progress-bar>
}
@if (+percentDownloaded > 99) {
<div class="spinner">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
<ng-template #indeterminateprogress>
<mat-progress-bar style="border-radius: 5px;" mode="indeterminate"></mat-progress-bar>
</ng-template>
}
</div>
<br/>
</div>
<div style="display: flex; justify-content: center;" *ngIf="downloads && downloads.length > 0 && !autoplay">
}
@if (downloads && downloads.length > 0 && !autoplay) {
<div style="display: flex; justify-content: center;">
<app-downloads style="width: 80%; min-width: 350px; margin-bottom: 10px" [uids]="download_uids"></app-downloads>
</div>
<ng-container *ngIf="cachedFileManagerEnabled || fileManagerEnabled">
}
@if (cachedFileManagerEnabled || fileManagerEnabled) {
<app-recent-videos #recentVideos></app-recent-videos>
<br/>
<h4 style="text-align: center">Custom playlists</h4>
<app-custom-playlists></app-custom-playlists>
</ng-container>
}

View File

@@ -1,4 +1,5 @@
<div style="height: 100%" *ngIf="playlist.length > 0 && show_player">
@if (playlist.length > 0 && show_player) {
<div style="height: 100%">
<div style="height: 100%" [ngClass]="(currentItem.type === 'audio/mp3') ? null : 'container-video'">
<div style="max-width: 100%; margin-left: 0px; height: 100%">
<mat-drawer-container style="height: 100%" class="example-container" autosize>
@@ -6,59 +7,80 @@
<vg-player style="height: fit-content; max-height: 75vh" (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(currentItem.type === 'audio/mp3') ? postsService.theme.drawer_color : 'black'">
<video [ngClass]="(currentItem.type === 'audio/mp3') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="$any(media)" [src]="currentItem.src" id="singleVideo" preload="auto" controls playsinline>
</video>
<app-skip-ad-button *ngIf="postsService['config']['API']['use_sponsorblock_API'] && api && playlist?.length > 0 && playlist[currentIndex]['type'] === 'video/mp4'" (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [current_video]="playlist[currentIndex]" [playback_timestamp]="api.currentTime" class="skip-ad-button"></app-skip-ad-button>
@if (postsService['config']['API']['use_sponsorblock_API'] && api && playlist?.length > 0 && playlist[currentIndex]['type'] === 'video/mp4') {
<app-skip-ad-button (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [current_video]="playlist[currentIndex]" [playback_timestamp]="api.currentTime" class="skip-ad-button"></app-skip-ad-button>
}
</vg-player>
</div>
<div style="height: fit-content; width: 100%; margin-top: 10px;">
<div class="container">
<div class="row">
<div class="col-2 col-lg-1">
<ng-container *ngIf="db_file">{{db_file['local_view_count'] ? db_file['local_view_count']+1 : 1}}&nbsp;<ng-container i18n="View count label">views</ng-container></ng-container>
@if (db_file) {
{{db_file['local_view_count'] ? db_file['local_view_count']+1 : 1}}&nbsp;<ng-container i18n="View count label">views</ng-container>
}
</div>
<div style="white-space: pre-line;" class="col-8 col-lg-9">
<ng-container *ngIf="db_file && db_file['description']">
@if (db_file && db_file['description']) {
<p>
<app-see-more [text]="db_file['description']"></app-see-more>
</p>
</ng-container>
<ng-container *ngIf="!db_file || !db_file['description']">
} @else {
<p i18n="No description" style="text-align: center;">
No description available.
</p>
</ng-container>
}
</div>
<div class="col-2">
<span class="buttons" *ngIf="db_playlist">
@if (db_playlist) {
<span class="buttons">
<button (click)="downloadContent()" [disabled]="downloading" mat-icon-button><mat-icon>save</mat-icon></button>
<mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner>
<button *ngIf="(!postsService.isLoggedIn || postsService.permissions.includes('sharing')) && !auto" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
@if (downloading) {
<mat-spinner class="spinner" [diameter]="35"></mat-spinner>
}
@if ((!postsService.isLoggedIn || postsService.permissions.includes('sharing')) && !auto) {
<button (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
}
</span>
<span class="buttons" *ngIf="db_file">
}
@if (db_file) {
<span class="buttons">
<button (click)="downloadFile()" [disabled]="downloading" mat-icon-button><mat-icon>cloud_download</mat-icon></button>
<mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner>
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('sharing')" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
@if (downloading) {
<mat-spinner class="spinner" [diameter]="35"></mat-spinner>
}
@if (!postsService.isLoggedIn || postsService.permissions.includes('sharing')) {
<button (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
}
</span>
<ng-container *ngIf="db_file || playlist[currentIndex]"></ng-container>
<button (click)="openFileInfoDialog()" *ngIf="db_file || db_playlist" mat-icon-button><mat-icon>info</mat-icon></button>
<button *ngIf="db_file && db_file.url.includes('twitch.tv')" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
}
@if (db_file || db_playlist) {
<button (click)="openFileInfoDialog()" mat-icon-button><mat-icon>info</mat-icon></button>
}
@if (db_file && db_file.url.includes('twitch.tv')) {
<button (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
}
</div>
</div>
</div>
</div>
<div style="height: fit-content; width: 100%; margin-top: 10px;">
<mat-button-toggle-group cdkDropList [cdkDropListSortingDisabled]="true" (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>
@for (playlist_item of playlist; track playlist_item; let i = $index) {
<mat-button-toggle cdkDrag [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>
</div>
<app-concurrent-stream *ngIf="db_file && api && postsService.config" (setPlaybackRate)="setPlaybackRate($event)" (togglePlayback)="togglePlayback($event)" (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [playing]="api.state === 'playing'" [uid]="uid" [playback_timestamp]="api.time.current/1000" [server_mode]="!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn"></app-concurrent-stream>
@if (db_file && api && postsService.config) {
<app-concurrent-stream (setPlaybackRate)="setPlaybackRate($event)" (togglePlayback)="togglePlayback($event)" (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [playing]="api.state === 'playing'" [uid]="uid" [playback_timestamp]="api.time.current/1000" [server_mode]="!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn"></app-concurrent-stream>
}
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists']">
<ng-container *ngIf="api_ready && db_file && db_file.url.includes('twitch.tv')">
@if (api_ready && db_file && db_file.url.includes('twitch.tv')) {
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime" [sub]="subscription"></app-twitch-chat>
</ng-container>
}
</mat-drawer>
</mat-drawer-container>
</div>
</div>
</div>
}

View File

@@ -8,6 +8,7 @@ import { ShareMediaDialogComponent } from '../dialogs/share-media-dialog/share-m
import { DatabaseFile, FileType, Playlist } from '../../api-types';
import { TwitchChatComponent } from 'app/components/twitch-chat/twitch-chat.component';
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
import { saveAs } from 'file-saver';
export interface IMedia {
title: string;

View File

@@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { THEMES_CONFIG } from '../themes';
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Router, ActivatedRouteSnapshot } from '@angular/router';
import { DOCUMENT } from '@angular/common';
import { BehaviorSubject, Observable } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -122,7 +122,7 @@ import { MatDrawerMode } from '@angular/material/sidenav';
import { environment } from '../environments/environment';
@Injectable()
export class PostsService implements CanActivate {
export class PostsService {
path = '';
// local settings

View File

@@ -3,7 +3,8 @@
<!-- Server -->
<mat-tab label="Main" i18n-label="Main settings label">
<ng-template matTabContent style="padding: 15px;">
<div *ngIf="new_config" class="container-fluid">
@if (new_config) {
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-form-field class="text-field" color="accent">
@@ -22,7 +23,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['multi_user_mode']"><ng-container i18n="Multi user mode setting">Multi-user mode</ng-container></mat-checkbox>
@@ -37,7 +38,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-checkbox color="accent" [(ngModel)]="new_config['Subscriptions']['allow_subscriptions']"><ng-container i18n="Allow subscriptions setting">Allow subscriptions</ng-container></mat-checkbox>
@@ -62,7 +63,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-form-field>
@@ -78,13 +79,15 @@
</div>
</div>
</div>
}
</ng-template>
</mat-tab>
<!-- Downloader -->
<mat-tab label="Downloader" i18n-label="Downloader settings label">
<ng-template matTabContent>
<!-- Downloader -->
<div *ngIf="new_config" class="container-fluid">
@if (new_config) {
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-form-field class="text-field" color="accent">
@@ -93,7 +96,6 @@
<mat-hint><ng-container i18n="Aduio path setting input hint">Path for audio only downloads. It is relative to YTDL-Material's root folder.</ng-container></mat-hint>
</mat-form-field>
</div>
<div class="col-12 mt-3">
<mat-form-field class="text-field" color="accent">
<mat-label i18n="Video folder path">Video folder path</mat-label>
@@ -101,7 +103,6 @@
<mat-hint><ng-container i18n="Video path setting input hint">Path for video downloads. It is relative to YTDL-Material's root folder.</ng-container></mat-hint>
</mat-form-field>
</div>
<div class="col-12 mt-3 mb-1">
<mat-form-field class="text-field" color="accent">
<mat-label i18n="Default file output">Default file output</mat-label>
@@ -112,7 +113,6 @@
</mat-hint>
</mat-form-field>
</div>
<div class="col-12 mt-4 mb-5">
<mat-form-field class="text-field" style="margin-right: 12px;" color="accent">
<mat-label i18n="Global custom args">Global custom args</mat-label>
@@ -124,12 +124,14 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<h6 i18n="Categories">Categories</h6>
<div *ngIf="postsService.categories && postsService.categories.length > 0" cdkDropList class="category-list" (cdkDropListDropped)="dropCategory($event)">
<div class="category-box" *ngFor="let category of postsService.categories" cdkDrag>
@if (postsService.categories && postsService.categories.length > 0) {
<div cdkDropList class="category-list" (cdkDropListDropped)="dropCategory($event)">
@for (category of postsService.categories; track category) {
<div class="category-box" cdkDrag>
<div class="category-custom-placeholder" *cdkDragPlaceholder></div>
{{category['name']}}
<span style="float: right">
@@ -137,7 +139,9 @@
<button mat-icon-button (click)="deleteCategory(category)"><mat-icon>cancel</mat-icon></button>
</span>
</div>
}
</div>
}
<button style="margin-top: 10px;" mat-mini-fab (click)="openAddCategoryDialog()"><mat-icon>add</mat-icon></button>
</div>
<div class="col-12 mt-2 mb-2">
@@ -146,23 +150,21 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<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>
</div>
<div class="col-12 mt-2">
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['include_thumbnail']"><ng-container i18n="Include thumbnail setting">Include thumbnail</ng-container></mat-checkbox>
</div>
<div class="col-12 mt-2 mb-2">
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['include_metadata']"><ng-container i18n="Include metadata setting">Include metadata</ng-container></mat-checkbox>
</div>
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3 mb-4">
<mat-form-field class="text-field" color="accent">
@@ -181,19 +183,21 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<button (click)="killAllDownloads()" mat-stroked-button color="warn"><ng-container i18n="Kill all downloads button">Kill all downloads</ng-container></button>
</div>
</div>
</div>
}
</ng-template>
</mat-tab>
<!-- Extra -->
<mat-tab label="Extra" i18n-label="Extra settings label">
<ng-template matTabContent>
<div *ngIf="new_config" class="container-fluid">
@if (new_config) {
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-form-field class="text-field" color="accent">
@@ -220,7 +224,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['use_API_key']"><ng-container i18n="Enable Public API key setting">Enable Public API</ng-container></mat-checkbox>
@@ -240,7 +244,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['use_youtube_API']"><ng-container i18n="Use YouTube API setting">Use YouTube API</ng-container></mat-checkbox>
@@ -264,7 +268,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<h6>RSS Feed</h6>
@@ -276,7 +280,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<h6>Chrome</h6>
@@ -299,50 +303,58 @@
</div>
</div>
</div>
}
</ng-template>
</mat-tab>
<!-- Database -->
<mat-tab label="Database" i18n-label="Database settings label">
<ng-template matTabContent>
<div *ngIf="new_config" class="container-fluid">
@if (new_config) {
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<div *ngIf="db_info">
@if (db_info) {
<div>
<p><ng-container i18n="Database location label">Database location:</ng-container>&nbsp;<strong>{{db_info['using_local_db'] ? 'Local' : 'MongoDB'}}</strong></p>
<h6 i18n="Records per table label">Records per table</h6>
<mat-list style="padding-top: 0px">
<mat-list-item style="height: 28px" *ngFor="let table_stats of db_info['stats_by_table'] | keyvalue">
@for (table_stats of db_info['stats_by_table'] | keyvalue; track table_stats) {
<mat-list-item style="height: 28px">
{{table_stats.key}}: {{table_stats.value.records_count}}
</mat-list-item>
}
</mat-list>
<mat-form-field style="width: 100%; margin-top: 15px; margin-bottom: 10px" color="accent">
<mat-label i18n="MongoDB Connection String">MongoDB Connection String</mat-label>
<input [(ngModel)]="new_config['Database']['mongodb_connection_string']" matInput required>
<mat-hint><ng-container i18n="MongoDB Connection String setting hint AKA preamble">Example:</ng-container>&nbsp;mongodb://127.0.0.1:27017/?compressors=zlib<br>Docker: mongodb://&lt;container name&gt;:27017/?compressors=zlib</mat-hint>
</mat-form-field>
<div class="test-connection-div">
<button (click)="testConnectionString(new_config['Database']['mongodb_connection_string'])" [disabled]="testing_connection_string" mat-flat-button color="accent"><ng-container i18n="Test connection string button">Test connection string</ng-container></button>
<mat-spinner class="test-connection-spinner" style="margin-left: 10px" *ngIf="testing_connection_string" [diameter]="25"></mat-spinner>
@if (testing_connection_string) {
<mat-spinner class="test-connection-spinner" style="margin-left: 10px" [diameter]="25"></mat-spinner>
}
</div>
<div class="transfer-db-div">
<button [disabled]="db_transferring" color="accent" (click)="transferDB()" mat-raised-button><ng-container i18n="Transfer DB button">Transfer DB to </ng-container>{{db_info['using_local_db'] ? 'MongoDB' : 'Local'}}</button>
</div>
</div>
<div *ngIf="!db_info">
} @else {
<div>
<ng-container i18n="Database info not retrieved error message">Database information could not be retrieved. Check the server logs for more information.</ng-container>
</div>
}
</div>
</div>
</div>
}
</ng-template>
</mat-tab>
<!-- Notifications -->
<mat-tab label="Notifications" i18n-label="Notifications settings label">
<ng-template matTabContent>
<div *ngIf="new_config" class="container-fluid">
@if (new_config) {
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<div><a target="_blank" href="https://github.com/Tzahi12345/YoutubeDL-Material/wiki/Notifications"><ng-container i18n="Documentation">Documentation</ng-container></a></div>
@@ -435,12 +447,14 @@
</div>
</div>
</div>
}
</ng-template>
</mat-tab>
<!-- Advanced -->
<mat-tab label="Advanced" i18n-label="Host settings label">
<ng-template matTabContent>
<div *ngIf="new_config" class="container-fluid">
@if (new_config) {
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-form-field>
@@ -500,7 +514,7 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-2 mb-2">
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['use_cookies']"><ng-container i18n="Use cookies setting">Use Cookies</ng-container></mat-checkbox>
@@ -509,17 +523,18 @@
</div>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid mt-3">
<div class="container-fluid mt-3">
<app-updater></app-updater>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-4">
<button (click)="restartServer()" mat-stroked-button color="warn"><ng-container i18n="Restart server button">Restart server</ng-container></button>
</div>
</div>
</div>
}
</ng-template>
</mat-tab>
<mat-tab [disabled]="!postsService.config?.Advanced.multi_user_mode">
@@ -528,8 +543,9 @@
<ng-container i18n="Users settings label">Users</ng-container>
</div>
</ng-template>
<ng-container *ngIf="postsService.config?.Advanced.multi_user_mode">
<div *ngIf="new_config" style="margin-top: 24px; margin-bottom: -25px;">
@if (postsService.config?.Advanced.multi_user_mode) {
@if (new_config) {
<div style="margin-top: 24px; margin-bottom: -25px;">
<div>
<mat-checkbox color="accent" [(ngModel)]="new_config['Users']['allow_registration']"><ng-container i18n="Allow registration setting">Allow user registration</ng-container></mat-checkbox>
</div>
@@ -545,7 +561,8 @@
</mat-option>
</mat-select>
</mat-form-field>
<div *ngIf="new_config['Users']['auth_method'] === 'ldap'">
@if (new_config['Users']['auth_method'] === 'ldap') {
<div>
<div>
<mat-form-field>
<mat-label i18n="LDAP URL">LDAP URL</mat-label>
@@ -577,20 +594,23 @@
</mat-form-field>
</div>
</div>
}
<mat-divider></mat-divider>
</div>
<app-modify-users *ngIf="new_config"></app-modify-users>
</ng-container>
<app-modify-users></app-modify-users>
}
}
</mat-tab>
<mat-tab *ngIf="postsService.config" label="Logs" i18n-label="Logs settings label">
@if (postsService.config) {
<mat-tab label="Logs" i18n-label="Logs settings label">
<ng-template matTabContent>
<div style="margin-top: 15px; height: 84%;">
<app-logs-viewer></app-logs-viewer>
</div>
</ng-template>
</mat-tab>
}
</mat-tab-group>
<div class="action-buttons">
<button style="margin-left: 10px; height: 37.3px" color="accent" (click)="saveSettings()" [disabled]="settingsSame()" mat-raised-button><mat-icon>done</mat-icon>&nbsp;&nbsp;
<ng-container i18n="Settings save button">Save</ng-container>
@@ -599,4 +619,3 @@
<span i18n="Settings cancel button">Cancel</span>
</button>
</div>

View File

@@ -1,27 +1,38 @@
<div style="margin-top: 14px;">
<button class="back-button" (click)="goBack()" mat-icon-button><mat-icon>arrow_back</mat-icon></button>
<div style="margin-bottom: 15px;">
<h2 style="text-align: center;" *ngIf="subscription">
{{subscription.name}}&nbsp;<ng-container *ngIf="subscription.paused" i18n="Paused suffix">(Paused)</ng-container>
@if (subscription) {
<h2 style="text-align: center;">
{{subscription.name}}
@if (subscription.paused) {
&nbsp;<ng-container i18n="Paused suffix">(Paused)</ng-container>
}
<button class="edit-button" (click)="editSubscription()" [disabled]="downloading" matTooltip="Edit" i18n-matTooltip="Edit" mat-icon-button><mat-icon class="save-icon">edit</mat-icon></button>
</h2>
<mat-progress-bar style="width: 80%; margin: 0 auto; margin-top: 15px;" *ngIf="subscription && subscription.downloading" mode="indeterminate"></mat-progress-bar>
}
@if (subscription && subscription.downloading) {
<mat-progress-bar style="width: 80%; margin: 0 auto; margin-top: 15px;" mode="indeterminate"></mat-progress-bar>
}
</div>
<mat-divider style="width: 80%; margin: 0 auto"></mat-divider>
<br/>
<!-- Extra margin added for floating buttons to have room -->
<div style="margin-bottom: 100px;" *ngIf="subscription">
@if (subscription) {
<div style="margin-bottom: 100px;">
<app-recent-videos #recentVideos [sub_id]="subscription.id"></app-recent-videos>
</div>
}
<div class="check-button">
<ng-container *ngIf="subscription.downloading">
@if (subscription.downloading) {
<button color="primary" (click)="cancelCheckSubscription()" [disabled]="cancel_clicked" matTooltip="Cancel subscription check" i18n-matTooltip="Cancel subscription check" mat-fab><mat-icon class="save-icon">cancel</mat-icon></button>
</ng-container>
<ng-container *ngIf="!subscription.downloading">
} @else {
<button color="primary" (click)="checkSubscription()" [disabled]="check_clicked" matTooltip="Check subscription" i18n-matTooltip="Check subscription" mat-fab><mat-icon class="save-icon">youtube_searched_for</mat-icon></button>
</ng-container>
}
</div>
<button class="watch-button" color="primary" (click)="watchSubscription()" matTooltip="Play all" i18n-matTooltip="Play all" mat-fab><mat-icon class="save-icon">video_library</mat-icon></button>
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" matTooltip="Download zip" i18n-matTooltip="Download zip" 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" matTooltip="Download zip" i18n-matTooltip="Download zip" mat-fab><mat-icon class="save-icon">save</mat-icon>
@if (downloading) {
<mat-spinner class="spinner" [diameter]="50"></mat-spinner>
}
</button>
</div>

View File

@@ -4,6 +4,7 @@ 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';
import { Subscription } from 'api-types';
import { saveAs } from 'file-saver';
@Component({
selector: 'app-subscription',

View File

@@ -1,18 +1,23 @@
<br/>
<h2 i18n="Subscriptions title" style="text-align: center; margin-bottom: 15px;">Your subscriptions</h2>
<mat-divider style="width: 80%; margin: 0 auto"></mat-divider>
<br/>
<h4 i18n="Subscriptions channels title" style="text-align: center;">Channels</h4>
<mat-nav-list class="sub-nav-list">
<mat-list-item *ngFor="let sub of channel_subscriptions" style="pointer-events: none">
@for (sub of channel_subscriptions; track sub) {
<mat-list-item style="pointer-events: none">
<a style="pointer-events: auto;" class="a-list-item" matListItemTitle (click)="goToSubscription(sub)">
<strong *ngIf="sub.name">{{ sub.name }}&nbsp;<ng-container *ngIf="sub.paused" i18n="Paused suffix">(Paused)</ng-container></strong>
<div *ngIf="!sub.name">
@if (sub.name) {
<strong>{{ sub.name }}
@if (sub.paused) {
&nbsp;<ng-container i18n="Paused suffix">(Paused)</ng-container>
}
</strong>
} @else {
<div>
<ng-container i18n="Subscription playlist not available text">Name not available. Channel retrieval in progress.</ng-container>
</div>
}
</a>
<div style="pointer-events: auto; color: unset" matListItemMeta>
<button matTooltip="Edit" i18n-matTooltip="Edit" mat-icon-button (click)="editSubscription(sub)">
@@ -23,20 +28,29 @@
</button>
</div>
</mat-list-item>
}
</mat-nav-list>
<div style="width: 80%; margin: 0 auto; padding-left: 15px;" *ngIf="channel_subscriptions.length === 0 && subscriptions">
@if (channel_subscriptions.length === 0 && subscriptions) {
<div style="width: 80%; margin: 0 auto; padding-left: 15px;">
<p i18n="No channel subscriptions text">You have no channel subscriptions.</p>
</div>
}
<h4 i18n="Subscriptions playlists title" style="text-align: center; margin-top: 10px;">Playlists</h4>
<mat-nav-list class="sub-nav-list">
<mat-list-item *ngFor="let sub of playlist_subscriptions" style="pointer-events: none">
@for (sub of playlist_subscriptions; track sub) {
<mat-list-item style="pointer-events: none">
<a style="pointer-events: auto;" class="a-list-item" matListItemTitle (click)="goToSubscription(sub)">
<strong *ngIf="sub.name">{{ sub.name }}&nbsp;<ng-container *ngIf="sub.paused" i18n="Paused suffix">(Paused)</ng-container></strong>
<div *ngIf="!sub.name">
@if (sub.name) {
<strong>{{ sub.name }}
@if (sub.paused) {
&nbsp;<ng-container i18n="Paused suffix">(Paused)</ng-container>
}
</strong>
} @else {
<div>
<ng-container i18n="Subscription playlist not available text">Name not available. Channel retrieval in progress.</ng-container>
</div>
}
</a>
<div style="pointer-events: auto; color: unset" matListItemMeta>
<button matTooltip="Edit" i18n-matTooltip="Edit" mat-icon-button (click)="editSubscription(sub)">
@@ -47,14 +61,16 @@
</button>
</div>
</mat-list-item>
}
</mat-nav-list>
<div style="width: 80%; margin: 0 auto; padding-left: 15px;" *ngIf="playlist_subscriptions.length === 0 && subscriptions">
@if (playlist_subscriptions.length === 0 && subscriptions) {
<div style="width: 80%; margin: 0 auto; padding-left: 15px;">
<p i18n="No playlist subscriptions text">You have no playlist subscriptions.</p>
</div>
<div style="margin: 0 auto; width: 80%" *ngIf="subscriptions_loading">
}
@if (subscriptions_loading) {
<div style="margin: 0 auto; width: 80%">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
}
<button class="add-subscription-button" (click)="openSubscribeDialog()" matTooltip="Add subscription" i18n-matTooltip="Add subscription" mat-fab><mat-icon>add</mat-icon></button>

View File

@@ -2,17 +2,27 @@
<div style="display: inline-block">
<ng-container i18n="Select a version">Select a version:</ng-container>
</div>
<div *ngIf="availableVersions" style="display: inline-block; margin-left: 15px;">
@if (availableVersions) {
<div style="display: inline-block; margin-left: 15px;">
<mat-form-field>
<mat-select [(ngModel)]="selectedVersion">
<mat-option *ngFor="let version of availableVersionsFiltered" [value]="version['tag_name']">
@for (version of availableVersionsFiltered; track version) {
<mat-option [value]="version['tag_name']">
{{version['tag_name'] + (version === latestStableRelease ? ' - Latest Stable' : '') + (version['tag_name'] === CURRENT_VERSION ? ' - Current Version' : '')}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div *ngIf="selectedVersion && selectedVersion !== CURRENT_VERSION" style="display: inline-block; margin-left: 15px;">
}
@if (selectedVersion && selectedVersion !== CURRENT_VERSION) {
<div style="display: inline-block; margin-left: 15px;">
<button (click)="updateServer()" color="accent" mat-raised-button><mat-icon>update</mat-icon>&nbsp;&nbsp;
<ng-container *ngIf="selectedVersion > CURRENT_VERSION">Upgrade to</ng-container><ng-container *ngIf="selectedVersion < CURRENT_VERSION">Downgrade to</ng-container>&nbsp;{{selectedVersion}}</button>
@if (selectedVersion > CURRENT_VERSION) {
<ng-container i18n="Upgrade to">Upgrade to</ng-container>
} @else {
<ng-container i18n="Downgrade to">Downgrade to</ng-container>
}&nbsp;{{selectedVersion}}</button>
</div>
}
</div>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version='1.0' encoding='UTF-8'?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template" target-language="cs">
<body>
@@ -2512,42 +2512,6 @@
</context-group>
<note priority="1" from="description">Video duration label</note>
</trans-unit>
<trans-unit id="6549265851868599441" datatype="html" xml:space="preserve">
<source>Video</source>
<target state="translated">Video</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">40</context>
</context-group>
</trans-unit>
<trans-unit id="347407180135731058" datatype="html" xml:space="preserve">
<source>Audio</source>
<target state="translated">Audio</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="3159807825117518005" datatype="html" xml:space="preserve">
<source>Delete archives</source>
<target state="translated">Odstranit archivy</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">152</context>
</context-group>
</trans-unit>
<trans-unit id="7022070615528435141" datatype="html" xml:space="preserve">
<source>Delete</source>
<target state="translated">Odstranit</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">154</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -224,7 +224,7 @@
</trans-unit>
<trans-unit id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
<source>Settings</source>
<target state="translated" state-qualifier="leveraged-glossary">Ajustes</target>
<target state="translated" state-qualifier="leveraged-glossary">Configuraciones</target>
</trans-unit>
<trans-unit id="801b98c6f02fe3b32f6afa3ee854c99ed83474e6">
<source>URL</source>
@@ -532,7 +532,7 @@
</trans-unit>
<trans-unit id="6765b4c916060f6bc42d9bb69e80377dbcb5e4e9">
<source>Login</source>
<target state="translated">Iniciar Sesión</target>
<target state="translated">Identificarse</target>
</trans-unit>
<trans-unit id="bb694b49d408265c91c62799c2b3a7e3151c824d">
<source>Logout</source>
@@ -1800,7 +1800,7 @@
</trans-unit>
<trans-unit id="4027175717527633324" datatype="html">
<source>Getting info</source>
<target state="translated">Obteniendo info</target>
<target>Cogiendo info</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">59</context>
@@ -2530,7 +2530,7 @@
</trans-unit>
<trans-unit id="3961621815065792326" datatype="html">
<source>Failed to clear finished downloads!</source>
<target state="translated">¡No se pudo limpiar las descargas finalizadas!</target>
<target state="translated">¡Error al limpiar las descargas finalizadas!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">157</context>
@@ -2546,7 +2546,7 @@
</trans-unit>
<trans-unit id="5223827577229167333" datatype="html">
<source>Failed to pause all downloads! See server logs for more info.</source>
<target state="translated">¡No se pudo pausar todas las descargas! Vea el registro del servidor para más información.</target>
<target state="translated">¡No se pudieron pausar todas las descargas! Vea el registro del servidor para más información.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">178</context>
@@ -2734,7 +2734,7 @@
</trans-unit>
<trans-unit id="39921032161993566" datatype="html">
<source>Successfully created playlist!</source>
<target state="translated">¡Lista de reproducción creada exitosamente!</target>
<target state="translated">¡Lista de reproducción creada con éxito!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">56</context>
@@ -2742,7 +2742,7 @@
</trans-unit>
<trans-unit id="7114033980971410157" datatype="html">
<source>Failed to pause download! See server logs for more info.</source>
<target state="translated">¡No se pudo pausar las descargas! Vea el registro del servidor para más información.</target>
<target state="translated">¡Error al pausar las descargas! Vea el registro del servidor para más información.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">170</context>
@@ -3169,7 +3169,7 @@
</trans-unit>
<trans-unit id="4b3972c3e9485218508a95f7a4ce7758e3f09ced" datatype="html">
<source>Upload</source>
<target state="translated">Subir</target>
<target state="translated">Cargado</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">137</context>
@@ -3206,7 +3206,7 @@
</trans-unit>
<trans-unit id="3159807825117518005" datatype="html">
<source>Delete archives</source>
<target state="translated">Borrar archivos</target>
<target state="translated">Borrar los archivos</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">152</context>
@@ -3962,7 +3962,7 @@
</trans-unit>
<trans-unit id="8456659390937171831" datatype="html">
<source>Show error</source>
<target state="translated">Mostrar error</target>
<target state="translated">Mostrar el error</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">56</context>

View File

@@ -2172,18 +2172,16 @@
</context-group>
<note priority="1" from="description">Simulated args title</note>
</trans-unit>
<trans-unit id="0b71824ae71972f236039bed43f8d2323e8fd570" datatype="html" xml:space="preserve">
<trans-unit id="0b71824ae71972f236039bed43f8d2323e8fd570" datatype="html">
<source>Add an arg</source>
<target state="translated">新增參數</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html</context>
<context context-type="linenumber">37</context>
</context-group>
<note priority="1" from="description">Add arg card title</note>
</trans-unit>
<trans-unit id="d618f383a0ea2458eeb945a85190d4a002ea394b" datatype="html" xml:space="preserve">
<trans-unit id="d618f383a0ea2458eeb945a85190d4a002ea394b" datatype="html">
<source>Arg</source>
<target state="translated">參數</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html</context>
<context context-type="linenumber">41</context>

View File

@@ -100,9 +100,9 @@
</context-group>
<note priority="1" from="description">Arg value placeholder</note>
</trans-unit>
<trans-unit id="7de2451ed3fb8d8b847979bd3f0c740b970f167b" datatype="html" xml:space="preserve">
<trans-unit id="7de2451ed3fb8d8b847979bd3f0c740b970f167b" datatype="html">
<source>Add arg</source>
<target state="translated">Ajouter un argument</target>
<target>Ajouter</target>
<context-group purpose="location">
<context context-type="sourcefile">app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html</context>
<context context-type="linenumber">73</context>
@@ -1332,9 +1332,9 @@
</context-group>
<note priority="1" from="description">About bug prefix</note>
</trans-unit>
<trans-unit id="e1f398f38ff1534303d4bb80bd6cece245f24016" datatype="html" xml:space="preserve">
<trans-unit id="e1f398f38ff1534303d4bb80bd6cece245f24016" datatype="html">
<source>to create an issue!</source>
<target state="translated">pour signaler un problème!</target>
<target>pour signaler un problème !</target>
<context-group purpose="location">
<context context-type="sourcefile">app/dialogs/about-dialog/about-dialog.component.html</context>
<context context-type="linenumber">25</context>
@@ -1995,9 +1995,9 @@
</context-group>
<note priority="1" from="description">Actions users table header</note>
</trans-unit>
<trans-unit id="4d92a0395dd66778a931460118626c5794a3fc7a" datatype="html" xml:space="preserve">
<trans-unit id="4d92a0395dd66778a931460118626c5794a3fc7a" datatype="html">
<source>Add Users</source>
<target state="translated">Ajout d'utilisateurs</target>
<target>Ajouter</target>
<context-group purpose="location">
<context context-type="sourcefile">app/components/modify-users/modify-users.component.html</context>
<context context-type="linenumber">90</context>
@@ -3010,9 +3010,9 @@
</context-group>
<note priority="1" from="description">Title</note>
</trans-unit>
<trans-unit id="47bbc861efa59ba4135e6aa8f63213420e3f3b91" datatype="html" xml:space="preserve">
<trans-unit id="47bbc861efa59ba4135e6aa8f63213420e3f3b91" datatype="html">
<source>Subscription</source>
<target state="translated">Abonnement</target>
<target>Souscription</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">23</context>
@@ -4006,9 +4006,9 @@
<context context-type="linenumber">189</context>
</context-group>
</trans-unit>
<trans-unit id="8314249599019746316" datatype="html" xml:space="preserve">
<trans-unit id="8314249599019746316" datatype="html">
<source>Download failed!</source>
<target state="translated">Le téléchargement a échoué!</target>
<target state="translated">Échec du téléchargement!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.ts</context>
<context context-type="linenumber">387</context>
@@ -4203,898 +4203,6 @@
<context context-type="linenumber">159</context>
</context-group>
</trans-unit>
<trans-unit id="c748ac656af9f13998206ef2c52018dd418b0483" datatype="html" xml:space="preserve">
<source>Archives</source>
<target state="translated">Archives</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="c41475a25c9f9d9639db9efa56637603a77528b4" datatype="html" xml:space="preserve">
<source>Download archive</source>
<target state="translated">Télécharger l'archive</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">80</context>
</context-group>
</trans-unit>
<trans-unit id="8953483585652369683" datatype="html" xml:space="preserve">
<source>Archive successfully imported!</source>
<target state="translated">Archive importée avec succès!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">130</context>
</context-group>
</trans-unit>
<trans-unit id="2525880134753073592" datatype="html" xml:space="preserve">
<source>Successfully deleted archive items!</source>
<target state="translated">Les archives ont bien été supprimées!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">172</context>
</context-group>
</trans-unit>
<trans-unit id="7022070615528435141" datatype="html" xml:space="preserve">
<source>Delete</source>
<target state="translated">Supprimer</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">154</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="5a105e7bd7e7db6ea211fe950fc9f317379acceb" datatype="html" xml:space="preserve">
<source>No notifications available</source>
<target state="translated">Aucune notification</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="8953033926734869941" datatype="html" xml:space="preserve">
<source>Name</source>
<target state="translated">Nom</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="7b4585a9072f3c1292972c14a3d0e14978fbfc9c" datatype="html" xml:space="preserve">
<source>Delete old files:</source>
<target state="translated">Supprimer les anciens fichiers :</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/tasks/tasks.component.html</context>
<context context-type="linenumber">66</context>
</context-group>
</trans-unit>
<trans-unit id="378c072ce05889c9771718d05106e7685fcd3507" datatype="html" xml:space="preserve">
<source>Medium</source>
<target state="translated">Moyenne</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">47,49</context>
</context-group>
</trans-unit>
<trans-unit id="9a865c2922f5c01899d06c472dba2e5bd63bcff9" datatype="html" xml:space="preserve">
<source>Small</source>
<target state="translated">Petite</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">50,52</context>
</context-group>
</trans-unit>
<trans-unit id="3371159074051387771" datatype="html" xml:space="preserve">
<source>Failed to delete <x id="category name" equiv-text="category['name']"/>!</source>
<target state="translated">Échec de la suppression de <x id="category name" equiv-text="category['name']"/>!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">172</context>
</context-group>
</trans-unit>
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html" xml:space="preserve">
<source>Filter</source>
<target state="translated">Filtres</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c" datatype="html" xml:space="preserve">
<source>ID</source>
<target state="translated">ID</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">47</context>
</context-group>
</trans-unit>
<trans-unit id="51a161ce175abcd44f6c6cbd0e996681bf553ac3" datatype="html" xml:space="preserve">
<source>Delete selected</source>
<target state="translated">Supprimer la sélection</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html" xml:space="preserve">
<source>Extractor</source>
<target state="translated">Extracteur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="c150a30bbbdb175b4d08820196a9acb66b167653" datatype="html" xml:space="preserve">
<source>Archives empty</source>
<target state="translated">Archives vides</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="a2f14a73f7a6e94479f67423cc51102da8d6f524" datatype="html" xml:space="preserve">
<source>None</source>
<target state="translated">Aucun</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">84</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">126</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">36</context>
</context-group>
</trans-unit>
<trans-unit id="4b3972c3e9485218508a95f7a4ce7758e3f09ced" datatype="html" xml:space="preserve">
<source>Upload</source>
<target state="translated">Charger</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">137</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="6549265851868599441" datatype="html" xml:space="preserve">
<source>Video</source>
<target state="translated">Vidéo</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">40</context>
</context-group>
</trans-unit>
<trans-unit id="347407180135731058" datatype="html" xml:space="preserve">
<source>Audio</source>
<target state="translated">Audio</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="3159807825117518005" datatype="html" xml:space="preserve">
<source>Delete archives</source>
<target state="translated">Supprimer les archives</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">152</context>
</context-group>
</trans-unit>
<trans-unit id="8425787787095143143" datatype="html" xml:space="preserve">
<source>Would you like to delete <x id="selected archives amount" equiv-text="this.selection.selected.length"/> archive(s)?</source>
<target state="translated">Voulez-vous supprimer <x id="selected archives amount" equiv-text="this.selection.selected.length"/> archive(s)?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">153</context>
</context-group>
</trans-unit>
<trans-unit id="8224301330941792118" datatype="html" xml:space="preserve">
<source>Failed to delete archive items!</source>
<target state="translated">Échec de la suppression des archives!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit id="019d4bd6a5690f0cfa0ecf346a4e6bf7f0d8debb" datatype="html" xml:space="preserve">
<source>Remove</source>
<target state="translated">Reprendre</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.html</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="8564202903947049539" datatype="html" xml:space="preserve">
<source>Play</source>
<target state="translated">Lecture</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="8643601595923420698" datatype="html" xml:space="preserve">
<source>Retry download</source>
<target state="translated">Réessayer le téléchargement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="6876310993601590130" datatype="html" xml:space="preserve">
<source>Download completed</source>
<target state="translated">Téléchargement terminé</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="1879058637439215882" datatype="html" xml:space="preserve">
<source>Download error</source>
<target state="translated">Erreur de téléchargement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="4578192247039196794" datatype="html" xml:space="preserve">
<source>Task</source>
<target state="translated">Tâche</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="5000203534763292992" datatype="html" xml:space="preserve">
<source>Download restarted!</source>
<target state="translated">Le téléchargement a redémarré!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="3533826530554274875" datatype="html" xml:space="preserve">
<source>Upload Date</source>
<target state="translated">Date de chargement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="c65dd978b3c7566551c0ebefb234c2d41942b847" datatype="html" xml:space="preserve">
<source>Delete files older than</source>
<target state="translated">Supprimer les fichiers antérieurs à</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">6</context>
</context-group>
</trans-unit>
<trans-unit id="56b1a3c93fb95fed1805005c561a5e431d57ffae" datatype="html" xml:space="preserve">
<source>Blacklist all files</source>
<target state="translated">Liste noire pour tous les fichiers</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="d618f383a0ea2458eeb945a85190d4a002ea394b" datatype="html" xml:space="preserve">
<source>Arg</source>
<target state="translated">Argument</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/arg-modifier-dialog/arg-modifier-dialog.component.html</context>
<context context-type="linenumber">41</context>
</context-group>
</trans-unit>
<trans-unit id="d57c023a4cf63b2f12c10328c15b636ff18929aa" datatype="html" xml:space="preserve">
<source>Best</source>
<target state="translated">Meilleure</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.html</context>
<context context-type="linenumber">24,25</context>
</context-group>
</trans-unit>
<trans-unit id="c40370dc182b5e4333828b70f7478bde58bb5cfe" datatype="html" xml:space="preserve">
<source>Enable notifications</source>
<target state="translated">Activer les notifications</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">349</context>
</context-group>
</trans-unit>
<trans-unit id="2361a4f76caaa4574803fbcdca8b0a47c91cc7ed" datatype="html" xml:space="preserve">
<source>Task finished</source>
<target state="translated">Tâche terminée</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">360</context>
</context-group>
</trans-unit>
<trans-unit id="6785427850041119037" datatype="html" xml:space="preserve">
<source>Delete category</source>
<target state="translated">Supprimer la catégorie</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">158</context>
</context-group>
</trans-unit>
<trans-unit id="0ed98b4c6ec1db6708a963e8a2699478ac97f55c" datatype="html" xml:space="preserve">
<source>Add subscription</source>
<target state="translated">Ajouter un abonnement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/subscriptions/subscriptions.component.html</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="8c6e24eab969d9f63a8a0e9d617aee3b99e28ae6" datatype="html" xml:space="preserve">
<source>Play all</source>
<target state="translated">Lire tout</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html" xml:space="preserve">
<source>Error</source>
<target state="translated">Erreur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="3640026747176198246" datatype="html" xml:space="preserve">
<source>Watch content</source>
<target state="translated">Visionner le contenu</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="8456659390937171831" datatype="html" xml:space="preserve">
<source>Show error</source>
<target state="translated">Afficher l'erreur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">56</context>
</context-group>
</trans-unit>
<trans-unit id="1236604279860679031" datatype="html" xml:space="preserve">
<source>Restart</source>
<target state="translated">Redémarrer</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">62</context>
</context-group>
</trans-unit>
<trans-unit id="9042260521669277115" datatype="html" xml:space="preserve">
<source>Pause</source>
<target state="translated">Mettre en pause</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="7182974689040833178" datatype="html" xml:space="preserve">
<source>Resume</source>
<target state="translated">Reprendre</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">74</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">80</context>
</context-group>
</trans-unit>
<trans-unit id="6219551536751479443" datatype="html" xml:space="preserve">
<source>Finished downloading</source>
<target state="translated">Fin du téléchargement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="5947241266456580665" datatype="html" xml:space="preserve">
<source>Download failed</source>
<target state="translated">Téléchargement échoué</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="8443034725057696949" datatype="html" xml:space="preserve">
<source>Task finished</source>
<target state="translated">Tâche terminée</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="8571838164752006148" datatype="html" xml:space="preserve">
<source>View error</source>
<target state="translated">Voir l'erreur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="5709555629190115111" datatype="html" xml:space="preserve">
<source>View task</source>
<target state="translated">Voir la tâche</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">33</context>
</context-group>
</trans-unit>
<trans-unit id="7911845622864460134" datatype="html" xml:space="preserve">
<source>Video only</source>
<target state="translated">Vidéo seulement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="6437411876967154040" datatype="html" xml:space="preserve">
<source>Audio only</source>
<target state="translated">Audio seulement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="4665451070906079743" datatype="html" xml:space="preserve">
<source>Favorited</source>
<target state="translated">Favoris</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="6268070779441507380" datatype="html" xml:space="preserve">
<source>Download Date</source>
<target state="translated">Date de téléchargement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="2492098975665776610" datatype="html" xml:space="preserve">
<source>File Size</source>
<target state="translated">Taille de fichier</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="7410432243549869948" datatype="html" xml:space="preserve">
<source>Duration</source>
<target state="translated">Durée</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="cdf5297d8d080a78e8b10debc5c38b7845a3cbe7" datatype="html" xml:space="preserve">
<source>Do not ask for confirmation</source>
<target state="translated">Ne pas demander de confirmation</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="ea2b65121b93921fe54692025da9b9e3ce779ad5" datatype="html" xml:space="preserve">
<source>Task settings - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></source>
<target state="translated">Paramètres de la tâche <x id="INTERPOLATION" equiv-text="{{task.title}}"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="9aa1b4779a515170b297d2c0507e6ff9d2e3e0e0" datatype="html" xml:space="preserve">
<source>Blacklist deleted subscription files</source>
<target state="translated">Liste noire pour les fichiers d'abonnement supprimés</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">14</context>
</context-group>
</trans-unit>
<trans-unit id="9176960997786930103" datatype="html" xml:space="preserve">
<source>Error for: <x id="PH" equiv-text="task['title']"/></source>
<target state="translated">Erreur pour : <x id="PH" equiv-text="task['title']"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/tasks/tasks.component.ts</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit id="11a0771f88158a540a54e0e4ec5d25733d65fc0e" datatype="html" xml:space="preserve">
<source>Favorite</source>
<target state="translated">Favori</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="1d4fa01d25990f60abf21c3a451fa8ba262b7912" datatype="html" xml:space="preserve">
<source>Unfavorite</source>
<target state="translated">Supprimer des favoris</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="c35ef0f03a863d33b04aae6807f140397a50f491" datatype="html" xml:space="preserve">
<source>Generate RSS URL</source>
<target state="translated">Générer une URL RSS</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">273</context>
</context-group>
</trans-unit>
<trans-unit id="1a9b415816364f554ee411020e65219092655271" datatype="html" xml:space="preserve">
<source>Title filter</source>
<target state="translated">Filtre de titre</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">8</context>
</context-group>
</trans-unit>
<trans-unit id="9aa62bf1a535a97a4d752bbc5cf1c31af0f0c1f7" datatype="html" xml:space="preserve">
<source>Supports regex</source>
<target state="translated">Prend en charge les expressions rationnelles</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit id="e08a77594f3d89311cdf6da5090044270909c194" datatype="html" xml:space="preserve">
<source>User</source>
<target state="translated">Utilisateur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="c2faa86201eab08b5b39b5437f96ab9432e125e7" datatype="html" xml:space="preserve">
<source>Item limit</source>
<target state="translated">Limite d'élément</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="b78a98bc54259a29cf6250dbaeab5fe11fae91cf" datatype="html" xml:space="preserve">
<source>Favorited</source>
<target state="translated">Favoris</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="8336047719608684263" datatype="html" xml:space="preserve">
<source>Unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/></source>
<target state="translated">Se désabonner de <x id="subscription name" equiv-text="this.sub['name']"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="784837056777689544" datatype="html" xml:space="preserve">
<source>Would you like to unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/>?</source>
<target state="translated">Souhaitez-vous vous désabonner de <x id="subscription name" equiv-text="this.sub['name']"/>?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="1698114086921246480" datatype="html" xml:space="preserve">
<source>Unsubscribe</source>
<target state="translated">Se désabonner</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="1091872159779006651" datatype="html" xml:space="preserve">
<source>You must input a time!</source>
<target state="translated">Vous devez indiquer une heure!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component.ts</context>
<context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="5ac5a0e5ffe8e5623b40696f4c2403c17349271f" datatype="html" xml:space="preserve">
<source>Sidepanel mode</source>
<target state="translated">Mode panneau latéral</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="338b44701a53ce3ef2f36abfb56f89c3edfa9eab" datatype="html" xml:space="preserve">
<source>Over</source>
<target state="translated">Au-dessus</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">32,34</context>
</context-group>
</trans-unit>
<trans-unit id="b6399391e706e2d7b7b7880eb5630e4e6f49728c" datatype="html" xml:space="preserve">
<source>Side</source>
<target state="translated">Sur le côté</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">35,37</context>
</context-group>
</trans-unit>
<trans-unit id="1f2809e6a99d511fdb6eaf041d785fe54d0680cc" datatype="html" xml:space="preserve">
<source>File card size</source>
<target state="translated">Taille des vignettes</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="e0a11fbea353b1ce1131161774e4a3e10bcb99b1" datatype="html" xml:space="preserve">
<source>Large</source>
<target state="translated">Grande</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">44,46</context>
</context-group>
</trans-unit>
<trans-unit id="af30e51aa8b67e1133a341ec28359be05150e65c" datatype="html" xml:space="preserve">
<source>No description available.</source>
<target state="translated">Aucune description disponible.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/player/player.component.html</context>
<context context-type="linenumber">25,27</context>
</context-group>
</trans-unit>
<trans-unit id="37469c9f3e31d95087fa22b6c9c3bc64adf1692d" datatype="html" xml:space="preserve">
<source>Enable RSS Feed</source>
<target state="translated">Activer le flux RSS</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">271</context>
</context-group>
</trans-unit>
<trans-unit id="35cf4cdcedc8ef3f94b6100e0d86836e31dbb908" datatype="html" xml:space="preserve">
<source>Force autoplay</source>
<target state="translated">Forcer la lecture automatique</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">218</context>
</context-group>
</trans-unit>
<trans-unit id="61d6b5fa4311b1c617b66dad72496f9dd43b07b4" datatype="html" xml:space="preserve">
<source>Be careful enabling this with multi-user mode! User data may be exposed.</source>
<target state="translated">Soyez prudent lorsque vous activez cette fonction en mode multi-utilisateurs! Les données de l'utilisateur peuvent être exposées.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">272</context>
</context-group>
</trans-unit>
<trans-unit id="fd467148a18f0921c10d116d4e0174fe29452be4" datatype="html" xml:space="preserve">
<source>See documentation here.</source>
<target state="translated">Voir la documentation ici.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">274</context>
</context-group>
</trans-unit>
<trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html" xml:space="preserve">
<source>Notifications</source>
<target state="translated">Notifications</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">343</context>
</context-group>
</trans-unit>
<trans-unit id="33a7c6d5ff3515fa237f1fd4e43df8b65373954d" datatype="html" xml:space="preserve">
<source>Enable all notifications</source>
<target state="translated">Activer toutes les notifications</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">352</context>
</context-group>
</trans-unit>
<trans-unit id="9c562d26e041390ecc3f49dabc51cc50ebba7469" datatype="html" xml:space="preserve">
<source>Allowed notification types</source>
<target state="translated">Types de notification autorisés</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
<trans-unit id="c5dc5fbcce45e9b1530e2a5c2baa8ebe722aef4c" datatype="html" xml:space="preserve">
<source>Download complete</source>
<target state="translated">Téléchargement terminé</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">358</context>
</context-group>
</trans-unit>
<trans-unit id="3ffd9490f3a4c0b24021d25e1dc71fcfe5d39cd6" datatype="html" xml:space="preserve">
<source>Download error</source>
<target state="translated">Erreur de téléchargement</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">359</context>
</context-group>
</trans-unit>
<trans-unit id="9e766e11a9de375907aaf566897ecc6dac393ebc" datatype="html" xml:space="preserve">
<source>Webhook URL</source>
<target state="translated">URL du webhook</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">366</context>
</context-group>
</trans-unit>
<trans-unit id="3264d82792954815be755b3da01e2625458711dc" datatype="html" xml:space="preserve">
<source>Discord Webhook URL</source>
<target state="translated">URL du Webhook Discord</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">373</context>
</context-group>
</trans-unit>
<trans-unit id="0cfc9cfe7cd8ea14bc053693b28872da739af02c" datatype="html" xml:space="preserve">
<source>See docs here.</source>
<target state="translated">Voir la documentation ici.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">375</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">382</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">392</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">402</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">409</context>
</context-group>
</trans-unit>
<trans-unit id="7cedb649779673568447b994463b2882c4e0436a" datatype="html" xml:space="preserve">
<source>Slack Webhook URL</source>
<target state="translated">URL Webhook de Slack</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">380</context>
</context-group>
</trans-unit>
<trans-unit id="8c1bf02206fbc371ff69ab1b7e35a17ba29d169d" datatype="html" xml:space="preserve">
<source>Use ntfy API</source>
<target state="translated">Utiliser l'API ntfy</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">386</context>
</context-group>
</trans-unit>
<trans-unit id="06f503e492d6dbcf59e7b9c412ca86913d718689" datatype="html" xml:space="preserve">
<source>ntfy topic URL</source>
<target state="translated">URL du sujet ntfy</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">390</context>
</context-group>
</trans-unit>
<trans-unit id="5827fde8fcafdd55ae80921ad3ad4aa01012e203" datatype="html" xml:space="preserve">
<source>Use gotify API</source>
<target state="translated">Utiliser l'API gotify</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">396</context>
</context-group>
</trans-unit>
<trans-unit id="55f559d6f666b945479f534b0c182f70cd0a8a69" datatype="html" xml:space="preserve">
<source>Gotify server URL</source>
<target state="translated">URL du serveur Gotify</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">400</context>
</context-group>
</trans-unit>
<trans-unit id="b770c48628d98cb4633d6a17e3f0ba0265376af5" datatype="html" xml:space="preserve">
<source>Gotify app token</source>
<target state="translated">Jeton d'application Gotify</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">407</context>
</context-group>
</trans-unit>
<trans-unit id="38992954440d6afb54aeb58af12ca0123ee5e26e" datatype="html" xml:space="preserve">
<source>Use Telegram API</source>
<target state="translated">Utiliser l'API Telegram</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">413</context>
</context-group>
</trans-unit>
<trans-unit id="2e076ff9866213d0815961c494aa48b177046b9d" datatype="html" xml:space="preserve">
<source>Telegram bot token</source>
<target state="translated">Jeton du bot Telegram</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">417</context>
</context-group>
</trans-unit>
<trans-unit id="eeb0ba2e4743901d8f5eebd9a3529aa1f236c608" datatype="html" xml:space="preserve">
<source>Create bot here.</source>
<target state="translated">Créer un bot ici.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">419</context>
</context-group>
</trans-unit>
<trans-unit id="144e1a21ebe8fa238f88d2ac27515ed711cfc9a0" datatype="html" xml:space="preserve">
<source>Telegram chat ID</source>
<target state="translated">ID du chat Telegram</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">424</context>
</context-group>
</trans-unit>
<trans-unit id="3e420c675b8f3f3702576d52e8bb6e8e1d3feda0" datatype="html" xml:space="preserve">
<source>How do I get the chat ID?</source>
<target state="translated">Comment obtenir l'identifiant du chat?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">426</context>
</context-group>
</trans-unit>
<trans-unit id="a4ed8eba1e057e67d5c2d87b52230f182b3dae4e" datatype="html" xml:space="preserve">
<source>Restart required.</source>
<target state="translated">Redémarrage nécessaire.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">446</context>
</context-group>
</trans-unit>
<trans-unit id="2481374649045841364" datatype="html" xml:space="preserve">
<source>Would you like to delete <x id="category name" equiv-text="category['name']"/>?</source>
<target state="translated">Souhaitez-vous supprimer <x id="category name" equiv-text="category['name']"/>?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">159</context>
</context-group>
</trans-unit>
<trans-unit id="7332320960988475089" datatype="html" xml:space="preserve">
<source>Successfully deleted <x id="category name" equiv-text="category['name']"/>!</source>
<target state="translated">Suppression réussie <x id="category name" equiv-text="category['name']"/>!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">168</context>
</context-group>
</trans-unit>
<trans-unit id="674a999dd48d7da565ffdd105602261b8a4761ea" datatype="html" xml:space="preserve">
<source>Download zip</source>
<target state="translated">Télécharger le zip</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3325,42 +3325,6 @@
</context-group>
<note priority="1" from="description">Update button</note>
</trans-unit>
<trans-unit id="c748ac656af9f13998206ef2c52018dd418b0483" datatype="html" xml:space="preserve">
<source>Archives</source>
<target state="translated">Ахриви</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<note priority="1" from="description">Archives menu label</note>
</trans-unit>
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html" xml:space="preserve">
<source>Filter</source>
<target state="translated">Филтер</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
<note priority="1" from="description">Filter</note>
</trans-unit>
<trans-unit id="c150a30bbbdb175b4d08820196a9acb66b167653" datatype="html" xml:space="preserve">
<source>Archives empty</source>
<target state="translated">Архивите се празни</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">72</context>
</context-group>
<note priority="1" from="description">Archives empty</note>
</trans-unit>
<trans-unit id="c41475a25c9f9d9639db9efa56637603a77528b4" datatype="html" xml:space="preserve">
<source>Download archive</source>
<target state="translated">Симни архива</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">80</context>
</context-group>
<note priority="1" from="description">Download archive</note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4505,241 +4505,6 @@
<context context-type="linenumber">159</context>
</context-group>
</trans-unit>
<trans-unit id="3159807825117518005" datatype="html">
<source>Delete archives</source>
<target state="translated">Usuń archiwa</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">152</context>
</context-group>
</trans-unit>
<trans-unit id="8224301330941792118" datatype="html">
<source>Failed to delete archive items!</source>
<target state="translated">Nie udało się usunąć elementów archiwum!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit id="6219551536751479443" datatype="html">
<source>Finished downloading</source>
<target state="translated">Zakończono pobieranie</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="5947241266456580665" datatype="html">
<source>Download failed</source>
<target state="translated">Pobieranie nie udane</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="11a0771f88158a540a54e0e4ec5d25733d65fc0e" datatype="html">
<source>Favorite</source>
<target state="translated">Ulubione</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<note priority="1" from="description">Favorite button</note>
</trans-unit>
<trans-unit id="5000203534763292992" datatype="html">
<source>Download restarted!</source>
<target state="translated">Pobieranie wznowione!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="56b1a3c93fb95fed1805005c561a5e431d57ffae" datatype="html" xml:space="preserve">
<source>Blacklist all files</source>
<target state="translated">Dodaj do listy blokowanie wszystkie pliki</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
<note priority="1" from="description">Blacklist deleted files</note>
</trans-unit>
<trans-unit id="5ac5a0e5ffe8e5623b40696f4c2403c17349271f" datatype="html" xml:space="preserve">
<source>Sidepanel mode</source>
<target state="translated">Tryb panelu bocznego</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">30</context>
</context-group>
<note priority="1" from="description">Sidepanel mode</note>
</trans-unit>
<trans-unit id="c2faa86201eab08b5b39b5437f96ab9432e125e7" datatype="html" xml:space="preserve">
<source>Item limit</source>
<target state="translated">Lista elementów</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">46</context>
</context-group>
<note priority="1" from="description">Item limit</note>
</trans-unit>
<trans-unit id="8336047719608684263" datatype="html" xml:space="preserve">
<source>Unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/></source>
<target state="translated">Anuluj subskrypcję <x id="subscription name" equiv-text="this.sub['name']"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="784837056777689544" datatype="html" xml:space="preserve">
<source>Would you like to unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/>?</source>
<target state="translated">Czy na pewno chcesz cofnąć subskrypcję dla <x id="subscription name" equiv-text="this.sub['name']"/>?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="c40370dc182b5e4333828b70f7478bde58bb5cfe" datatype="html" xml:space="preserve">
<source>Enable notifications</source>
<target state="translated">Włącz powiadomienia</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">349</context>
</context-group>
<note priority="1" from="description">Enable notifications setting</note>
</trans-unit>
<trans-unit id="3371159074051387771" datatype="html" xml:space="preserve">
<source>Failed to delete <x id="category name" equiv-text="category['name']"/>!</source>
<target state="translated">Nie udało się usunąć <x id="category name" equiv-text="category['name']"/>!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">172</context>
</context-group>
</trans-unit>
<trans-unit id="7332320960988475089" datatype="html" xml:space="preserve">
<source>Successfully deleted <x id="category name" equiv-text="category['name']"/>!</source>
<target state="translated">Usunięto subskrypcję <x id="category name" equiv-text="category['name']"/>!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">168</context>
</context-group>
</trans-unit>
<trans-unit id="a4ed8eba1e057e67d5c2d87b52230f182b3dae4e" datatype="html" xml:space="preserve">
<source>Restart required.</source>
<target state="translated">Wymagane jest ponowne uruchomienie.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">446</context>
</context-group>
<note priority="1" from="description">Restart required hint</note>
</trans-unit>
<trans-unit id="674a999dd48d7da565ffdd105602261b8a4761ea" datatype="html" xml:space="preserve">
<source>Download zip</source>
<target state="translated">Pobierz zip</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/subscription/subscription/subscription.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<note priority="1" from="description">Download zip</note>
</trans-unit>
<trans-unit id="c35ef0f03a863d33b04aae6807f140397a50f491" datatype="html" xml:space="preserve">
<source>Generate RSS URL</source>
<target state="translated">Generuj adres URL kanału RSS</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">273</context>
</context-group>
<note priority="1" from="description">Generate RSS URL</note>
</trans-unit>
<trans-unit id="b6399391e706e2d7b7b7880eb5630e4e6f49728c" datatype="html" xml:space="preserve">
<source>Side</source>
<target state="translated">Strona</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">35,37</context>
</context-group>
<note priority="1" from="description">Side</note>
</trans-unit>
<trans-unit id="8c1bf02206fbc371ff69ab1b7e35a17ba29d169d" datatype="html" xml:space="preserve">
<source>Use ntfy API</source>
<target state="translated">Użyj API ntfy</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">386</context>
</context-group>
<note priority="1" from="description">Use ntfy API setting</note>
</trans-unit>
<trans-unit id="eeb0ba2e4743901d8f5eebd9a3529aa1f236c608" datatype="html" xml:space="preserve">
<source>Create bot here.</source>
<target state="translated">Stwórz bota.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">419</context>
</context-group>
<note priority="1" from="description">Telegram bot create link</note>
</trans-unit>
<trans-unit id="6785427850041119037" datatype="html" xml:space="preserve">
<source>Delete category</source>
<target state="translated">Usuń kategorię</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">158</context>
</context-group>
</trans-unit>
<trans-unit id="9e766e11a9de375907aaf566897ecc6dac393ebc" datatype="html" xml:space="preserve">
<source>Webhook URL</source>
<target state="translated">Adres URL Webhook</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">366</context>
</context-group>
<note priority="1" from="description">webhook URL</note>
</trans-unit>
<trans-unit id="3264d82792954815be755b3da01e2625458711dc" datatype="html" xml:space="preserve">
<source>Discord Webhook URL</source>
<target state="translated">Adres URL Webhook Discord</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">373</context>
</context-group>
<note priority="1" from="description">Discord Webhook URL</note>
</trans-unit>
<trans-unit id="0cfc9cfe7cd8ea14bc053693b28872da739af02c" datatype="html" xml:space="preserve">
<source>See docs here.</source>
<target state="translated">Zobacz dokumentacje tutaj.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">375</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">382</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">392</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">402</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">409</context>
</context-group>
<note priority="1" from="description">Discord API setting hint</note>
</trans-unit>
<trans-unit id="378c072ce05889c9771718d05106e7685fcd3507" datatype="html" xml:space="preserve">
<source>Medium</source>
<target state="translated">Średnia</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">47,49</context>
</context-group>
<note priority="1" from="description">Medium</note>
</trans-unit>
</body>
</file>
</xliff>

File diff suppressed because it is too large Load Diff

View File

@@ -81,9 +81,9 @@
</context-group>
<note priority="1" from="description">Navigation menu Subscriptions Page title</note>
</trans-unit>
<trans-unit id="822fab38216f64e8166d368b59fe756ca39d301b" datatype="html" xml:space="preserve">
<trans-unit id="822fab38216f64e8166d368b59fe756ca39d301b" datatype="html">
<source>Downloads</source>
<target state="translated">Descarregados</target>
<target>Baixados</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.html</context>
<context context-type="linenumber">46</context>
@@ -99,9 +99,9 @@
</context-group>
<note priority="1" from="description">Only Audio checkbox</note>
</trans-unit>
<trans-unit id="6a21ba5fb0ac804a525bf9ab168038c3ee88e661" datatype="html" xml:space="preserve">
<trans-unit id="6a21ba5fb0ac804a525bf9ab168038c3ee88e661" datatype="html">
<source>Download</source>
<target state="translated">Descarregar</target>
<target>Baixar</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.html</context>
<context context-type="linenumber">74,75</context>
@@ -240,9 +240,9 @@
</context-group>
<note priority="1" from="description">Youtube-dl output template documentation link</note>
</trans-unit>
<trans-unit id="19d1ae64d94d28a29b2c57ae8671aace906b5401" datatype="html" xml:space="preserve">
<trans-unit id="19d1ae64d94d28a29b2c57ae8671aace906b5401" datatype="html">
<source>Path is relative to the config download path. Don't include extension.</source>
<target state="translated">Caminho é relativo à configuração do caminho de descargas. Não inclui a extensão.</target>
<target>Caminho é relativo a configuração do caminho de download. Não inclua a extensão.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.html</context>
<context context-type="linenumber">128</context>
@@ -420,9 +420,9 @@
</context-group>
<note priority="1" from="description">Subscription custom name placeholder</note>
</trans-unit>
<trans-unit id="ea30873bd3f0d5e4fb2378eec3f0a1db77634a28" datatype="html" xml:space="preserve">
<trans-unit id="ea30873bd3f0d5e4fb2378eec3f0a1db77634a28" datatype="html">
<source>Download all uploads</source>
<target state="translated">Descarregar todos envios</target>
<target>Baixar todos uploads</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
<context context-type="linenumber">23</context>
@@ -536,9 +536,9 @@
</context-group>
<note priority="1" from="description">Subscribe button</note>
</trans-unit>
<trans-unit id="28a678e9cabf86e44c32594c43fa0e890135c20f" datatype="html" xml:space="preserve">
<trans-unit id="28a678e9cabf86e44c32594c43fa0e890135c20f" datatype="html">
<source>Download videos uploaded in the last</source>
<target state="translated">Descarregar vídeos enviados no último</target>
<target>Baixe vídeos enviados no último</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscribe-dialog/subscribe-dialog.component.html</context>
<context context-type="linenumber">26</context>
@@ -1023,9 +1023,9 @@
</context-group>
<note priority="1" from="description">Add new rule tooltip</note>
</trans-unit>
<trans-unit id="792dc6a57f28a1066db283f2e736484f066005fd" datatype="html" xml:space="preserve">
<trans-unit id="792dc6a57f28a1066db283f2e736484f066005fd" datatype="html">
<source>Download Twitch Chat</source>
<target state="translated">Descarregar Twitch Chat</target>
<target>Baixar Twitch Chat</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/twitch-chat/twitch-chat.component.html</context>
<context context-type="linenumber">10</context>
@@ -1266,9 +1266,9 @@
</context-group>
<note priority="1" from="description">Main settings label</note>
</trans-unit>
<trans-unit id="0ba25ad86a240576c4f20a2fada4722ebba77b1e" datatype="html" xml:space="preserve">
<trans-unit id="0ba25ad86a240576c4f20a2fada4722ebba77b1e" datatype="html">
<source>Downloader</source>
<target state="translated">Descarregador</target>
<target>Downloader</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">102</context>
@@ -1373,9 +1373,9 @@
</context-group>
<note priority="1" from="description">Users base path placeholder</note>
</trans-unit>
<trans-unit id="a64505c41150663968e277ec9b3ddaa5f4838798" datatype="html" xml:space="preserve">
<trans-unit id="a64505c41150663968e277ec9b3ddaa5f4838798" datatype="html">
<source>Base path for users and their downloaded videos.</source>
<target state="translated">Diretório base para utilizadores e os seus vídeos descarregados.</target>
<target>Diretório base para usuários e seus vídeos baixados.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">39</context>
@@ -1427,18 +1427,18 @@
</context-group>
<note priority="1" from="description">Check interval setting input hint</note>
</trans-unit>
<trans-unit id="13759b09a7f4074ceee8fa2f968f9815fdf63295" datatype="html" xml:space="preserve">
<trans-unit id="13759b09a7f4074ceee8fa2f968f9815fdf63295" datatype="html">
<source>Sometimes new videos are downloaded before being fully processed. This setting will mean new videos will be checked for a higher quality version the following day.</source>
<target state="translated">Algumas vezes vídeos são descarregados antes de serem totalmente processados. Esta configuração faz com que vídeos novos sejam verificados por uma qualidade mais alta no dia seguinte.</target>
<target>Algumas vezes vídeos são baixados antes de serem totalmente processados. Esta configuração faz com que vídeos novos sejam verificados por uma qualidade mais alta no dia seguinte.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">63</context>
</context-group>
<note priority="1" from="description">Redownload fresh uploads tooltip</note>
</trans-unit>
<trans-unit id="3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513" datatype="html" xml:space="preserve">
<trans-unit id="3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513" datatype="html">
<source>Redownload fresh uploads</source>
<target state="translated">Descarregar envios recentes novamente</target>
<target>Baixar novamente uploads recentes</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">63</context>
@@ -1490,9 +1490,9 @@
</context-group>
<note priority="1" from="description">Audio folder path input placeholder</note>
</trans-unit>
<trans-unit id="c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca" datatype="html" xml:space="preserve">
<trans-unit id="c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca" datatype="html">
<source>Path for audio only downloads. It is relative to YTDL-Material's root folder.</source>
<target state="translated">Diretório para descargas de 'apenas áudio'. Relativo ao diretório raiz do YTDL-Material.</target>
<target>Diretório para downloads de 'áudio apenas'. Relativo ao diretório raiz do YTDL-Material.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">110</context>
@@ -1508,9 +1508,9 @@
</context-group>
<note priority="1" from="description">Video folder path input placeholder</note>
</trans-unit>
<trans-unit id="17c92e6d47a213fa95b5aa344b3f258147123f93" datatype="html" xml:space="preserve">
<trans-unit id="17c92e6d47a213fa95b5aa344b3f258147123f93" datatype="html">
<source>Path for video downloads. It is relative to YTDL-Material's root folder.</source>
<target state="translated">Diretório para descargas de vídeos. Relativo ao diretório raiz do YTDL-Material.</target>
<target>Diretório para download de vídeos. Relativo ao diretório raiz do YTDL-Material.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">117</context>
@@ -1526,9 +1526,9 @@
</context-group>
<note priority="1" from="description">Default file output placeholder</note>
</trans-unit>
<trans-unit id="1148fd45287ff09955b938756bc302042bcb29c7" datatype="html" xml:space="preserve">
<trans-unit id="1148fd45287ff09955b938756bc302042bcb29c7" datatype="html">
<source>Path is relative to the above download paths. Don't include extension.</source>
<target state="translated">O caminho é relativo ao diretório de descargas acima. Não inclua extensão.</target>
<target>O caminho é relativo ao diretório de download acima. Não inclua extensão.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">126</context>
@@ -1589,9 +1589,9 @@
</context-group>
<note priority="1" from="description">Include metadata setting</note>
</trans-unit>
<trans-unit id="fb35145bfb84521e21b6385363d59221f436a573" datatype="html" xml:space="preserve">
<trans-unit id="fb35145bfb84521e21b6385363d59221f436a573" datatype="html">
<source>Kill all downloads</source>
<target state="translated">Parar todas as descargas</target>
<target>Parar todos os downloads</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">175</context>
@@ -1616,9 +1616,9 @@
</context-group>
<note priority="1" from="description">File manager enabled setting</note>
</trans-unit>
<trans-unit id="a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a" datatype="html" xml:space="preserve">
<trans-unit id="a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a" datatype="html">
<source>Downloads manager enabled</source>
<target state="translated">Ativar gestor de descargas</target>
<target>Habilitar gerenciador de downloads</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">196</context>
@@ -1634,9 +1634,9 @@
</context-group>
<note priority="1" from="description">Allow quality seelct setting</note>
</trans-unit>
<trans-unit id="bda5508e24e0d77debb28bcd9194d8fefb1cfb92" datatype="html" xml:space="preserve">
<trans-unit id="bda5508e24e0d77debb28bcd9194d8fefb1cfb92" datatype="html">
<source>Download only mode</source>
<target state="translated">Modo apenas descargas</target>
<target>Modo Apenas Download</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">202</context>
@@ -1755,9 +1755,9 @@
</context-group>
<note priority="1" from="description">Twitch API Key setting hint AKA preamble</note>
</trans-unit>
<trans-unit id="5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9" datatype="html" xml:space="preserve">
<trans-unit id="5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9" datatype="html">
<source>Auto-download Twitch Chat</source>
<target state="translated">Descarregar Twitch Chat automaticamente</target>
<target>Baixar Twitch Chat automaticamente</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">244</context>
@@ -1781,9 +1781,9 @@
</context-group>
<note priority="1" from="description">Chrome ext click here</note>
</trans-unit>
<trans-unit id="7f09776373995003161235c0c8d02b7f91dbc4df" datatype="html" xml:space="preserve">
<trans-unit id="7f09776373995003161235c0c8d02b7f91dbc4df" datatype="html">
<source>to download the official YoutubeDL-Material Chrome extension manually.</source>
<target state="translated">para descarregar a extensão do YoutubeDL-Material para o Chrome manualmente.</target>
<target>para baixar a extensão do YoutubeDL-Material para o Chrome manualmente.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">259</context>
@@ -1826,9 +1826,9 @@
</context-group>
<note priority="1" from="description">Firefox setup suffix</note>
</trans-unit>
<trans-unit id="61b81b11aad0b9d970ece2fce18405f07eac69c2" datatype="html" xml:space="preserve">
<trans-unit id="61b81b11aad0b9d970ece2fce18405f07eac69c2" datatype="html">
<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>
<target state="translated">Arraste a ligação abaixo para os seus favoritos, e pronto! Navegue para o vídeo do Youtube que deseja descarregar e clique na ligação favorita.</target>
<target>Arraste o link abaixo para seus Favoritos, e pronto! Navegue para o vídeo do Youtube que deseja baixar e clique no link favoritado.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">271</context>
@@ -1844,27 +1844,27 @@
</context-group>
<note priority="1" from="description">Generate audio only bookmarklet checkbox</note>
</trans-unit>
<trans-unit id="ec71e08aee647ea4a71fd6b7510c54d84a797ca6" datatype="html" xml:space="preserve">
<trans-unit id="ec71e08aee647ea4a71fd6b7510c54d84a797ca6" datatype="html">
<source>Select a downloader</source>
<target state="translated">Selecione um descarregador</target>
<target>Selecione um downloader</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">287</context>
</context-group>
<note priority="1" from="description">Default downloader select label</note>
</trans-unit>
<trans-unit id="5fab47f146b0a4b809dcebf3db9da94df6299ea1" datatype="html" xml:space="preserve">
<trans-unit id="5fab47f146b0a4b809dcebf3db9da94df6299ea1" datatype="html">
<source>Use default downloading agent</source>
<target state="translated">Usar agente de descargas padrão</target>
<target>Usar agente de download padrão</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">295</context>
</context-group>
<note priority="1" from="description">Use default downloading agent setting</note>
</trans-unit>
<trans-unit id="c776eb4992b6c98f58cd89b20c1ea8ac37888521" datatype="html" xml:space="preserve">
<trans-unit id="c776eb4992b6c98f58cd89b20c1ea8ac37888521" datatype="html">
<source>Select a download agent</source>
<target state="translated">Selecionar um agente de descargas</target>
<target>Selecionar um agente de download</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">299</context>
@@ -1889,9 +1889,9 @@
</context-group>
<note priority="1" from="description">Login expiration select label</note>
</trans-unit>
<trans-unit id="dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8" datatype="html" xml:space="preserve">
<trans-unit id="dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8" datatype="html">
<source>Allow advanced download</source>
<target state="translated">Permitir descarga avançada</target>
<target>Permitir Download avançado</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">336</context>
@@ -2177,9 +2177,9 @@
</context-group>
<note priority="1" from="description">Current session</note>
</trans-unit>
<trans-unit id="7117fc42f860e86d983bfccfcf2654e5750f3406" datatype="html" xml:space="preserve">
<trans-unit id="7117fc42f860e86d983bfccfcf2654e5750f3406" datatype="html">
<source>No downloads available!</source>
<target state="translated">Não há descargas disponíveis!</target>
<target>Não há downloads disponíveis!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">25</context>
@@ -2459,9 +2459,9 @@
</context-group>
<note priority="1" from="description">Go to subscription menu item</note>
</trans-unit>
<trans-unit id="94e01842dcee90531caa52e4147f70679bac87fe" datatype="html" xml:space="preserve">
<trans-unit id="94e01842dcee90531caa52e4147f70679bac87fe" datatype="html">
<source>Delete and redownload</source>
<target state="translated">Apagar e descarregar novamente</target>
<target>Excluir e baixar novamente</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">28</context>
@@ -2565,18 +2565,18 @@
<context context-type="linenumber">48</context>
</context-group>
</trans-unit>
<trans-unit id="4f9f174dc9939283b3192acc9e87d6c1e4cca118" datatype="html" xml:space="preserve">
<trans-unit id="4f9f174dc9939283b3192acc9e87d6c1e4cca118" datatype="html">
<source>Limits the amount of downloads that can be simultaneously downloaded. Use -1 for no limit.</source>
<target state="translated">Limita a quantidade de descargas simultâneas. Use -1 para ilimitado.</target>
<target>Limita a quantidade de downloads simultâneos. Use -1 para ilimitado.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">176</context>
</context-group>
<note priority="1" from="description">Max concurrent downloads input hint</note>
</trans-unit>
<trans-unit id="6c2714fbfa525868fea90cc7a8f8de62458fbecf" datatype="html" xml:space="preserve">
<trans-unit id="6c2714fbfa525868fea90cc7a8f8de62458fbecf" datatype="html">
<source>Max concurrent downloads</source>
<target state="translated">Número máximo de descargas simultâneas</target>
<target>Número máximo de downloads simultâneos</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">175</context>
@@ -2636,9 +2636,9 @@
</context-group>
<note priority="1" from="description">Title</note>
</trans-unit>
<trans-unit id="b36b7458192b833592e13029fa8a0b3555e0d9bd" datatype="html" xml:space="preserve">
<trans-unit id="b36b7458192b833592e13029fa8a0b3555e0d9bd" datatype="html">
<source>Pause all downloads</source>
<target state="translated">Pausar todas as descargas</target>
<target>Pausar todos os downloads</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">83</context>
@@ -2680,9 +2680,9 @@
</context-group>
<note priority="1" from="description">Clear</note>
</trans-unit>
<trans-unit id="7220285196408439810" datatype="html" xml:space="preserve">
<source>Download for <x id="url" equiv-text="url"/> has been queued!</source>
<target state="translated">A descarga de <x id="url" equiv-text="url"/> foi adicionado à fila!</target>
<trans-unit id="7220285196408439810" datatype="html">
<source>Download for <x id="url" equiv-text="d, cropF"/> has been queued!</source>
<target>Download de <x id="url" equiv-text="d, cropF"/> foi adicionado à fila!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.ts</context>
<context context-type="linenumber">469</context>
@@ -2773,18 +2773,18 @@
</context-group>
<note priority="1" from="description">Allow playlist categorization setting label</note>
</trans-unit>
<trans-unit id="a30fc9944a494022ba67b3046ad3d27c62dd7fee" datatype="html" xml:space="preserve">
<trans-unit id="a30fc9944a494022ba67b3046ad3d27c62dd7fee" datatype="html">
<source>Download rate limit</source>
<target state="translated">Taxa limite de descargas</target>
<target>Taxa limite do download</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">181</context>
</context-group>
<note priority="1" from="description">Download rate limit input placeholder</note>
</trans-unit>
<trans-unit id="0af5ee1867be592a6cd35a94faba8833b52c740f" datatype="html" xml:space="preserve">
<trans-unit id="0af5ee1867be592a6cd35a94faba8833b52c740f" datatype="html">
<source>Rate limits your downloads to the specified amount. Ex: 200K</source>
<target state="translated">Limita a taxa das suas descargas na quantidade especificada. Ex: 200K</target>
<target>Limita a taxa de seus downloads na quantidade especificada. Ex: 200K</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">182</context>
@@ -2930,9 +2930,9 @@
</context-group>
<note priority="1" from="description">Share video dialog title</note>
</trans-unit>
<trans-unit id="2827589726081052618" datatype="html" xml:space="preserve">
<trans-unit id="2827589726081052618" datatype="html">
<source>Creating download</source>
<target state="translated">A criar descarga</target>
<target>Criando download</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">58</context>
@@ -3068,9 +3068,9 @@
</context-group>
<note priority="1" from="description">Stage</note>
</trans-unit>
<trans-unit id="9b2084f9aea764292cf0978cb083907d8be51bf7" datatype="html" xml:space="preserve">
<trans-unit id="9b2084f9aea764292cf0978cb083907d8be51bf7" datatype="html">
<source>Resume all downloads</source>
<target state="translated">Resumir todos as descargas</target>
<target>Resumir todos os downloads</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">84</context>
@@ -3113,42 +3113,6 @@
</context-group>
<note priority="1" from="description">Navigation menu Tasks Page title</note>
</trans-unit>
<trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c" datatype="html" xml:space="preserve">
<source>ID</source>
<target state="translated">Identidade/identificação</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">47</context>
</context-group>
<note priority="1" from="description">ID</note>
</trans-unit>
<trans-unit id="c748ac656af9f13998206ef2c52018dd418b0483" datatype="html" xml:space="preserve">
<source>Archives</source>
<target state="translated">Arquivos</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<note priority="1" from="description">Archives menu label</note>
</trans-unit>
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html" xml:space="preserve">
<source>Filter</source>
<target state="translated">Filtrar</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
<note priority="1" from="description">Filter</note>
</trans-unit>
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html" xml:space="preserve">
<source>Extractor</source>
<target state="translated">Extrator</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">57</context>
</context-group>
<note priority="1" from="description">Extractor</note>
</trans-unit>
</body>
</file>
</xliff>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3757,794 +3757,6 @@
</context-group>
<note priority="1" from="description">Select a version</note>
</trans-unit>
<trans-unit id="c748ac656af9f13998206ef2c52018dd418b0483" datatype="html" xml:space="preserve">
<source>Archives</source>
<target state="translated">Arşivler</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22" datatype="html" xml:space="preserve">
<source>Filter</source>
<target state="translated">Filtre</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="28da11220a3377ddce3c7948825d33101f142782" datatype="html" xml:space="preserve">
<source>Extractor</source>
<target state="translated">Dışa Çıkarıcı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="c150a30bbbdb175b4d08820196a9acb66b167653" datatype="html" xml:space="preserve">
<source>Archives empty</source>
<target state="translated">Arşivler boş</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="51a161ce175abcd44f6c6cbd0e996681bf553ac3" datatype="html" xml:space="preserve">
<source>Delete selected</source>
<target state="translated">Seçilenleri sil</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="a2f14a73f7a6e94479f67423cc51102da8d6f524" datatype="html" xml:space="preserve">
<source>None</source>
<target state="translated">Hiçbiri</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">84</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">126</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">36</context>
</context-group>
</trans-unit>
<trans-unit id="2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4" datatype="html" xml:space="preserve">
<source>Video</source>
<target state="translated">Video</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">92</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="f0baeb8b69d120073b6d60d34785889b0c3232c8" datatype="html" xml:space="preserve">
<source>Audio</source>
<target state="translated">Ses</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">93</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">134</context>
</context-group>
</trans-unit>
<trans-unit id="4b3972c3e9485218508a95f7a4ce7758e3f09ced" datatype="html" xml:space="preserve">
<source>Upload</source>
<target state="translated">Yükle</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">137</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.html</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="6549265851868599441" datatype="html" xml:space="preserve">
<source>Video</source>
<target state="translated">Video</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">40</context>
</context-group>
</trans-unit>
<trans-unit id="347407180135731058" datatype="html" xml:space="preserve">
<source>Audio</source>
<target state="translated">Ses</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="8953483585652369683" datatype="html" xml:space="preserve">
<source>Archive successfully imported!</source>
<target state="translated">Arşiv başarıyla içe aktarıldı!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">130</context>
</context-group>
</trans-unit>
<trans-unit id="3159807825117518005" datatype="html" xml:space="preserve">
<source>Delete archives</source>
<target state="translated">Arşivleri sil</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">152</context>
</context-group>
</trans-unit>
<trans-unit id="8425787787095143143" datatype="html" xml:space="preserve">
<source>Would you like to delete <x id="selected archives amount" equiv-text="this.selection.selected.length"/> archive(s)?</source>
<target state="translated"><x id="selected archives amount" equiv-text="this.selection.selected.length"/> arşivi silmek istiyor musunuz?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">153</context>
</context-group>
</trans-unit>
<trans-unit id="7022070615528435141" datatype="html" xml:space="preserve">
<source>Delete</source>
<target state="translated">Sil</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">154</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="2525880134753073592" datatype="html" xml:space="preserve">
<source>Successfully deleted archive items!</source>
<target state="translated">Arşiv öğeleri başarıyla silindi!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">172</context>
</context-group>
</trans-unit>
<trans-unit id="8224301330941792118" datatype="html" xml:space="preserve">
<source>Failed to delete archive items!</source>
<target state="translated">Arşiv öğelerini silmek başarısız oldu!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.ts</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit id="8443034725057696949" datatype="html" xml:space="preserve">
<source>Task finished</source>
<target state="translated">Görev tamamlandı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="4665451070906079743" datatype="html" xml:space="preserve">
<source>Favorited</source>
<target state="translated">Favorilendi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="8953033926734869941" datatype="html" xml:space="preserve">
<source>Name</source>
<target state="translated">İsim</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ea2b65121b93921fe54692025da9b9e3ce779ad5" datatype="html" xml:space="preserve">
<source>Task settings - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></source>
<target state="translated">Görev ayarları - <x id="INTERPOLATION" equiv-text="{{task.title}}"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="11a0771f88158a540a54e0e4ec5d25733d65fc0e" datatype="html" xml:space="preserve">
<source>Favorite</source>
<target state="translated">Favori</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="1f2809e6a99d511fdb6eaf041d785fe54d0680cc" datatype="html" xml:space="preserve">
<source>File card size</source>
<target state="translated">Dosya kardı boyutu</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="c35ef0f03a863d33b04aae6807f140397a50f491" datatype="html" xml:space="preserve">
<source>Generate RSS URL</source>
<target state="translated">RSS URLsi oluştur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">273</context>
</context-group>
</trans-unit>
<trans-unit id="1a9b415816364f554ee411020e65219092655271" datatype="html" xml:space="preserve">
<source>Title filter</source>
<target state="translated">Başlık filtresi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">8</context>
</context-group>
</trans-unit>
<trans-unit id="b78a98bc54259a29cf6250dbaeab5fe11fae91cf" datatype="html" xml:space="preserve">
<source>Favorited</source>
<target state="translated">Favorilendi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="1091872159779006651" datatype="html" xml:space="preserve">
<source>You must input a time!</source>
<target state="translated">Zaman girmelisiniz!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component.ts</context>
<context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="35cf4cdcedc8ef3f94b6100e0d86836e31dbb908" datatype="html" xml:space="preserve">
<source>Force autoplay</source>
<target state="translated">Otomatik oynatmayı zorla</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">218</context>
</context-group>
</trans-unit>
<trans-unit id="37469c9f3e31d95087fa22b6c9c3bc64adf1692d" datatype="html" xml:space="preserve">
<source>Enable RSS Feed</source>
<target state="translated">RSS Akışını Etkinleştir</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">271</context>
</context-group>
</trans-unit>
<trans-unit id="a4ed8eba1e057e67d5c2d87b52230f182b3dae4e" datatype="html" xml:space="preserve">
<source>Restart required.</source>
<target state="translated">Yeniden başlatma gerekli.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">446</context>
</context-group>
</trans-unit>
<trans-unit id="2481374649045841364" datatype="html" xml:space="preserve">
<source>Would you like to delete <x id="category name" equiv-text="category['name']"/>?</source>
<target state="translated"><x id="category name" equiv-text="category['name']"/> kategorisini silmek istiyor musunuz?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">159</context>
</context-group>
</trans-unit>
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html" xml:space="preserve">
<source>Error</source>
<target state="translated">Hata</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="8456659390937171831" datatype="html" xml:space="preserve">
<source>Show error</source>
<target state="translated">Hatayı göster</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">56</context>
</context-group>
</trans-unit>
<trans-unit id="1236604279860679031" datatype="html" xml:space="preserve">
<source>Restart</source>
<target state="translated">Yeniden Başlat</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">62</context>
</context-group>
</trans-unit>
<trans-unit id="9042260521669277115" datatype="html" xml:space="preserve">
<source>Pause</source>
<target state="translated">Durdur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="7182974689040833178" datatype="html" xml:space="preserve">
<source>Resume</source>
<target state="translated">Devam et</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">74</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">80</context>
</context-group>
</trans-unit>
<trans-unit id="019d4bd6a5690f0cfa0ecf346a4e6bf7f0d8debb" datatype="html" xml:space="preserve">
<source>Remove</source>
<target state="translated">Kaldır</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.html</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="6219551536751479443" datatype="html" xml:space="preserve">
<source>Finished downloading</source>
<target state="translated">İndirme tamamlandı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="5947241266456580665" datatype="html" xml:space="preserve">
<source>Download failed</source>
<target state="translated">İndirme başarısız</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="8564202903947049539" datatype="html" xml:space="preserve">
<source>Play</source>
<target state="translated">Oynat</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="8643601595923420698" datatype="html" xml:space="preserve">
<source>Retry download</source>
<target state="translated">İndirmeyi yeniden dene</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="8571838164752006148" datatype="html" xml:space="preserve">
<source>View error</source>
<target state="translated">Hatayı görüntüle</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="5709555629190115111" datatype="html" xml:space="preserve">
<source>View task</source>
<target state="translated">Görevi görüntüle</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications-list/notifications-list.component.ts</context>
<context context-type="linenumber">33</context>
</context-group>
</trans-unit>
<trans-unit id="5a105e7bd7e7db6ea211fe950fc9f317379acceb" datatype="html" xml:space="preserve">
<source>No notifications available</source>
<target state="translated">Bildirim yok</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="6876310993601590130" datatype="html" xml:space="preserve">
<source>Download completed</source>
<target state="translated">İndirme tamamlandı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="1879058637439215882" datatype="html" xml:space="preserve">
<source>Download error</source>
<target state="translated">İndirme hatası</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="4578192247039196794" datatype="html" xml:space="preserve">
<source>Task</source>
<target state="translated">Görev</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="5000203534763292992" datatype="html" xml:space="preserve">
<source>Download restarted!</source>
<target state="translated">İndirme yeniden başlatıldı!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/notifications/notifications.component.ts</context>
<context context-type="linenumber">72</context>
</context-group>
</trans-unit>
<trans-unit id="7911845622864460134" datatype="html" xml:space="preserve">
<source>Video only</source>
<target state="translated">Yalnızca video</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">55</context>
</context-group>
</trans-unit>
<trans-unit id="6437411876967154040" datatype="html" xml:space="preserve">
<source>Audio only</source>
<target state="translated">Yalnızca ses</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">60</context>
</context-group>
</trans-unit>
<trans-unit id="6268070779441507380" datatype="html" xml:space="preserve">
<source>Download Date</source>
<target state="translated">İndirme Tarihi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="3533826530554274875" datatype="html" xml:space="preserve">
<source>Upload Date</source>
<target state="translated">Yüklenme Tarihi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="2492098975665776610" datatype="html" xml:space="preserve">
<source>File Size</source>
<target state="translated">Dosya Boyutu</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="7410432243549869948" datatype="html" xml:space="preserve">
<source>Duration</source>
<target state="translated">Uzunluk</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/sort-property/sort-property.component.ts</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="9aa1b4779a515170b297d2c0507e6ff9d2e3e0e0" datatype="html" xml:space="preserve">
<source>Blacklist deleted subscription files</source>
<target state="translated">Silinen abonelik dosyalarını kara listele</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">14</context>
</context-group>
</trans-unit>
<trans-unit id="7b4585a9072f3c1292972c14a3d0e14978fbfc9c" datatype="html" xml:space="preserve">
<source>Delete old files:</source>
<target state="translated">Eski dosyaları sil:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/tasks/tasks.component.html</context>
<context context-type="linenumber">66</context>
</context-group>
</trans-unit>
<trans-unit id="9aa62bf1a535a97a4d752bbc5cf1c31af0f0c1f7" datatype="html" xml:space="preserve">
<source>Supports regex</source>
<target state="translated">Regex destekler</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit id="e08a77594f3d89311cdf6da5090044270909c194" datatype="html" xml:space="preserve">
<source>User</source>
<target state="translated">Kullanıcı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="c2faa86201eab08b5b39b5437f96ab9432e125e7" datatype="html" xml:space="preserve">
<source>Item limit</source>
<target state="translated">Öğe sınırı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/generate-rss-url/generate-rss-url.component.html</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="1698114086921246480" datatype="html" xml:space="preserve">
<source>Unsubscribe</source>
<target state="translated">Abonelikten çık</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="b6399391e706e2d7b7b7880eb5630e4e6f49728c" datatype="html" xml:space="preserve">
<source>Side</source>
<target state="translated">Yan</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">35,37</context>
</context-group>
</trans-unit>
<trans-unit id="e0a11fbea353b1ce1131161774e4a3e10bcb99b1" datatype="html" xml:space="preserve">
<source>Large</source>
<target state="translated">Büyük</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">44,46</context>
</context-group>
</trans-unit>
<trans-unit id="d57c023a4cf63b2f12c10328c15b636ff18929aa" datatype="html" xml:space="preserve">
<source>Best</source>
<target state="translated">En iyisi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.html</context>
<context context-type="linenumber">24,25</context>
</context-group>
</trans-unit>
<trans-unit id="af30e51aa8b67e1133a341ec28359be05150e65c" datatype="html" xml:space="preserve">
<source>No description available.</source>
<target state="translated">Açıklama yok.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/player/player.component.html</context>
<context context-type="linenumber">25,27</context>
</context-group>
</trans-unit>
<trans-unit id="fd467148a18f0921c10d116d4e0174fe29452be4" datatype="html" xml:space="preserve">
<source>See documentation here.</source>
<target state="translated">Buradan dokümentasyonu okuyun.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">274</context>
</context-group>
</trans-unit>
<trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html" xml:space="preserve">
<source>Notifications</source>
<target state="translated">Bildirimler</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">343</context>
</context-group>
</trans-unit>
<trans-unit id="c40370dc182b5e4333828b70f7478bde58bb5cfe" datatype="html" xml:space="preserve">
<source>Enable notifications</source>
<target state="translated">Bildirimleri etkinleştir</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">349</context>
</context-group>
</trans-unit>
<trans-unit id="c5dc5fbcce45e9b1530e2a5c2baa8ebe722aef4c" datatype="html" xml:space="preserve">
<source>Download complete</source>
<target state="translated">İndirme tamamlandı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">358</context>
</context-group>
</trans-unit>
<trans-unit id="3ffd9490f3a4c0b24021d25e1dc71fcfe5d39cd6" datatype="html" xml:space="preserve">
<source>Download error</source>
<target state="translated">İndirme hatası</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">359</context>
</context-group>
</trans-unit>
<trans-unit id="2361a4f76caaa4574803fbcdca8b0a47c91cc7ed" datatype="html" xml:space="preserve">
<source>Task finished</source>
<target state="translated">Görev tamamlandı</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">360</context>
</context-group>
</trans-unit>
<trans-unit id="9e766e11a9de375907aaf566897ecc6dac393ebc" datatype="html" xml:space="preserve">
<source>Webhook URL</source>
<target state="translated">Webhook URLsi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">366</context>
</context-group>
</trans-unit>
<trans-unit id="3264d82792954815be755b3da01e2625458711dc" datatype="html" xml:space="preserve">
<source>Discord Webhook URL</source>
<target state="translated">Discord Webhook URLsi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">373</context>
</context-group>
</trans-unit>
<trans-unit id="0cfc9cfe7cd8ea14bc053693b28872da739af02c" datatype="html" xml:space="preserve">
<source>See docs here.</source>
<target state="translated">Buradan dokümentasyonu okuyun.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">375</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">382</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">392</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">402</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">409</context>
</context-group>
</trans-unit>
<trans-unit id="7cedb649779673568447b994463b2882c4e0436a" datatype="html" xml:space="preserve">
<source>Slack Webhook URL</source>
<target state="translated">Slack Webhook URLsi</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">380</context>
</context-group>
</trans-unit>
<trans-unit id="eeb0ba2e4743901d8f5eebd9a3529aa1f236c608" datatype="html" xml:space="preserve">
<source>Create bot here.</source>
<target state="translated">Buradan bot oluşturun.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">419</context>
</context-group>
</trans-unit>
<trans-unit id="378c072ce05889c9771718d05106e7685fcd3507" datatype="html" xml:space="preserve">
<source>Medium</source>
<target state="translated">Orta</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">47,49</context>
</context-group>
</trans-unit>
<trans-unit id="cdf5297d8d080a78e8b10debc5c38b7845a3cbe7" datatype="html" xml:space="preserve">
<source>Do not ask for confirmation</source>
<target state="translated">Doğrulama isteme</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="9176960997786930103" datatype="html" xml:space="preserve">
<source>Error for: <x id="PH" equiv-text="task['title']"/></source>
<target state="translated">Şunun için hata: <x id="PH" equiv-text="task['title']"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/tasks/tasks.component.ts</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit id="3640026747176198246" datatype="html" xml:space="preserve">
<source>Watch content</source>
<target state="translated">İçerik izle</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="5ac5a0e5ffe8e5623b40696f4c2403c17349271f" datatype="html" xml:space="preserve">
<source>Sidepanel mode</source>
<target state="translated">Yan panel modu</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="6785427850041119037" datatype="html" xml:space="preserve">
<source>Delete category</source>
<target state="translated">Kategoriyi sil</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">158</context>
</context-group>
</trans-unit>
<trans-unit id="784837056777689544" datatype="html" xml:space="preserve">
<source>Would you like to unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/>?</source>
<target state="translated"><x id="subscription name" equiv-text="this.sub['name']"/> aboneliğinden çıkmak istiyor musunuz?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="33a7c6d5ff3515fa237f1fd4e43df8b65373954d" datatype="html" xml:space="preserve">
<source>Enable all notifications</source>
<target state="translated">Tüm bildirimleri etkinleştir</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">352</context>
</context-group>
</trans-unit>
<trans-unit id="c41475a25c9f9d9639db9efa56637603a77528b4" datatype="html" xml:space="preserve">
<source>Download archive</source>
<target state="translated">Arşivi indir</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/archive-viewer/archive-viewer.component.html</context>
<context context-type="linenumber">80</context>
</context-group>
</trans-unit>
<trans-unit id="61d6b5fa4311b1c617b66dad72496f9dd43b07b4" datatype="html" xml:space="preserve">
<source>Be careful enabling this with multi-user mode! User data may be exposed.</source>
<target state="translated">Çoklu kullanıcı moduyla beraber bunu etkinleştirirken dikkatli olun! Kullanıcı verileri açığa çıkabilir.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">272</context>
</context-group>
</trans-unit>
<trans-unit id="c65dd978b3c7566551c0ebefb234c2d41942b847" datatype="html" xml:space="preserve">
<source>Delete files older than</source>
<target state="translated">Şundan eski dosyaları sil</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">6</context>
</context-group>
</trans-unit>
<trans-unit id="56b1a3c93fb95fed1805005c561a5e431d57ffae" datatype="html" xml:space="preserve">
<source>Blacklist all files</source>
<target state="translated">Tüm dosyaları kara listele</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/task-settings/task-settings.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="1d4fa01d25990f60abf21c3a451fa8ba262b7912" datatype="html" xml:space="preserve">
<source>Unfavorite</source>
<target state="translated">Favoriden Çıkar</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="8336047719608684263" datatype="html" xml:space="preserve">
<source>Unsubscribe from <x id="subscription name" equiv-text="this.sub['name']"/></source>
<target state="translated"><x id="subscription name" equiv-text="this.sub['name']"/> aboneliğinden çık</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscription-info-dialog/subscription-info-dialog.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="9a865c2922f5c01899d06c472dba2e5bd63bcff9" datatype="html" xml:space="preserve">
<source>Small</source>
<target state="translated">Küçük</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/user-profile-dialog/user-profile-dialog.component.html</context>
<context context-type="linenumber">50,52</context>
</context-group>
</trans-unit>
<trans-unit id="7332320960988475089" datatype="html" xml:space="preserve">
<source>Successfully deleted <x id="category name" equiv-text="category['name']"/>!</source>
<target state="translated"><x id="category name" equiv-text="category['name']"/> başarıyla silindi!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">168</context>
</context-group>
</trans-unit>
<trans-unit id="9c562d26e041390ecc3f49dabc51cc50ebba7469" datatype="html" xml:space="preserve">
<source>Allowed notification types</source>
<target state="translated">İzin verilen bildirim türleri</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">356</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
/* You can add global styles to this file, and also import other style files */
@use '@angular/material' as mat;
@import '~material-icons/iconfont/material-icons.css';
@@ -12,7 +13,6 @@
html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
@import '@angular/material/theming';
// Plus imports for other components in your app.
/*// Typography
@@ -24,22 +24,22 @@ $custom-typography: mat-typography-config(
@include angular-material-typography($custom-typography);
*/
// Default colors
$my-app-primary: mat-palette($mat-light-blue, 700, 100, 800);
$my-app-accent: mat-palette($mat-blue, 700, 100, 800);
$my-app-warn: mat-palette($mat-red, 700, 100, 800);
$my-app-primary: mat.define-palette(mat.$light-blue-palette, 700, 100, 800); // mat-palette($mat-light-blue, 700, 100, 800);
$my-app-accent: mat.define-palette(mat.$blue-palette, 700, 100, 800);
$my-app-warn: mat.define-palette(mat.$red-palette, 700, 100, 800);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include angular-material-theme($my-app-theme);
$my-app-theme: mat.define-light-theme((color: (primary: $my-app-primary, accent: $my-app-accent, warn: $my-app-warn)));
@include mat.all-component-themes($my-app-theme);
// Dark theme
$dark-primary: mat-palette($mat-light-blue, 700, 100, 800);
$dark-accent: mat-palette($mat-blue);
$dark-warn: mat-palette($mat-deep-orange);
$dark-primary: mat.define-palette(mat.$light-blue-palette, 700, 100, 800);
$dark-accent: mat.define-palette(mat.$blue-palette);
$dark-warn: mat.define-palette(mat.$deep-orange-palette);
$dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
$dark-theme: mat.define-dark-theme((color: (primary: $dark-primary, accent: $dark-accent, warn: $dark-warn)));
.dark-theme {
@include angular-material-theme($dark-theme);
@include mat.all-component-themes($dark-theme);
}
.mat-mdc-outlined-button, .mat-mdc-raised-button, .mat-mdc-flat-button {
@@ -47,14 +47,14 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
}
// Light theme
$light-primary: mat-palette($mat-grey, 200, 500, 300);
$light-accent: mat-palette($mat-brown, 200);
$light-warn: mat-palette($mat-deep-orange, 200);
$light-primary: mat.define-palette(mat.$grey-palette, 200, 500, 300);
$light-accent: mat.define-palette(mat.$brown-palette, 200);
$light-warn: mat.define-palette(mat.$deep-orange-palette, 200);
$light-theme: mat-light-theme($light-primary, $light-accent, $light-warn);
$light-theme: mat.define-light-theme((color: (primary: $light-primary, accent: $light-accent, warn: $light-warn)));
.light-theme {
@include angular-material-theme($light-theme);
@include mat.all-component-themes($light-theme);
}
.no-outline {
@@ -65,9 +65,7 @@ $light-theme: mat-light-theme($light-primary, $light-accent, $light-warn);
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
// @import '../node_modules/@angular/material/theming';
@include mat.core();
.centered {
margin: 0 auto;