mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-15 01:12:20 -04:00
Compare commits
126 Commits
feat-lti-i
...
ltimoodle1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10cc4eb069 | ||
|
|
715e6dec72 | ||
|
|
b157ecf5d3 | ||
|
|
e33144b8d0 | ||
|
|
6279f8bd60 | ||
|
|
0e0ab6e22c | ||
|
|
a40a0037bd | ||
|
|
04693f11c8 | ||
|
|
fb71e1ead4 | ||
|
|
cb9b4df3f6 | ||
|
|
22c9c391f5 | ||
|
|
f3bc24a7a2 | ||
|
|
d27c245f58 | ||
|
|
633e666cea | ||
|
|
0954b3ff0d | ||
|
|
8d0f4827ed | ||
|
|
e1e04ea93a | ||
|
|
0c8a464845 | ||
|
|
e0974433a4 | ||
|
|
e130c7403c | ||
|
|
182fb9599a | ||
|
|
eadaffb27c | ||
|
|
27bba37a55 | ||
|
|
90b480b4f8 | ||
|
|
583a8c4512 | ||
|
|
b806ffd7f5 | ||
|
|
a6f7ce71c6 | ||
|
|
ab5f86ee4d | ||
|
|
216365fdb6 | ||
|
|
66b19b99f2 | ||
|
|
692b6b5f09 | ||
|
|
814a7d3cee | ||
|
|
31571fc4cb | ||
|
|
edd40d45b4 | ||
|
|
45774fbc8c | ||
|
|
5ac0515d05 | ||
|
|
492e3366c0 | ||
|
|
3f1ca5d02f | ||
|
|
8bc09b69f7 | ||
|
|
7b8e4975ab | ||
|
|
df8701a515 | ||
|
|
2c1103e906 | ||
|
|
1c50dcdff8 | ||
|
|
b8fee7d06a | ||
|
|
1df6e0c10d | ||
|
|
dc328cd33c | ||
|
|
b39789c2c4 | ||
|
|
86fa084391 | ||
|
|
533a30b6cb | ||
|
|
cdbed4bc1d | ||
|
|
58d336478c | ||
|
|
d28fc114f3 | ||
|
|
0056e1ed8f | ||
|
|
2423e9517e | ||
|
|
699f4bd09d | ||
|
|
6071921908 | ||
|
|
33da2242b4 | ||
|
|
21ddd04165 | ||
|
|
96755af3b2 | ||
|
|
17526aeecd | ||
|
|
23c5c544ce | ||
|
|
ae9d614e84 | ||
|
|
e044187973 | ||
|
|
b5a76c53e1 | ||
|
|
86348f1747 | ||
|
|
c6be65edd6 | ||
|
|
4656ba1176 | ||
|
|
7e7cd10549 | ||
|
|
d5919e10af | ||
|
|
f16b61644a | ||
|
|
c486574eb0 | ||
|
|
364d22dd8e | ||
|
|
209cede39c | ||
|
|
9b8f63881a | ||
|
|
82fb2665e6 | ||
|
|
abd22426d7 | ||
|
|
86f4484c36 | ||
|
|
f226c2fe1e | ||
|
|
98111234f8 | ||
|
|
467e273ff5 | ||
|
|
166fb5c00b | ||
|
|
d3f722611d | ||
|
|
fae5634729 | ||
|
|
5cdcef2205 | ||
|
|
f6e44f8343 | ||
|
|
1b3a58bc60 | ||
|
|
befc1f0efa | ||
|
|
6075514ffa | ||
|
|
9c7cc293ce | ||
|
|
f7c675596f | ||
|
|
36d815c0cf | ||
|
|
8f28b00a63 | ||
|
|
74952f68d7 | ||
|
|
7950a4655a | ||
|
|
b76282f9e4 | ||
|
|
b405a04e34 | ||
|
|
76a27ae256 | ||
|
|
156e0bddd6 | ||
|
|
637e9eabea | ||
|
|
e12f361935 | ||
|
|
c4d569e7b0 | ||
|
|
24d15d2639 | ||
|
|
1c0805748d | ||
|
|
8e7f1ef15d | ||
|
|
d00b4ed3c5 | ||
|
|
7126cf1cbd | ||
|
|
33135f81e9 | ||
|
|
cfd305d563 | ||
|
|
d4bad33592 | ||
|
|
7c0ea60d0e | ||
|
|
c6726d1b13 | ||
|
|
7c04cc30fb | ||
|
|
f28ce5990b | ||
|
|
27828d798e | ||
|
|
ba53467033 | ||
|
|
a77761ec35 | ||
|
|
4d07ae64c6 | ||
|
|
223e87073f | ||
|
|
1a96ac0703 | ||
|
|
9e0290ae49 | ||
|
|
610a22935f | ||
|
|
81e3325687 | ||
|
|
16f7b010ab | ||
|
|
68cb21ff8a | ||
|
|
2ddc3b24d7 | ||
|
|
63e96b327e |
22
.github/workflows/semantic-pull-request.yaml
vendored
Normal file
22
.github/workflows/semantic-pull-request.yaml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: "Lint PR"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
name: Validate PR title
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: dev
|
||||||
|
steps:
|
||||||
|
- uses: amannn/action-semantic-pull-request@v5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
47
.github/workflows/semantic-release.yaml
vendored
Normal file
47
.github/workflows/semantic-release.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Semantic Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
semantic-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: dev
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup SSH
|
||||||
|
uses: webfactory/ssh-agent@v0.8.0
|
||||||
|
with:
|
||||||
|
ssh-private-key: ${{ secrets.GA_DEPLOY_KEY }}
|
||||||
|
|
||||||
|
# use SSH url to ensure git commit using a deploy key bypasses the main
|
||||||
|
# branch protection rule
|
||||||
|
- name: Configure Git for SSH Push
|
||||||
|
run: git remote set-url origin "git@github.com:${{ github.repository }}.git"
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "lts/*"
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm clean-install
|
||||||
|
|
||||||
|
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
||||||
|
run: npm audit signatures
|
||||||
|
|
||||||
|
- name: Run Semantic Release
|
||||||
|
run: npx semantic-release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
/templates/cms/*
|
/templates/cms/*
|
||||||
/templates/*.html
|
/templates/*.html
|
||||||
*.scss
|
*.scss
|
||||||
|
/frontend/
|
||||||
100
.releaserc.json
Normal file
100
.releaserc.json
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"branches": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
{
|
||||||
|
"preset": "conventionalcommits"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
{
|
||||||
|
"preset": "conventionalcommits",
|
||||||
|
"presetConfig": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "feat",
|
||||||
|
"section": "Features"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "fix",
|
||||||
|
"section": "Bug Fixes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chore",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docs",
|
||||||
|
"section": "Documentation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "style",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "refactor",
|
||||||
|
"section": "Refactors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "perf",
|
||||||
|
"section": "Performance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "depr",
|
||||||
|
"section": "Deprecations"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"semantic-release-replace-plugin",
|
||||||
|
{
|
||||||
|
"replacements": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"from": "\"version\": \".*\"",
|
||||||
|
"to": "\"version\": \"${nextRelease.version}\"",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"file": "package.json",
|
||||||
|
"hasChanged": true,
|
||||||
|
"numMatches": 1,
|
||||||
|
"numReplacements": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"countMatches": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/changelog",
|
||||||
|
{
|
||||||
|
"changelogFile": "CHANGELOG.md",
|
||||||
|
"changelogTitle": "# Changelog"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@semantic-release/github",
|
||||||
|
[
|
||||||
|
"@semantic-release/git",
|
||||||
|
{
|
||||||
|
"assets": [
|
||||||
|
"package.json",
|
||||||
|
"CHANGELOG.md"
|
||||||
|
],
|
||||||
|
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
37
CHANGELOG.md
Normal file
37
CHANGELOG.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [7.5.0](https://github.com/mediacms-io/mediacms/compare/v7.4.0...v7.5.0) (2026-02-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* bump version ([36d815c](https://github.com/mediacms-io/mediacms/commit/36d815c0cfbe21d3136541d410d545742b9ebecd))
|
||||||
|
|
||||||
|
## [7.4.0](https://github.com/mediacms-io/mediacms/compare/v7.3.0...v7.4.0) (2026-02-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add video player context menu with share/embed options ([#1472](https://github.com/mediacms-io/mediacms/issues/1472)) ([74952f6](https://github.com/mediacms-io/mediacms/commit/74952f68d79bc67617edb38eac62d2f5e7457565))
|
||||||
|
|
||||||
|
## [7.3.0](https://github.com/mediacms-io/mediacms/compare/v7.2.0...v7.3.0) (2026-02-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add package json for semantic release ([b405a04](https://github.com/mediacms-io/mediacms/commit/b405a04e346ca81b7d3f4e099eb984e7785cdd0f))
|
||||||
|
* add semantic release github actions ([76a27ae](https://github.com/mediacms-io/mediacms/commit/76a27ae25609178c1bd47c947b9f1a082c791d61))
|
||||||
|
* frontend unit tests ([1c15880](https://github.com/mediacms-io/mediacms/commit/1c15880ae3ef1ce77f53d5b473dfc0cc448b4977))
|
||||||
|
* Implement persistent "Embed Mode" to hide UI shell via Session Storage ([#1484](https://github.com/mediacms-io/mediacms/issues/1484)) ([223e870](https://github.com/mediacms-io/mediacms/commit/223e87073f7d5e44130c9976854cac670db0ae66))
|
||||||
|
* Improve Visual Distinction Between Trim and Chapters Editors ([#1445](https://github.com/mediacms-io/mediacms/issues/1445)) ([d9b1d6c](https://github.com/mediacms-io/mediacms/commit/d9b1d6cab1d2bdfc16f799a0a27b64313e2e0d22))
|
||||||
|
* semantic release ([b76282f](https://github.com/mediacms-io/mediacms/commit/b76282f9e465a39c2da5e9a22184d1db23de3f56))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add delay to task creation ([1b3cdfd](https://github.com/mediacms-io/mediacms/commit/1b3cdfd302abc5e69ebe01ca52b5091f3b24c0b2))
|
||||||
|
* Add regex denoter and improve celerybeat gitignore ([#1446](https://github.com/mediacms-io/mediacms/issues/1446)) ([90331f3](https://github.com/mediacms-io/mediacms/commit/90331f3b4a2a5737de9dd75ab45c096944813c42))
|
||||||
|
* adjust poster url for audio ([01912ea](https://github.com/mediacms-io/mediacms/commit/01912ea1f99ef43793a65712539d6264f1f6410f))
|
||||||
|
* Chapter numbering and preserve custom titles on segment reorder ([#1435](https://github.com/mediacms-io/mediacms/issues/1435)) ([cd7dd4f](https://github.com/mediacms-io/mediacms/commit/cd7dd4f72c9f0bac466c680f686a9ecfdd3a38dd))
|
||||||
|
* Show default chapter names in textarea instead of placeholder text ([#1428](https://github.com/mediacms-io/mediacms/issues/1428)) ([5eb6faf](https://github.com/mediacms-io/mediacms/commit/5eb6fafb8c6928b8bc3fe5f0c7af315273f78a55))
|
||||||
|
* static files ([#1429](https://github.com/mediacms-io/mediacms/issues/1429)) ([ba2c31b](https://github.com/mediacms-io/mediacms/commit/ba2c31b1e65b7f508dee598b1f2d86f01f9bf036))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
* update page link ([aeef828](https://github.com/mediacms-io/mediacms/commit/aeef8284bfba2a9a7f69c684f96c54f0e0e0cf92))
|
||||||
253
LTI_SETUP.md
253
LTI_SETUP.md
@@ -1,253 +0,0 @@
|
|||||||
# MediaCMS LTI 1.3 Integration Setup Guide
|
|
||||||
|
|
||||||
This guide walks you through integrating MediaCMS with a Learning Management System (LMS) like Moodle using LTI 1.3.
|
|
||||||
|
|
||||||
## 1. Configure MediaCMS Settings
|
|
||||||
|
|
||||||
Add these settings to `cms/local_settings.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Enable LTI integration
|
|
||||||
USE_LTI = True
|
|
||||||
|
|
||||||
# Enable RBAC for course-based access control
|
|
||||||
USE_RBAC = True
|
|
||||||
|
|
||||||
# Your production domain
|
|
||||||
FRONTEND_HOST = 'https://your-mediacms-domain.com'
|
|
||||||
ALLOWED_HOSTS = ['your-mediacms-domain.com', 'localhost']
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** LTI-specific cookie settings (SESSION_COOKIE_SAMESITE='None', etc.) are automatically applied when `USE_LTI=True`.
|
|
||||||
|
|
||||||
## 2. MediaCMS Configuration
|
|
||||||
|
|
||||||
### A. Verify HTTPS Setup
|
|
||||||
|
|
||||||
Ensure your MediaCMS server is running on HTTPS. LTI 1.3 requires HTTPS for security and iframe embedding.
|
|
||||||
|
|
||||||
### B. Register Your LMS Platform
|
|
||||||
|
|
||||||
1. Access Django Admin: `https://your-mediacms-domain.com/admin/lti/ltiplatform/`
|
|
||||||
2. Add new LTI Platform with these settings:
|
|
||||||
|
|
||||||
**Basic Info:**
|
|
||||||
- **Name:** My LMS (or any descriptive name)
|
|
||||||
- **Platform ID (Issuer):** Get this from your LMS (e.g., `https://mylms.example.com`)
|
|
||||||
- **Client ID:** You'll get this from your LMS after registering MediaCMS as an external tool
|
|
||||||
|
|
||||||
**OIDC Endpoints (get from your LMS):**
|
|
||||||
- **Auth Login URL:** `https://mylms.example.com/mod/lti/auth.php`
|
|
||||||
- **Auth Token URL:** `https://mylms.example.com/mod/lti/token.php`
|
|
||||||
- **Key Set URL:** `https://mylms.example.com/mod/lti/certs.php`
|
|
||||||
|
|
||||||
**Deployment IDs:** Add the deployment ID(s) provided by your LMS as a JSON list, e.g., `["1"]`
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- ✓ Enable NRPS (Names and Role Provisioning)
|
|
||||||
- ✓ Enable Deep Linking
|
|
||||||
- ✓ Auto-create categories
|
|
||||||
- ✓ Auto-create users
|
|
||||||
- ✓ Auto-sync roles
|
|
||||||
|
|
||||||
### C. Note MediaCMS URLs for LMS Configuration
|
|
||||||
|
|
||||||
You'll need these URLs when configuring your LMS:
|
|
||||||
|
|
||||||
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
|
|
||||||
- **OIDC Login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
|
|
||||||
- **JWK Set URL:** `https://your-mediacms-domain.com/lti/jwks/`
|
|
||||||
- **Redirection URI:** `https://your-mediacms-domain.com/lti/launch/`
|
|
||||||
- **Deep Linking URL:** `https://your-mediacms-domain.com/lti/select-media/`
|
|
||||||
|
|
||||||
## 3. LMS Configuration (Moodle Example)
|
|
||||||
|
|
||||||
### A. Register MediaCMS as External Tool
|
|
||||||
|
|
||||||
1. Navigate to: **Site administration → Plugins → Activity modules → External tool → Manage tools**
|
|
||||||
2. Click **Configure a tool manually** or add new tool
|
|
||||||
|
|
||||||
**Basic Settings:**
|
|
||||||
- **Tool name:** MediaCMS
|
|
||||||
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
|
|
||||||
- **LTI version:** LTI 1.3
|
|
||||||
- **Tool configuration usage:** Show in activity chooser
|
|
||||||
|
|
||||||
**URLs:**
|
|
||||||
- **Public keyset URL:** `https://your-mediacms-domain.com/lti/jwks/`
|
|
||||||
- **Initiate login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
|
|
||||||
- **Redirection URI(s):** `https://your-mediacms-domain.com/lti/launch/`
|
|
||||||
|
|
||||||
**Launch Settings:**
|
|
||||||
- **Default launch container:** Embed (without blocks) or New window
|
|
||||||
- **Accept grades from tool:** Optional
|
|
||||||
- **Share launcher's name:** Always ⚠️ **REQUIRED for user names**
|
|
||||||
- **Share launcher's email:** Always ⚠️ **REQUIRED for user emails**
|
|
||||||
|
|
||||||
> **Important:** MediaCMS creates user accounts automatically on first LTI launch. To ensure users have proper names and email addresses in MediaCMS, you **must** set both "Share launcher's name with tool" and "Share launcher's email with tool" to **Always** in the Privacy settings. Without these settings, users will be created with only a username based on their LTI user ID.
|
|
||||||
|
|
||||||
**Services:**
|
|
||||||
- ✓ IMS LTI Names and Role Provisioning (for roster sync)
|
|
||||||
- ✓ IMS LTI Deep Linking (for media selection)
|
|
||||||
|
|
||||||
**Tool Settings (Important for Deep Linking):**
|
|
||||||
- ✓ **Supports Deep Linking (Content-Item Message)** - Enable this to allow instructors to browse and select media from MediaCMS when adding activities
|
|
||||||
|
|
||||||
3. Save the tool configuration
|
|
||||||
|
|
||||||
### B. Copy Platform Details to MediaCMS
|
|
||||||
|
|
||||||
After saving, your LMS will provide:
|
|
||||||
- Platform ID (Issuer URL)
|
|
||||||
- Client ID
|
|
||||||
- Deployment ID
|
|
||||||
|
|
||||||
Copy these values back to the LTIPlatform configuration in MediaCMS admin (step 2B above).
|
|
||||||
|
|
||||||
### C. Using MediaCMS in Courses
|
|
||||||
|
|
||||||
**Option 1: Embed "My Media" view (Default)**
|
|
||||||
- In a course, add activity → External tool → MediaCMS
|
|
||||||
- Leave the custom URL blank (uses default launch URL)
|
|
||||||
- Students/teachers will see their MediaCMS profile in an iframe
|
|
||||||
|
|
||||||
**Option 2: Link to a Specific Video**
|
|
||||||
- Add activity → External tool → MediaCMS
|
|
||||||
- Activity name: "November 2020 Video" (or any descriptive name)
|
|
||||||
- In the activity settings, find **"Custom parameters"** (may be under "Privacy" or "Additional Settings")
|
|
||||||
- Add this parameter:
|
|
||||||
```
|
|
||||||
media_friendly_token=abc123def
|
|
||||||
```
|
|
||||||
- Replace `abc123def` with your video's token from MediaCMS (found in the URL: `/view?m=abc123def`)
|
|
||||||
- Students clicking this activity will go directly to that specific video
|
|
||||||
|
|
||||||
**Option 3: Link to Any MediaCMS Page**
|
|
||||||
- Add activity → External tool → MediaCMS
|
|
||||||
- In **"Custom parameters"**, add:
|
|
||||||
```
|
|
||||||
redirect_path=/featured
|
|
||||||
```
|
|
||||||
- Supported paths:
|
|
||||||
- `/featured` - Featured videos page
|
|
||||||
- `/latest` - Latest videos
|
|
||||||
- `/search/?q=keyword` - Search results
|
|
||||||
- `/category/category-name` - Specific category
|
|
||||||
- `/user/username` - User's profile
|
|
||||||
- Any other MediaCMS page path
|
|
||||||
|
|
||||||
**Option 4: Embed Specific Media via Deep Linking (Interactive)**
|
|
||||||
|
|
||||||
⚠️ **Prerequisite:** Ensure "Supports Deep Linking (Content-Item Message)" is enabled in the External Tool configuration (see section 3.A above)
|
|
||||||
|
|
||||||
When adding the activity to your course:
|
|
||||||
1. Add activity → External tool → MediaCMS
|
|
||||||
2. In the activity settings, enable **"Supports Deep Linking"** checkbox (may be under "Tool settings" or "Privacy" section)
|
|
||||||
3. Click **"Select content"** button → This launches the MediaCMS media browser
|
|
||||||
4. Browse and select media from MediaCMS (you can select multiple)
|
|
||||||
5. Click **"Add to course"** → Returns to Moodle with selected media configured
|
|
||||||
6. The activity will be automatically configured with the selected media's title and embed URL
|
|
||||||
7. Students clicking this activity will go directly to the selected media
|
|
||||||
|
|
||||||
### D. Custom Parameters - Complete Examples
|
|
||||||
|
|
||||||
**Example 1: Link to a specific video titled "Lecture 1 - Introduction"**
|
|
||||||
```
|
|
||||||
Activity Name: Lecture 1 - Introduction
|
|
||||||
Custom Parameters:
|
|
||||||
media_friendly_token=a1b2c3d4e5
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example 2: Link to course-specific videos**
|
|
||||||
```
|
|
||||||
Activity Name: Course Videos
|
|
||||||
Custom Parameters:
|
|
||||||
redirect_path=/category/biology101
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example 3: Link to search results for "genetics"**
|
|
||||||
```
|
|
||||||
Activity Name: Genetics Videos
|
|
||||||
Custom Parameters:
|
|
||||||
redirect_path=/search/?q=genetics
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example 4: Link to featured content**
|
|
||||||
```
|
|
||||||
Activity Name: Featured Videos
|
|
||||||
Custom Parameters:
|
|
||||||
redirect_path=/featured
|
|
||||||
```
|
|
||||||
|
|
||||||
**Where to find Custom Parameters in Moodle:**
|
|
||||||
1. When creating/editing the External Tool activity
|
|
||||||
2. Expand **"Privacy"** section, or look for **"Additional Settings"**
|
|
||||||
3. Find the **"Custom parameters"** text field
|
|
||||||
4. Enter one parameter per line in the format: `key=value`
|
|
||||||
|
|
||||||
## 4. Testing Checklist
|
|
||||||
|
|
||||||
- [ ] HTTPS is working on MediaCMS
|
|
||||||
- [ ] `USE_LTI = True` in local_settings.py
|
|
||||||
- [ ] LTIPlatform configured in Django admin
|
|
||||||
- [ ] External tool registered in LMS
|
|
||||||
- [ ] Launch from LMS creates new user in MediaCMS
|
|
||||||
- [ ] Course is mapped to MediaCMS category
|
|
||||||
- [ ] Users are added to RBAC group with correct roles
|
|
||||||
- [ ] Media from course category is visible to course members
|
|
||||||
- [ ] Public media is accessible
|
|
||||||
- [ ] Private media from other courses is not accessible
|
|
||||||
|
|
||||||
## 5. Default Role Mappings
|
|
||||||
|
|
||||||
The system automatically maps LMS roles to MediaCMS:
|
|
||||||
|
|
||||||
- **Instructor/Teacher** → advancedUser (global) + manager (course group)
|
|
||||||
- **Student/Learner** → user (global) + member (course group)
|
|
||||||
- **Teaching Assistant** → user (global) + contributor (course group)
|
|
||||||
- **Administrator** → manager (global) + manager (course group)
|
|
||||||
|
|
||||||
You can customize these in Django admin under **LTI Role Mappings**.
|
|
||||||
|
|
||||||
## 6. User Creation and Authentication
|
|
||||||
|
|
||||||
### User Creation via LTI
|
|
||||||
|
|
||||||
When a user launches MediaCMS from your LMS for the first time, a MediaCMS account is automatically created with:
|
|
||||||
- **Username:** Generated from email (preferred) or name, or a unique ID if neither is available
|
|
||||||
- **Email:** From LTI claim (if shared by LMS)
|
|
||||||
- **Name:** From LTI given_name/family_name claims (if shared by LMS)
|
|
||||||
- **Roles:** Mapped from LTI roles to MediaCMS permissions
|
|
||||||
- **Course membership:** Automatically added to the RBAC group for the course
|
|
||||||
|
|
||||||
### Privacy Settings Are Critical
|
|
||||||
|
|
||||||
⚠️ **For proper user accounts, you must configure the LTI tool's privacy settings in Moodle:**
|
|
||||||
|
|
||||||
1. Edit the External Tool configuration in Moodle
|
|
||||||
2. Go to the **Privacy** section
|
|
||||||
3. Set **"Share launcher's name with tool"** to **Always**
|
|
||||||
4. Set **"Share launcher's email with tool"** to **Always**
|
|
||||||
|
|
||||||
Without these settings:
|
|
||||||
- Users will not have proper names in MediaCMS
|
|
||||||
- Users will not have email addresses
|
|
||||||
- Usernames will be generic hashes (e.g., `lti_user_abc123def`)
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
Users created through LTI integration do **not** have a password set. They can only access MediaCMS through LTI launches from your LMS. This is intentional for security.
|
|
||||||
|
|
||||||
If you need a user to have both LTI access and direct login capability, manually set a password using:
|
|
||||||
```bash
|
|
||||||
python manage.py changepassword <username>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Need Help?
|
|
||||||
|
|
||||||
If you encounter issues, check:
|
|
||||||
- `/admin/lti/ltilaunchlog/` for launch attempt logs
|
|
||||||
- Django logs for detailed error messages
|
|
||||||
- Ensure HTTPS is properly configured (required for iframe cookies)
|
|
||||||
- Verify all URLs are correct and accessible
|
|
||||||
- Check that the Client ID and Deployment ID match between MediaCMS and your LMS
|
|
||||||
@@ -665,6 +665,5 @@ if USE_LTI:
|
|||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
CSRF_COOKIE_SAMESITE = 'None'
|
CSRF_COOKIE_SAMESITE = 'None'
|
||||||
CSRF_COOKIE_SECURE = True
|
CSRF_COOKIE_SECURE = True
|
||||||
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
# SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||||
# Use cached_db for reliability - stores in both cache AND database
|
# Consider using cached_db for reliability if sessions are lost between many LTI launches
|
||||||
# This prevents session loss during multiple simultaneous LTI launches
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VERSION = "7.8124"
|
VERSION = "8.37"
|
||||||
|
|||||||
@@ -73,3 +73,8 @@ Can be set through the SAML Configurations tab:
|
|||||||
3. **Group Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in groups that user will be added
|
3. **Group Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in groups that user will be added
|
||||||
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
|
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
|
||||||
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS
|
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS
|
||||||
|
|
||||||
|
|
||||||
|
## More options
|
||||||
|
USER_SEARCH_FIELD = "name_username_email"
|
||||||
|
ALLOW_MEDIA_REPLACEMENT = True
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ class CategoryAdmin(admin.ModelAdmin):
|
|||||||
list_filter.insert(0, "is_rbac_category")
|
list_filter.insert(0, "is_rbac_category")
|
||||||
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
list_filter.insert(-1, "identity_provider")
|
list_filter.insert(-1, "identity_provider")
|
||||||
|
if getattr(settings, 'USE_LTI', False):
|
||||||
|
list_filter.append("is_lms_course")
|
||||||
|
|
||||||
return list_filter
|
return list_filter
|
||||||
|
|
||||||
@@ -155,6 +157,8 @@ class CategoryAdmin(admin.ModelAdmin):
|
|||||||
list_display.insert(-1, "is_rbac_category")
|
list_display.insert(-1, "is_rbac_category")
|
||||||
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
list_display.insert(-1, "identity_provider")
|
list_display.insert(-1, "identity_provider")
|
||||||
|
if getattr(settings, 'USE_LTI', False):
|
||||||
|
list_display.insert(-1, "is_lms_course")
|
||||||
|
|
||||||
return list_display
|
return list_display
|
||||||
|
|
||||||
|
|||||||
@@ -123,10 +123,12 @@ class MediaPublishForm(forms.ModelForm):
|
|||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
"category": CategoryModalWidget(),
|
"category": CategoryModalWidget(),
|
||||||
|
"state": forms.RadioSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
self.user = user
|
self.user = user
|
||||||
|
self.request = kwargs.pop('request', None)
|
||||||
super(MediaPublishForm, self).__init__(*args, **kwargs)
|
super(MediaPublishForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.has_custom_permissions = self.instance.permissions.exists() if self.instance.pk else False
|
self.has_custom_permissions = self.instance.permissions.exists() if self.instance.pk else False
|
||||||
@@ -169,6 +171,16 @@ class MediaPublishForm(forms.ModelForm):
|
|||||||
|
|
||||||
self.fields['category'].queryset = Category.objects.filter(id__in=combined_category_ids).order_by('title')
|
self.fields['category'].queryset = Category.objects.filter(id__in=combined_category_ids).order_by('title')
|
||||||
|
|
||||||
|
# Filter for LMS courses only when in embed mode
|
||||||
|
if self.request and 'category' in self.fields:
|
||||||
|
is_embed_mode = self._check_embed_mode()
|
||||||
|
if is_embed_mode:
|
||||||
|
current_queryset = self.fields['category'].queryset
|
||||||
|
self.fields['category'].queryset = current_queryset.filter(is_lms_course=True)
|
||||||
|
self.fields['category'].label = 'Course'
|
||||||
|
self.fields['category'].help_text = 'Media can be shared with one or more courses'
|
||||||
|
self.fields['category'].widget.is_lms_mode = True
|
||||||
|
|
||||||
self.helper = FormHelper()
|
self.helper = FormHelper()
|
||||||
self.helper.form_tag = True
|
self.helper.form_tag = True
|
||||||
self.helper.form_class = 'post-form'
|
self.helper.form_class = 'post-form'
|
||||||
@@ -186,6 +198,22 @@ class MediaPublishForm(forms.ModelForm):
|
|||||||
|
|
||||||
self.helper.layout.append(FormActions(Submit('submit', 'Publish Media', css_class='primaryAction')))
|
self.helper.layout.append(FormActions(Submit('submit', 'Publish Media', css_class='primaryAction')))
|
||||||
|
|
||||||
|
def _check_embed_mode(self):
|
||||||
|
"""Check if the current request is in embed mode"""
|
||||||
|
if not self.request:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check query parameter
|
||||||
|
mode = self.request.GET.get('mode', '')
|
||||||
|
if mode == 'lms_embed_mode':
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check session storage
|
||||||
|
if self.request.session.get('lms_embed_mode') == 'true':
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
state = cleaned_data.get("state")
|
state = cleaned_data.get("state")
|
||||||
|
|||||||
@@ -57,4 +57,4 @@ def translate_string(language_code, string):
|
|||||||
if not check_language_code(language_code):
|
if not check_language_code(language_code):
|
||||||
return string
|
return string
|
||||||
|
|
||||||
return translation_strings[language_code].get(string, string)
|
return translation_strings[language_code].get(string) or string
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "حذف الوسائط",
|
"DELETE MEDIA": "حذف الوسائط",
|
||||||
"DOWNLOAD": "تحميل",
|
"DOWNLOAD": "تحميل",
|
||||||
"DURATION": "المدة",
|
"DURATION": "المدة",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "حذف الوسائط",
|
"Delete Media": "حذف الوسائط",
|
||||||
"Delete media": "حذف الوسائط",
|
"Delete media": "حذف الوسائط",
|
||||||
"Disable Comments": "تعطيل التعليقات",
|
"Disable Comments": "تعطيل التعليقات",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "فشل تغيير المالك. يرجى المحاولة مرة أخرى.",
|
"Failed to change owner. Please try again.": "فشل تغيير المالك. يرجى المحاولة مرة أخرى.",
|
||||||
"Failed to copy media.": "فشل نسخ الوسائط.",
|
"Failed to copy media.": "فشل نسخ الوسائط.",
|
||||||
"Failed to create playlist": "فشل إنشاء قائمة التشغيل",
|
"Failed to create playlist": "فشل إنشاء قائمة التشغيل",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "فشل حذف الوسائط. يرجى المحاولة مرة أخرى.",
|
"Failed to delete media. Please try again.": "فشل حذف الوسائط. يرجى المحاولة مرة أخرى.",
|
||||||
"Failed to disable comments.": "فشل تعطيل التعليقات.",
|
"Failed to disable comments.": "فشل تعطيل التعليقات.",
|
||||||
"Failed to disable download.": "فشل تعطيل التنزيل.",
|
"Failed to disable download.": "فشل تعطيل التنزيل.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "تصفية المستخدمين الموجودين...",
|
"Filter existing users...": "تصفية المستخدمين الموجودين...",
|
||||||
"Filter playlists...": "تصفية قوائم التشغيل...",
|
"Filter playlists...": "تصفية قوائم التشغيل...",
|
||||||
"Filters": "الفلاتر",
|
"Filters": "الفلاتر",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "اذهب",
|
"Go": "اذهب",
|
||||||
"History": "التاريخ",
|
"History": "التاريخ",
|
||||||
"Home": "الرئيسية",
|
"Home": "الرئيسية",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "إدارة التعليقات",
|
"Manage comments": "إدارة التعليقات",
|
||||||
"Manage media": "إدارة الوسائط",
|
"Manage media": "إدارة الوسائط",
|
||||||
"Manage users": "إدارة المستخدمين",
|
"Manage users": "إدارة المستخدمين",
|
||||||
|
"Management": "",
|
||||||
"Media": "وسائط",
|
"Media": "وسائط",
|
||||||
"Media I own": "الوسائط التي أمتلكها",
|
"Media I own": "الوسائط التي أمتلكها",
|
||||||
"Media was edited": "تم تعديل الوسائط",
|
"Media was edited": "تم تعديل الوسائط",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "لا توجد نتائج لـ",
|
"No results for": "لا توجد نتائج لـ",
|
||||||
"No tags": "لا توجد علامات",
|
"No tags": "لا توجد علامات",
|
||||||
"No users to add": "لا يوجد مستخدمون لإضافتهم",
|
"No users to add": "لا يوجد مستخدمون لإضافتهم",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "قوائم التشغيل",
|
"PLAYLISTS": "قوائم التشغيل",
|
||||||
"PUBLISH STATE": "حالة النشر",
|
"PUBLISH STATE": "حالة النشر",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "نشر في",
|
"Published on": "نشر في",
|
||||||
"Recent uploads": "التحميلات الأخيرة",
|
"Recent uploads": "التحميلات الأخيرة",
|
||||||
"Recommended": "موصى به",
|
"Recommended": "موصى به",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "تسجيل الشاشة",
|
"Record Screen": "تسجيل الشاشة",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "تسجيل",
|
"Register": "تسجيل",
|
||||||
"Remove category": "إزالة الفئة",
|
"Remove category": "إزالة الفئة",
|
||||||
"Remove from list": "إزالة من القائمة",
|
"Remove from list": "إزالة من القائمة",
|
||||||
"Remove tag": "إزالة العلامة",
|
"Remove tag": "إزالة العلامة",
|
||||||
"Remove user": "إزالة المستخدم",
|
"Remove user": "إزالة المستخدم",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "حفظ",
|
"SAVE": "حفظ",
|
||||||
"SEARCH": "بحث",
|
"SEARCH": "بحث",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "تحديد جميع الوسائط",
|
"Select all media": "تحديد جميع الوسائط",
|
||||||
"Select publish state:": "اختر حالة النشر:",
|
"Select publish state:": "اختر حالة النشر:",
|
||||||
"Selected": "محدد",
|
"Selected": "محدد",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "مشاركة مني",
|
"Shared by me": "مشاركة مني",
|
||||||
"Shared with me": "مشاركة معي",
|
"Shared with me": "مشاركة معي",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "تسجيل الدخول",
|
"Sign in": "تسجيل الدخول",
|
||||||
"Sign out": "تسجيل الخروج",
|
"Sign out": "تسجيل الخروج",
|
||||||
"Sort By": "ترتيب حسب",
|
"Sort By": "ترتيب حسب",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "تم تفعيل التعليقات بنجاح",
|
"Successfully Enabled comments": "تم تفعيل التعليقات بنجاح",
|
||||||
"Successfully changed owner": "تم تغيير المالك بنجاح",
|
"Successfully changed owner": "تم تغيير المالك بنجاح",
|
||||||
"Successfully deleted": "تم الحذف بنجاح",
|
"Successfully deleted": "تم الحذف بنجاح",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "تم التحديث بنجاح",
|
"Successfully updated": "تم التحديث بنجاح",
|
||||||
"Successfully updated categories": "تم تحديث الفئات بنجاح",
|
"Successfully updated categories": "تم تحديث الفئات بنجاح",
|
||||||
"Successfully updated playlist membership": "تم تحديث عضوية قائمة التشغيل بنجاح",
|
"Successfully updated playlist membership": "تم تحديث عضوية قائمة التشغيل بنجاح",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "رفع الوسائط",
|
"Upload media": "رفع الوسائط",
|
||||||
"Uploads": "التحميلات",
|
"Uploads": "التحميلات",
|
||||||
"Users": "المستخدمون",
|
"Users": "المستخدمون",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "عرض الكل",
|
"VIEW ALL": "عرض الكل",
|
||||||
"Video": "فيديو",
|
"Video": "فيديو",
|
||||||
"View all": "عرض الكل",
|
"View all": "عرض الكل",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "مرحباً",
|
"Welcome": "مرحباً",
|
||||||
"You are going to copy": "سوف تقوم بالنسخ",
|
"You are going to copy": "سوف تقوم بالنسخ",
|
||||||
"You are going to delete": "سوف تقوم بالحذف",
|
"You are going to delete": "سوف تقوم بالحذف",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "سوف تقوم بتعطيل التعليقات لـ",
|
"You are going to disable comments to": "سوف تقوم بتعطيل التعليقات لـ",
|
||||||
"You are going to disable download for": "سوف تقوم بتعطيل التنزيل لـ",
|
"You are going to disable download for": "سوف تقوم بتعطيل التنزيل لـ",
|
||||||
"You are going to enable comments to": "سوف تقوم بتفعيل التعليقات لـ",
|
"You are going to enable comments to": "سوف تقوم بتفعيل التعليقات لـ",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "মিডিয়া মুছুন",
|
"DELETE MEDIA": "মিডিয়া মুছুন",
|
||||||
"DOWNLOAD": "ডাউনলোড",
|
"DOWNLOAD": "ডাউনলোড",
|
||||||
"DURATION": "সময়কাল",
|
"DURATION": "সময়কাল",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "",
|
"Delete Media": "",
|
||||||
"Delete media": "মিডিয়া মুছুন",
|
"Delete media": "মিডিয়া মুছুন",
|
||||||
"Disable Comments": "",
|
"Disable Comments": "",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "",
|
"Failed to change owner. Please try again.": "",
|
||||||
"Failed to copy media.": "মিডিয়া কপি করতে ব্যর্থ হয়েছে।",
|
"Failed to copy media.": "মিডিয়া কপি করতে ব্যর্থ হয়েছে।",
|
||||||
"Failed to create playlist": "",
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "মিডিয়া মুছতে ব্যর্থ হয়েছে। দয়া করে আবার চেষ্টা করুন।",
|
"Failed to delete media. Please try again.": "মিডিয়া মুছতে ব্যর্থ হয়েছে। দয়া করে আবার চেষ্টা করুন।",
|
||||||
"Failed to disable comments.": "মন্তব্য নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
|
"Failed to disable comments.": "মন্তব্য নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
|
||||||
"Failed to disable download.": "ডাউনলোড নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
|
"Failed to disable download.": "ডাউনলোড নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "",
|
"Filter existing users...": "",
|
||||||
"Filter playlists...": "",
|
"Filter playlists...": "",
|
||||||
"Filters": "ফিল্টার",
|
"Filters": "ফিল্টার",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "যাও",
|
"Go": "যাও",
|
||||||
"History": "ইতিহাস",
|
"History": "ইতিহাস",
|
||||||
"Home": "বাড়ি",
|
"Home": "বাড়ি",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "মন্তব্য পরিচালনা করুন",
|
"Manage comments": "মন্তব্য পরিচালনা করুন",
|
||||||
"Manage media": "মিডিয়া পরিচালনা করুন",
|
"Manage media": "মিডিয়া পরিচালনা করুন",
|
||||||
"Manage users": "ব্যবহারকারীদের পরিচালনা করুন",
|
"Manage users": "ব্যবহারকারীদের পরিচালনা করুন",
|
||||||
|
"Management": "",
|
||||||
"Media": "মিডিয়া",
|
"Media": "মিডিয়া",
|
||||||
"Media I own": "",
|
"Media I own": "",
|
||||||
"Media was edited": "মিডিয়া সম্পাদিত হয়েছে",
|
"Media was edited": "মিডিয়া সম্পাদিত হয়েছে",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "এর জন্য কোন ফলাফল নেই",
|
"No results for": "এর জন্য কোন ফলাফল নেই",
|
||||||
"No tags": "",
|
"No tags": "",
|
||||||
"No users to add": "",
|
"No users to add": "",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "প্লেলিস্ট",
|
"PLAYLISTS": "প্লেলিস্ট",
|
||||||
"PUBLISH STATE": "প্রকাশের অবস্থা",
|
"PUBLISH STATE": "প্রকাশের অবস্থা",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "প্রকাশিত",
|
"Published on": "প্রকাশিত",
|
||||||
"Recent uploads": "সাম্প্রতিক আপলোড",
|
"Recent uploads": "সাম্প্রতিক আপলোড",
|
||||||
"Recommended": "প্রস্তাবিত",
|
"Recommended": "প্রস্তাবিত",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "স্ক্রিন রেকর্ড করুন",
|
"Record Screen": "স্ক্রিন রেকর্ড করুন",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "নিবন্ধন করুন",
|
"Register": "নিবন্ধন করুন",
|
||||||
"Remove category": "",
|
"Remove category": "",
|
||||||
"Remove from list": "",
|
"Remove from list": "",
|
||||||
"Remove tag": "",
|
"Remove tag": "",
|
||||||
"Remove user": "",
|
"Remove user": "",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "সংরক্ষণ করুন",
|
"SAVE": "সংরক্ষণ করুন",
|
||||||
"SEARCH": "অনুসন্ধান",
|
"SEARCH": "অনুসন্ধান",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "",
|
"Select all media": "",
|
||||||
"Select publish state:": "",
|
"Select publish state:": "",
|
||||||
"Selected": "",
|
"Selected": "",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "আমার দ্বারা শেয়ার করা",
|
"Shared by me": "আমার দ্বারা শেয়ার করা",
|
||||||
"Shared with me": "আমার সাথে শেয়ার করা",
|
"Shared with me": "আমার সাথে শেয়ার করা",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "সাইন ইন করুন",
|
"Sign in": "সাইন ইন করুন",
|
||||||
"Sign out": "সাইন আউট করুন",
|
"Sign out": "সাইন আউট করুন",
|
||||||
"Sort By": "সাজান",
|
"Sort By": "সাজান",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "মন্তব্য সফলভাবে সক্রিয় হয়েছে",
|
"Successfully Enabled comments": "মন্তব্য সফলভাবে সক্রিয় হয়েছে",
|
||||||
"Successfully changed owner": "",
|
"Successfully changed owner": "",
|
||||||
"Successfully deleted": "সফলভাবে মুছে ফেলা হয়েছে",
|
"Successfully deleted": "সফলভাবে মুছে ফেলা হয়েছে",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "",
|
"Successfully updated": "",
|
||||||
"Successfully updated categories": "",
|
"Successfully updated categories": "",
|
||||||
"Successfully updated playlist membership": "",
|
"Successfully updated playlist membership": "",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "মিডিয়া আপলোড করুন",
|
"Upload media": "মিডিয়া আপলোড করুন",
|
||||||
"Uploads": "আপলোডসমূহ",
|
"Uploads": "আপলোডসমূহ",
|
||||||
"Users": "",
|
"Users": "",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "সব দেখুন",
|
"VIEW ALL": "সব দেখুন",
|
||||||
"Video": "ভিডিও",
|
"Video": "ভিডিও",
|
||||||
"View all": "সব দেখুন",
|
"View all": "সব দেখুন",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "স্বাগতম",
|
"Welcome": "স্বাগতম",
|
||||||
"You are going to copy": "আপনি কপি করতে চলেছেন",
|
"You are going to copy": "আপনি কপি করতে চলেছেন",
|
||||||
"You are going to delete": "আপনি মুছে ফেলতে চলেছেন",
|
"You are going to delete": "আপনি মুছে ফেলতে চলেছেন",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "আপনি মন্তব্য নিষ্ক্রিয় করতে চলেছেন",
|
"You are going to disable comments to": "আপনি মন্তব্য নিষ্ক্রিয় করতে চলেছেন",
|
||||||
"You are going to disable download for": "আপনি ডাউনলোড নিষ্ক্রিয় করতে চলেছেন",
|
"You are going to disable download for": "আপনি ডাউনলোড নিষ্ক্রিয় করতে চলেছেন",
|
||||||
"You are going to enable comments to": "আপনি মন্তব্য সক্রিয় করতে চলেছেন",
|
"You are going to enable comments to": "আপনি মন্তব্য সক্রিয় করতে চলেছেন",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "SLET MEDIE",
|
"DELETE MEDIA": "SLET MEDIE",
|
||||||
"DOWNLOAD": "HENT",
|
"DOWNLOAD": "HENT",
|
||||||
"DURATION": "VARIGHED",
|
"DURATION": "VARIGHED",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Slet Medie",
|
"Delete Media": "Slet Medie",
|
||||||
"Delete media": "Slet medie",
|
"Delete media": "Slet medie",
|
||||||
"Disable Comments": "Deaktiver Kommentarer",
|
"Disable Comments": "Deaktiver Kommentarer",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Ændring af ejer mislykkedes. Prøv venligst igen.",
|
"Failed to change owner. Please try again.": "Ændring af ejer mislykkedes. Prøv venligst igen.",
|
||||||
"Failed to copy media.": "Kopiering af medie mislykkedes.",
|
"Failed to copy media.": "Kopiering af medie mislykkedes.",
|
||||||
"Failed to create playlist": "Oprettelse af playliste mislykkedes",
|
"Failed to create playlist": "Oprettelse af playliste mislykkedes",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Sletning af medie mislykkedes. Prøv venligst igen.",
|
"Failed to delete media. Please try again.": "Sletning af medie mislykkedes. Prøv venligst igen.",
|
||||||
"Failed to disable comments.": "Deaktivering af kommentarer mislykkedes.",
|
"Failed to disable comments.": "Deaktivering af kommentarer mislykkedes.",
|
||||||
"Failed to disable download.": "Deaktivering af download mislykkedes.",
|
"Failed to disable download.": "Deaktivering af download mislykkedes.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filtrer eksisterende brugere...",
|
"Filter existing users...": "Filtrer eksisterende brugere...",
|
||||||
"Filter playlists...": "Filtrer playlister...",
|
"Filter playlists...": "Filtrer playlister...",
|
||||||
"Filters": "Filtre",
|
"Filters": "Filtre",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Vælg",
|
"Go": "Vælg",
|
||||||
"History": "Historik",
|
"History": "Historik",
|
||||||
"Home": "Hjem",
|
"Home": "Hjem",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Administrer kommentarer",
|
"Manage comments": "Administrer kommentarer",
|
||||||
"Manage media": "Administrer medier",
|
"Manage media": "Administrer medier",
|
||||||
"Manage users": "Administrer brugere",
|
"Manage users": "Administrer brugere",
|
||||||
|
"Management": "",
|
||||||
"Media": "Medier",
|
"Media": "Medier",
|
||||||
"Media I own": "Medier jeg ejer",
|
"Media I own": "Medier jeg ejer",
|
||||||
"Media was edited": "Mediet er blevet redigeret",
|
"Media was edited": "Mediet er blevet redigeret",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Ingen resultater for",
|
"No results for": "Ingen resultater for",
|
||||||
"No tags": "Ingen tags",
|
"No tags": "Ingen tags",
|
||||||
"No users to add": "Ingen brugere at tilføje",
|
"No users to add": "Ingen brugere at tilføje",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "PLAYLISTER",
|
"PLAYLISTS": "PLAYLISTER",
|
||||||
"PUBLISH STATE": "PUBLICERINGSSTATUS",
|
"PUBLISH STATE": "PUBLICERINGSSTATUS",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Udgivet på",
|
"Published on": "Udgivet på",
|
||||||
"Recent uploads": "Nylige uploads",
|
"Recent uploads": "Nylige uploads",
|
||||||
"Recommended": "Anbefalet",
|
"Recommended": "Anbefalet",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Optag skærm",
|
"Record Screen": "Optag skærm",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registrer",
|
"Register": "Registrer",
|
||||||
"Remove category": "Fjern kategori",
|
"Remove category": "Fjern kategori",
|
||||||
"Remove from list": "Fjern fra liste",
|
"Remove from list": "Fjern fra liste",
|
||||||
"Remove tag": "Fjern tag",
|
"Remove tag": "Fjern tag",
|
||||||
"Remove user": "Fjern bruger",
|
"Remove user": "Fjern bruger",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "GEM",
|
"SAVE": "GEM",
|
||||||
"SEARCH": "SØG",
|
"SEARCH": "SØG",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Vælg alle medier",
|
"Select all media": "Vælg alle medier",
|
||||||
"Select publish state:": "Vælg publiceringsstatus:",
|
"Select publish state:": "Vælg publiceringsstatus:",
|
||||||
"Selected": "Valgt",
|
"Selected": "Valgt",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Delt af mig",
|
"Shared by me": "Delt af mig",
|
||||||
"Shared with me": "Delt med mig",
|
"Shared with me": "Delt med mig",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Log ind",
|
"Sign in": "Log ind",
|
||||||
"Sign out": "Log ud",
|
"Sign out": "Log ud",
|
||||||
"Sort By": "Sorter efter",
|
"Sort By": "Sorter efter",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Kommentarer aktiveret med succes",
|
"Successfully Enabled comments": "Kommentarer aktiveret med succes",
|
||||||
"Successfully changed owner": "Ejer ændret med succes",
|
"Successfully changed owner": "Ejer ændret med succes",
|
||||||
"Successfully deleted": "Slettet med succes",
|
"Successfully deleted": "Slettet med succes",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Opdateret med succes",
|
"Successfully updated": "Opdateret med succes",
|
||||||
"Successfully updated categories": "Kategorier opdateret med succes",
|
"Successfully updated categories": "Kategorier opdateret med succes",
|
||||||
"Successfully updated playlist membership": "Playlistemedlemskab opdateret med succes",
|
"Successfully updated playlist membership": "Playlistemedlemskab opdateret med succes",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Upload medie",
|
"Upload media": "Upload medie",
|
||||||
"Uploads": "Uploads",
|
"Uploads": "Uploads",
|
||||||
"Users": "Brugere",
|
"Users": "Brugere",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "SE ALLE",
|
"VIEW ALL": "SE ALLE",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Se alle",
|
"View all": "Se alle",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Velkommen",
|
"Welcome": "Velkommen",
|
||||||
"You are going to copy": "Du er ved at kopiere",
|
"You are going to copy": "Du er ved at kopiere",
|
||||||
"You are going to delete": "Du er ved at slette",
|
"You are going to delete": "Du er ved at slette",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Du er ved at deaktivere kommentarer til",
|
"You are going to disable comments to": "Du er ved at deaktivere kommentarer til",
|
||||||
"You are going to disable download for": "Du er ved at deaktivere download for",
|
"You are going to disable download for": "Du er ved at deaktivere download for",
|
||||||
"You are going to enable comments to": "Du er ved at aktivere kommentarer til",
|
"You are going to enable comments to": "Du er ved at aktivere kommentarer til",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "MEDIEN LÖSCHEN",
|
"DELETE MEDIA": "MEDIEN LÖSCHEN",
|
||||||
"DOWNLOAD": "HERUNTERLADEN",
|
"DOWNLOAD": "HERUNTERLADEN",
|
||||||
"DURATION": "DAUER",
|
"DURATION": "DAUER",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Medien löschen",
|
"Delete Media": "Medien löschen",
|
||||||
"Delete media": "Medien löschen",
|
"Delete media": "Medien löschen",
|
||||||
"Disable Comments": "Kommentare deaktivieren",
|
"Disable Comments": "Kommentare deaktivieren",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Fehler beim Ändern des Eigentümers. Bitte versuchen Sie es erneut.",
|
"Failed to change owner. Please try again.": "Fehler beim Ändern des Eigentümers. Bitte versuchen Sie es erneut.",
|
||||||
"Failed to copy media.": "Fehler beim Kopieren der Medien.",
|
"Failed to copy media.": "Fehler beim Kopieren der Medien.",
|
||||||
"Failed to create playlist": "Fehler beim Erstellen der Playlist",
|
"Failed to create playlist": "Fehler beim Erstellen der Playlist",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Fehler beim Löschen der Medien. Bitte versuchen Sie es erneut.",
|
"Failed to delete media. Please try again.": "Fehler beim Löschen der Medien. Bitte versuchen Sie es erneut.",
|
||||||
"Failed to disable comments.": "Fehler beim Deaktivieren der Kommentare.",
|
"Failed to disable comments.": "Fehler beim Deaktivieren der Kommentare.",
|
||||||
"Failed to disable download.": "Fehler beim Deaktivieren des Downloads.",
|
"Failed to disable download.": "Fehler beim Deaktivieren des Downloads.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Vorhandene Benutzer filtern...",
|
"Filter existing users...": "Vorhandene Benutzer filtern...",
|
||||||
"Filter playlists...": "Playlists filtern...",
|
"Filter playlists...": "Playlists filtern...",
|
||||||
"Filters": "Filter",
|
"Filters": "Filter",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Los",
|
"Go": "Los",
|
||||||
"History": "Verlauf",
|
"History": "Verlauf",
|
||||||
"Home": "Startseite",
|
"Home": "Startseite",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Kommentare verwalten",
|
"Manage comments": "Kommentare verwalten",
|
||||||
"Manage media": "Medien verwalten",
|
"Manage media": "Medien verwalten",
|
||||||
"Manage users": "Benutzer verwalten",
|
"Manage users": "Benutzer verwalten",
|
||||||
|
"Management": "",
|
||||||
"Media": "Medien",
|
"Media": "Medien",
|
||||||
"Media I own": "Medien, die mir gehören",
|
"Media I own": "Medien, die mir gehören",
|
||||||
"Media was edited": "Medien wurden bearbeitet",
|
"Media was edited": "Medien wurden bearbeitet",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Keine Ergebnisse für",
|
"No results for": "Keine Ergebnisse für",
|
||||||
"No tags": "Keine Tags",
|
"No tags": "Keine Tags",
|
||||||
"No users to add": "Keine Benutzer hinzuzufügen",
|
"No users to add": "Keine Benutzer hinzuzufügen",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "PLAYLISTS",
|
"PLAYLISTS": "PLAYLISTS",
|
||||||
"PUBLISH STATE": "VERÖFFENTLICHUNGSSTATUS",
|
"PUBLISH STATE": "VERÖFFENTLICHUNGSSTATUS",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Veröffentlicht am",
|
"Published on": "Veröffentlicht am",
|
||||||
"Recent uploads": "Neue Uploads",
|
"Recent uploads": "Neue Uploads",
|
||||||
"Recommended": "Empfohlen",
|
"Recommended": "Empfohlen",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Bildschirm aufnehmen",
|
"Record Screen": "Bildschirm aufnehmen",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registrieren",
|
"Register": "Registrieren",
|
||||||
"Remove category": "Kategorie entfernen",
|
"Remove category": "Kategorie entfernen",
|
||||||
"Remove from list": "Aus Liste entfernen",
|
"Remove from list": "Aus Liste entfernen",
|
||||||
"Remove tag": "Tag entfernen",
|
"Remove tag": "Tag entfernen",
|
||||||
"Remove user": "Benutzer entfernen",
|
"Remove user": "Benutzer entfernen",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "SPEICHERN",
|
"SAVE": "SPEICHERN",
|
||||||
"SEARCH": "SUCHE",
|
"SEARCH": "SUCHE",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Alle Medien auswählen",
|
"Select all media": "Alle Medien auswählen",
|
||||||
"Select publish state:": "Veröffentlichungsstatus auswählen:",
|
"Select publish state:": "Veröffentlichungsstatus auswählen:",
|
||||||
"Selected": "Ausgewählt",
|
"Selected": "Ausgewählt",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Von mir geteilt",
|
"Shared by me": "Von mir geteilt",
|
||||||
"Shared with me": "Mit mir geteilt",
|
"Shared with me": "Mit mir geteilt",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Anmelden",
|
"Sign in": "Anmelden",
|
||||||
"Sign out": "Abmelden",
|
"Sign out": "Abmelden",
|
||||||
"Sort By": "Sortieren nach",
|
"Sort By": "Sortieren nach",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Kommentare erfolgreich aktiviert",
|
"Successfully Enabled comments": "Kommentare erfolgreich aktiviert",
|
||||||
"Successfully changed owner": "Eigentümer erfolgreich geändert",
|
"Successfully changed owner": "Eigentümer erfolgreich geändert",
|
||||||
"Successfully deleted": "Erfolgreich gelöscht",
|
"Successfully deleted": "Erfolgreich gelöscht",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Erfolgreich aktualisiert",
|
"Successfully updated": "Erfolgreich aktualisiert",
|
||||||
"Successfully updated categories": "Kategorien erfolgreich aktualisiert",
|
"Successfully updated categories": "Kategorien erfolgreich aktualisiert",
|
||||||
"Successfully updated playlist membership": "Playlist-Mitgliedschaft erfolgreich aktualisiert",
|
"Successfully updated playlist membership": "Playlist-Mitgliedschaft erfolgreich aktualisiert",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Medien hochladen",
|
"Upload media": "Medien hochladen",
|
||||||
"Uploads": "Uploads",
|
"Uploads": "Uploads",
|
||||||
"Users": "Benutzer",
|
"Users": "Benutzer",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "ALLE ANZEIGEN",
|
"VIEW ALL": "ALLE ANZEIGEN",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Alle anzeigen",
|
"View all": "Alle anzeigen",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Willkommen",
|
"Welcome": "Willkommen",
|
||||||
"You are going to copy": "Sie werden kopieren",
|
"You are going to copy": "Sie werden kopieren",
|
||||||
"You are going to delete": "Sie werden löschen",
|
"You are going to delete": "Sie werden löschen",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Sie werden Kommentare deaktivieren für",
|
"You are going to disable comments to": "Sie werden Kommentare deaktivieren für",
|
||||||
"You are going to disable download for": "Sie werden Download deaktivieren für",
|
"You are going to disable download for": "Sie werden Download deaktivieren für",
|
||||||
"You are going to enable comments to": "Sie werden Kommentare aktivieren für",
|
"You are going to enable comments to": "Sie werden Kommentare aktivieren für",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "ΔΙΑΓΡΑΦΗ ΑΡΧΕΙΟΥ",
|
"DELETE MEDIA": "ΔΙΑΓΡΑΦΗ ΑΡΧΕΙΟΥ",
|
||||||
"DOWNLOAD": "ΚΑΤΕΒΑΣΜΑ",
|
"DOWNLOAD": "ΚΑΤΕΒΑΣΜΑ",
|
||||||
"DURATION": "ΔΙΑΡΚΕΙΑ",
|
"DURATION": "ΔΙΑΡΚΕΙΑ",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Διαγραφή Αρχείου",
|
"Delete Media": "Διαγραφή Αρχείου",
|
||||||
"Delete media": "Διαγραφή αρχείου",
|
"Delete media": "Διαγραφή αρχείου",
|
||||||
"Disable Comments": "Απενεργοποίηση Σχολίων",
|
"Disable Comments": "Απενεργοποίηση Σχολίων",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Αποτυχία αλλαγής ιδιοκτήτη. Παρακαλώ δοκιμάστε ξανά.",
|
"Failed to change owner. Please try again.": "Αποτυχία αλλαγής ιδιοκτήτη. Παρακαλώ δοκιμάστε ξανά.",
|
||||||
"Failed to copy media.": "Αποτυχία αντιγραφής αρχείου.",
|
"Failed to copy media.": "Αποτυχία αντιγραφής αρχείου.",
|
||||||
"Failed to create playlist": "Αποτυχία δημιουργίας λίστας",
|
"Failed to create playlist": "Αποτυχία δημιουργίας λίστας",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Αποτυχία διαγραφής αρχείου. Παρακαλώ δοκιμάστε ξανά.",
|
"Failed to delete media. Please try again.": "Αποτυχία διαγραφής αρχείου. Παρακαλώ δοκιμάστε ξανά.",
|
||||||
"Failed to disable comments.": "Αποτυχία απενεργοποίησης σχολίων.",
|
"Failed to disable comments.": "Αποτυχία απενεργοποίησης σχολίων.",
|
||||||
"Failed to disable download.": "Αποτυχία απενεργοποίησης λήψης.",
|
"Failed to disable download.": "Αποτυχία απενεργοποίησης λήψης.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Φιλτράρισμα υπαρχόντων χρηστών...",
|
"Filter existing users...": "Φιλτράρισμα υπαρχόντων χρηστών...",
|
||||||
"Filter playlists...": "Φιλτράρισμα λιστών...",
|
"Filter playlists...": "Φιλτράρισμα λιστών...",
|
||||||
"Filters": "Φίλτρα",
|
"Filters": "Φίλτρα",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Μετάβαση",
|
"Go": "Μετάβαση",
|
||||||
"History": "Ιστορικό",
|
"History": "Ιστορικό",
|
||||||
"Home": "Αρχική",
|
"Home": "Αρχική",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Διαχείριση σχολίων",
|
"Manage comments": "Διαχείριση σχολίων",
|
||||||
"Manage media": "Διαχείριση αρχείων",
|
"Manage media": "Διαχείριση αρχείων",
|
||||||
"Manage users": "Διαχείριση χρηστών",
|
"Manage users": "Διαχείριση χρηστών",
|
||||||
|
"Management": "",
|
||||||
"Media": "Αρχεία",
|
"Media": "Αρχεία",
|
||||||
"Media I own": "Δικά μου αρχεία",
|
"Media I own": "Δικά μου αρχεία",
|
||||||
"Media was edited": "Το αρχείο επεξεργάστηκε",
|
"Media was edited": "Το αρχείο επεξεργάστηκε",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Δεν υπάρχουν αποτελέσματα για",
|
"No results for": "Δεν υπάρχουν αποτελέσματα για",
|
||||||
"No tags": "Δεν υπάρχουν ετικέτες",
|
"No tags": "Δεν υπάρχουν ετικέτες",
|
||||||
"No users to add": "Δεν υπάρχουν χρήστες για προσθήκη",
|
"No users to add": "Δεν υπάρχουν χρήστες για προσθήκη",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "ΛΙΣΤΕΣ",
|
"PLAYLISTS": "ΛΙΣΤΕΣ",
|
||||||
"PUBLISH STATE": "ΚΑΤΑΣΤΑΣΗ ΔΗΜΟΣΙΕΥΣΗΣ",
|
"PUBLISH STATE": "ΚΑΤΑΣΤΑΣΗ ΔΗΜΟΣΙΕΥΣΗΣ",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Δημοσιεύτηκε στις",
|
"Published on": "Δημοσιεύτηκε στις",
|
||||||
"Recent uploads": "Πρόσφατα ανεβάσματα",
|
"Recent uploads": "Πρόσφατα ανεβάσματα",
|
||||||
"Recommended": "Προτεινόμενα",
|
"Recommended": "Προτεινόμενα",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Καταγραφή οθόνης",
|
"Record Screen": "Καταγραφή οθόνης",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Εγγραφή",
|
"Register": "Εγγραφή",
|
||||||
"Remove category": "Αφαίρεση κατηγορίας",
|
"Remove category": "Αφαίρεση κατηγορίας",
|
||||||
"Remove from list": "Αφαίρεση από λίστα",
|
"Remove from list": "Αφαίρεση από λίστα",
|
||||||
"Remove tag": "Αφαίρεση ετικέτας",
|
"Remove tag": "Αφαίρεση ετικέτας",
|
||||||
"Remove user": "Αφαίρεση χρήστη",
|
"Remove user": "Αφαίρεση χρήστη",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "ΑΠΟΘΗΚΕΥΣΗ",
|
"SAVE": "ΑΠΟΘΗΚΕΥΣΗ",
|
||||||
"SEARCH": "ΑΝΑΖΗΤΗΣΗ",
|
"SEARCH": "ΑΝΑΖΗΤΗΣΗ",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Επιλογή όλων των αρχείων",
|
"Select all media": "Επιλογή όλων των αρχείων",
|
||||||
"Select publish state:": "Επιλέξτε κατάσταση δημοσίευσης:",
|
"Select publish state:": "Επιλέξτε κατάσταση δημοσίευσης:",
|
||||||
"Selected": "Επιλεγμένα",
|
"Selected": "Επιλεγμένα",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Κοινοποιήθηκαν από εμένα",
|
"Shared by me": "Κοινοποιήθηκαν από εμένα",
|
||||||
"Shared with me": "Κοινοποιήθηκαν σε εμένα",
|
"Shared with me": "Κοινοποιήθηκαν σε εμένα",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Σύνδεση",
|
"Sign in": "Σύνδεση",
|
||||||
"Sign out": "Αποσύνδεση",
|
"Sign out": "Αποσύνδεση",
|
||||||
"Sort By": "Ταξινόμηση",
|
"Sort By": "Ταξινόμηση",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Τα σχόλια ενεργοποιήθηκαν με επιτυχία",
|
"Successfully Enabled comments": "Τα σχόλια ενεργοποιήθηκαν με επιτυχία",
|
||||||
"Successfully changed owner": "Ο ιδιοκτήτης άλλαξε με επιτυχία",
|
"Successfully changed owner": "Ο ιδιοκτήτης άλλαξε με επιτυχία",
|
||||||
"Successfully deleted": "Διαγράφηκε με επιτυχία",
|
"Successfully deleted": "Διαγράφηκε με επιτυχία",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Ενημερώθηκε με επιτυχία",
|
"Successfully updated": "Ενημερώθηκε με επιτυχία",
|
||||||
"Successfully updated categories": "Οι κατηγορίες ενημερώθηκαν με επιτυχία",
|
"Successfully updated categories": "Οι κατηγορίες ενημερώθηκαν με επιτυχία",
|
||||||
"Successfully updated playlist membership": "Η συμμετοχή στη λίστα ενημερώθηκε με επιτυχία",
|
"Successfully updated playlist membership": "Η συμμετοχή στη λίστα ενημερώθηκε με επιτυχία",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Ανέβασμα αρχείων",
|
"Upload media": "Ανέβασμα αρχείων",
|
||||||
"Uploads": "Ανεβάσματα",
|
"Uploads": "Ανεβάσματα",
|
||||||
"Users": "Χρήστες",
|
"Users": "Χρήστες",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "ΔΕΣ ΤΑ ΟΛΑ",
|
"VIEW ALL": "ΔΕΣ ΤΑ ΟΛΑ",
|
||||||
"Video": "Βίντεο",
|
"Video": "Βίντεο",
|
||||||
"View all": "Δες τα όλα",
|
"View all": "Δες τα όλα",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Καλώς ήρθατε",
|
"Welcome": "Καλώς ήρθατε",
|
||||||
"You are going to copy": "Πρόκειται να αντιγράψετε",
|
"You are going to copy": "Πρόκειται να αντιγράψετε",
|
||||||
"You are going to delete": "Πρόκειται να διαγράψετε",
|
"You are going to delete": "Πρόκειται να διαγράψετε",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Πρόκειται να απενεργοποιήσετε τα σχόλια για",
|
"You are going to disable comments to": "Πρόκειται να απενεργοποιήσετε τα σχόλια για",
|
||||||
"You are going to disable download for": "Πρόκειται να απενεργοποιήσετε τη λήψη για",
|
"You are going to disable download for": "Πρόκειται να απενεργοποιήσετε τη λήψη για",
|
||||||
"You are going to enable comments to": "Πρόκειται να ενεργοποιήσετε τα σχόλια για",
|
"You are going to enable comments to": "Πρόκειται να ενεργοποιήσετε τα σχόλια για",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE": "",
|
"DELETE": "",
|
||||||
"DELETE MEDIA": "",
|
"DELETE MEDIA": "",
|
||||||
"Delete media": "",
|
"Delete media": "",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "",
|
"Delete Media": "",
|
||||||
"Disable Comments": "",
|
"Disable Comments": "",
|
||||||
"Disable Download": "",
|
"Disable Download": "",
|
||||||
@@ -71,6 +72,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "",
|
"Failed to change owner. Please try again.": "",
|
||||||
"Failed to copy media.": "",
|
"Failed to copy media.": "",
|
||||||
"Failed to create playlist": "",
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "",
|
"Failed to delete media. Please try again.": "",
|
||||||
"Failed to disable comments.": "",
|
"Failed to disable comments.": "",
|
||||||
"Failed to disable download.": "",
|
"Failed to disable download.": "",
|
||||||
@@ -102,6 +104,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "",
|
"Filter existing users...": "",
|
||||||
"Filter playlists...": "",
|
"Filter playlists...": "",
|
||||||
"Filters": "",
|
"Filters": "",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "",
|
"Go": "",
|
||||||
"History": "",
|
"History": "",
|
||||||
"Home": "",
|
"Home": "",
|
||||||
@@ -117,6 +122,7 @@ translation_strings = {
|
|||||||
"Loading existing users...": "",
|
"Loading existing users...": "",
|
||||||
"Loading playlists...": "",
|
"Loading playlists...": "",
|
||||||
"Loading tags...": "",
|
"Loading tags...": "",
|
||||||
|
"Management": "",
|
||||||
"Manage": "",
|
"Manage": "",
|
||||||
"Manage comments": "",
|
"Manage comments": "",
|
||||||
"Manage media": "",
|
"Manage media": "",
|
||||||
@@ -143,6 +149,7 @@ translation_strings = {
|
|||||||
"No results for": "",
|
"No results for": "",
|
||||||
"No tags": "",
|
"No tags": "",
|
||||||
"No users to add": "",
|
"No users to add": "",
|
||||||
|
"Organization": "",
|
||||||
"or": "",
|
"or": "",
|
||||||
"Pdf": "",
|
"Pdf": "",
|
||||||
"PLAYLISTS": "",
|
"PLAYLISTS": "",
|
||||||
@@ -162,8 +169,13 @@ translation_strings = {
|
|||||||
"Published": "",
|
"Published": "",
|
||||||
"Published on": "",
|
"Published on": "",
|
||||||
"Recent uploads": "",
|
"Recent uploads": "",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Recommended": "",
|
"Recommended": "",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "",
|
"Record Screen": "",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "",
|
"Register": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"Remove category": "",
|
"Remove category": "",
|
||||||
@@ -171,6 +183,7 @@ translation_strings = {
|
|||||||
"Remove tag": "",
|
"Remove tag": "",
|
||||||
"Remove user": "",
|
"Remove user": "",
|
||||||
"results for": "",
|
"results for": "",
|
||||||
|
"Settings": "",
|
||||||
"SAVE": "",
|
"SAVE": "",
|
||||||
"SEARCH": "",
|
"SEARCH": "",
|
||||||
"Search": "",
|
"Search": "",
|
||||||
@@ -184,6 +197,12 @@ translation_strings = {
|
|||||||
"Selected": "",
|
"Selected": "",
|
||||||
"selected": "",
|
"selected": "",
|
||||||
"SHARE": "",
|
"SHARE": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
|
"Sharing": "",
|
||||||
|
"Share with": "",
|
||||||
"Shared by me": "",
|
"Shared by me": "",
|
||||||
"Shared with me": "",
|
"Shared with me": "",
|
||||||
"SHOW MORE": "",
|
"SHOW MORE": "",
|
||||||
@@ -201,6 +220,7 @@ translation_strings = {
|
|||||||
"Successfully changed owner": "",
|
"Successfully changed owner": "",
|
||||||
"Successfully Copied": "",
|
"Successfully Copied": "",
|
||||||
"Successfully deleted": "",
|
"Successfully deleted": "",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully Disabled comments": "",
|
"Successfully Disabled comments": "",
|
||||||
"Successfully Disabled Download": "",
|
"Successfully Disabled Download": "",
|
||||||
"Successfully Enabled comments": "",
|
"Successfully Enabled comments": "",
|
||||||
@@ -240,6 +260,9 @@ translation_strings = {
|
|||||||
"UPLOAD MEDIA": "",
|
"UPLOAD MEDIA": "",
|
||||||
"Upload media": "",
|
"Upload media": "",
|
||||||
"Uploads": "",
|
"Uploads": "",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"Users": "",
|
"Users": "",
|
||||||
"Video": "",
|
"Video": "",
|
||||||
"view": "",
|
"view": "",
|
||||||
@@ -252,6 +275,7 @@ translation_strings = {
|
|||||||
"yet": "",
|
"yet": "",
|
||||||
"You are going to copy": "",
|
"You are going to copy": "",
|
||||||
"You are going to delete": "",
|
"You are going to delete": "",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "",
|
"You are going to disable comments to": "",
|
||||||
"You are going to disable download for": "",
|
"You are going to disable download for": "",
|
||||||
"You are going to enable comments to": "",
|
"You are going to enable comments to": "",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "ELIMINAR MEDIOS",
|
"DELETE MEDIA": "ELIMINAR MEDIOS",
|
||||||
"DOWNLOAD": "DESCARGAR",
|
"DOWNLOAD": "DESCARGAR",
|
||||||
"DURATION": "DURACIÓN",
|
"DURATION": "DURACIÓN",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Eliminar Medio",
|
"Delete Media": "Eliminar Medio",
|
||||||
"Delete media": "Eliminar medios",
|
"Delete media": "Eliminar medios",
|
||||||
"Disable Comments": "Deshabilitar Comentarios",
|
"Disable Comments": "Deshabilitar Comentarios",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Error al cambiar propietario. Por favor, inténtelo de nuevo.",
|
"Failed to change owner. Please try again.": "Error al cambiar propietario. Por favor, inténtelo de nuevo.",
|
||||||
"Failed to copy media.": "Error al copiar medios.",
|
"Failed to copy media.": "Error al copiar medios.",
|
||||||
"Failed to create playlist": "Error al crear lista de reproducción",
|
"Failed to create playlist": "Error al crear lista de reproducción",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Error al eliminar medios. Por favor, inténtelo de nuevo.",
|
"Failed to delete media. Please try again.": "Error al eliminar medios. Por favor, inténtelo de nuevo.",
|
||||||
"Failed to disable comments.": "Error al deshabilitar comentarios.",
|
"Failed to disable comments.": "Error al deshabilitar comentarios.",
|
||||||
"Failed to disable download.": "Error al deshabilitar descarga.",
|
"Failed to disable download.": "Error al deshabilitar descarga.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filtrar usuarios existentes...",
|
"Filter existing users...": "Filtrar usuarios existentes...",
|
||||||
"Filter playlists...": "Filtrar listas de reproducción...",
|
"Filter playlists...": "Filtrar listas de reproducción...",
|
||||||
"Filters": "Filtros",
|
"Filters": "Filtros",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Ir",
|
"Go": "Ir",
|
||||||
"History": "Historial",
|
"History": "Historial",
|
||||||
"Home": "Inicio",
|
"Home": "Inicio",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Gestionar comentarios",
|
"Manage comments": "Gestionar comentarios",
|
||||||
"Manage media": "Gestionar medios",
|
"Manage media": "Gestionar medios",
|
||||||
"Manage users": "Gestionar usuarios",
|
"Manage users": "Gestionar usuarios",
|
||||||
|
"Management": "",
|
||||||
"Media": "Medios",
|
"Media": "Medios",
|
||||||
"Media I own": "Medios que poseo",
|
"Media I own": "Medios que poseo",
|
||||||
"Media was edited": "El medio fue editado",
|
"Media was edited": "El medio fue editado",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "No hay resultados para",
|
"No results for": "No hay resultados para",
|
||||||
"No tags": "Sin etiquetas",
|
"No tags": "Sin etiquetas",
|
||||||
"No users to add": "No hay usuarios para agregar",
|
"No users to add": "No hay usuarios para agregar",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "LISTAS DE REPRODUCCIÓN",
|
"PLAYLISTS": "LISTAS DE REPRODUCCIÓN",
|
||||||
"PUBLISH STATE": "ESTADO DE PUBLICACIÓN",
|
"PUBLISH STATE": "ESTADO DE PUBLICACIÓN",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Publicado en",
|
"Published on": "Publicado en",
|
||||||
"Recent uploads": "Subidas recientes",
|
"Recent uploads": "Subidas recientes",
|
||||||
"Recommended": "Recomendado",
|
"Recommended": "Recomendado",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Grabar pantalla",
|
"Record Screen": "Grabar pantalla",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registrarse",
|
"Register": "Registrarse",
|
||||||
"Remove category": "Eliminar categoría",
|
"Remove category": "Eliminar categoría",
|
||||||
"Remove from list": "Eliminar de la lista",
|
"Remove from list": "Eliminar de la lista",
|
||||||
"Remove tag": "Eliminar etiqueta",
|
"Remove tag": "Eliminar etiqueta",
|
||||||
"Remove user": "Eliminar usuario",
|
"Remove user": "Eliminar usuario",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "GUARDAR",
|
"SAVE": "GUARDAR",
|
||||||
"SEARCH": "BUSCAR",
|
"SEARCH": "BUSCAR",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Seleccionar todos los medios",
|
"Select all media": "Seleccionar todos los medios",
|
||||||
"Select publish state:": "Seleccionar estado de publicación:",
|
"Select publish state:": "Seleccionar estado de publicación:",
|
||||||
"Selected": "Seleccionado",
|
"Selected": "Seleccionado",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Compartido por mí",
|
"Shared by me": "Compartido por mí",
|
||||||
"Shared with me": "Compartido conmigo",
|
"Shared with me": "Compartido conmigo",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Iniciar sesión",
|
"Sign in": "Iniciar sesión",
|
||||||
"Sign out": "Cerrar sesión",
|
"Sign out": "Cerrar sesión",
|
||||||
"Sort By": "Ordenar por",
|
"Sort By": "Ordenar por",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Comentarios habilitados exitosamente",
|
"Successfully Enabled comments": "Comentarios habilitados exitosamente",
|
||||||
"Successfully changed owner": "Propietario cambiado exitosamente",
|
"Successfully changed owner": "Propietario cambiado exitosamente",
|
||||||
"Successfully deleted": "Eliminado exitosamente",
|
"Successfully deleted": "Eliminado exitosamente",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Actualizado exitosamente",
|
"Successfully updated": "Actualizado exitosamente",
|
||||||
"Successfully updated categories": "Categorías actualizadas exitosamente",
|
"Successfully updated categories": "Categorías actualizadas exitosamente",
|
||||||
"Successfully updated playlist membership": "Membresía de lista de reproducción actualizada exitosamente",
|
"Successfully updated playlist membership": "Membresía de lista de reproducción actualizada exitosamente",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Subir medios",
|
"Upload media": "Subir medios",
|
||||||
"Uploads": "Subidas",
|
"Uploads": "Subidas",
|
||||||
"Users": "Usuarios",
|
"Users": "Usuarios",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "VER TODO",
|
"VIEW ALL": "VER TODO",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Ver todo",
|
"View all": "Ver todo",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Bienvenido",
|
"Welcome": "Bienvenido",
|
||||||
"You are going to copy": "Vas a copiar",
|
"You are going to copy": "Vas a copiar",
|
||||||
"You are going to delete": "Vas a eliminar",
|
"You are going to delete": "Vas a eliminar",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Vas a deshabilitar comentarios de",
|
"You are going to disable comments to": "Vas a deshabilitar comentarios de",
|
||||||
"You are going to disable download for": "Vas a deshabilitar descarga de",
|
"You are going to disable download for": "Vas a deshabilitar descarga de",
|
||||||
"You are going to enable comments to": "Vas a habilitar comentarios de",
|
"You are going to enable comments to": "Vas a habilitar comentarios de",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "SUPPRIMER LE MÉDIA",
|
"DELETE MEDIA": "SUPPRIMER LE MÉDIA",
|
||||||
"DOWNLOAD": "TÉLÉCHARGER",
|
"DOWNLOAD": "TÉLÉCHARGER",
|
||||||
"DURATION": "DURÉE",
|
"DURATION": "DURÉE",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Supprimer le média",
|
"Delete Media": "Supprimer le média",
|
||||||
"Delete media": "Supprimer le média",
|
"Delete media": "Supprimer le média",
|
||||||
"Disable Comments": "Désactiver les commentaires",
|
"Disable Comments": "Désactiver les commentaires",
|
||||||
@@ -71,6 +72,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Échec du changement de propriétaire. Veuillez réessayer.",
|
"Failed to change owner. Please try again.": "Échec du changement de propriétaire. Veuillez réessayer.",
|
||||||
"Failed to copy media.": "Échec de la copie du média.",
|
"Failed to copy media.": "Échec de la copie du média.",
|
||||||
"Failed to create playlist": "Échec de la création de la playlist",
|
"Failed to create playlist": "Échec de la création de la playlist",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Échec de la suppression du média. Veuillez réessayer.",
|
"Failed to delete media. Please try again.": "Échec de la suppression du média. Veuillez réessayer.",
|
||||||
"Failed to disable comments.": "Échec de la désactivation des commentaires.",
|
"Failed to disable comments.": "Échec de la désactivation des commentaires.",
|
||||||
"Failed to disable download.": "Échec de la désactivation du téléchargement.",
|
"Failed to disable download.": "Échec de la désactivation du téléchargement.",
|
||||||
@@ -102,6 +104,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filtrer les utilisateurs existants...",
|
"Filter existing users...": "Filtrer les utilisateurs existants...",
|
||||||
"Filter playlists...": "Filtrer les playlists...",
|
"Filter playlists...": "Filtrer les playlists...",
|
||||||
"Filters": "Filtres",
|
"Filters": "Filtres",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Aller",
|
"Go": "Aller",
|
||||||
"History": "Historique",
|
"History": "Historique",
|
||||||
"Home": "Accueil",
|
"Home": "Accueil",
|
||||||
@@ -122,6 +127,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Gérer les commentaires",
|
"Manage comments": "Gérer les commentaires",
|
||||||
"Manage media": "Gérer les médias",
|
"Manage media": "Gérer les médias",
|
||||||
"Manage users": "Gérer les utilisateurs",
|
"Manage users": "Gérer les utilisateurs",
|
||||||
|
"Management": "",
|
||||||
"Media": "Média",
|
"Media": "Média",
|
||||||
"Media I own": "Médias que je possède",
|
"Media I own": "Médias que je possède",
|
||||||
"Media was edited": "Le média a été modifié",
|
"Media was edited": "Le média a été modifié",
|
||||||
@@ -138,6 +144,7 @@ translation_strings = {
|
|||||||
"No results for": "Aucun résultat pour",
|
"No results for": "Aucun résultat pour",
|
||||||
"No tags": "Aucun tag",
|
"No tags": "Aucun tag",
|
||||||
"No users to add": "Aucun utilisateur à ajouter",
|
"No users to add": "Aucun utilisateur à ajouter",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "PLAYLISTS",
|
"PLAYLISTS": "PLAYLISTS",
|
||||||
"PUBLISH STATE": "ÉTAT DE PUBLICATION",
|
"PUBLISH STATE": "ÉTAT DE PUBLICATION",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -157,12 +164,17 @@ translation_strings = {
|
|||||||
"Published on": "Publié le",
|
"Published on": "Publié le",
|
||||||
"Recent uploads": "Téléchargements récents",
|
"Recent uploads": "Téléchargements récents",
|
||||||
"Recommended": "Recommandé",
|
"Recommended": "Recommandé",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Enregistrer l'écran",
|
"Record Screen": "Enregistrer l'écran",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "S'inscrire",
|
"Register": "S'inscrire",
|
||||||
"Remove category": "Supprimer la catégorie",
|
"Remove category": "Supprimer la catégorie",
|
||||||
"Remove from list": "Supprimer de la liste",
|
"Remove from list": "Supprimer de la liste",
|
||||||
"Remove tag": "Supprimer le tag",
|
"Remove tag": "Supprimer le tag",
|
||||||
"Remove user": "Supprimer l'utilisateur",
|
"Remove user": "Supprimer l'utilisateur",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "ENREGISTRER",
|
"SAVE": "ENREGISTRER",
|
||||||
"SEARCH": "RECHERCHER",
|
"SEARCH": "RECHERCHER",
|
||||||
@@ -179,8 +191,15 @@ translation_strings = {
|
|||||||
"Select all media": "Sélectionner tous les médias",
|
"Select all media": "Sélectionner tous les médias",
|
||||||
"Select publish state:": "Sélectionner l'état de publication:",
|
"Select publish state:": "Sélectionner l'état de publication:",
|
||||||
"Selected": "Sélectionné",
|
"Selected": "Sélectionné",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Partagé par moi",
|
"Shared by me": "Partagé par moi",
|
||||||
"Shared with me": "Partagé avec moi",
|
"Shared with me": "Partagé avec moi",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Se connecter",
|
"Sign in": "Se connecter",
|
||||||
"Sign out": "Se déconnecter",
|
"Sign out": "Se déconnecter",
|
||||||
"Sort By": "Trier par",
|
"Sort By": "Trier par",
|
||||||
@@ -197,6 +216,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Commentaires activés avec succès",
|
"Successfully Enabled comments": "Commentaires activés avec succès",
|
||||||
"Successfully changed owner": "Propriétaire changé avec succès",
|
"Successfully changed owner": "Propriétaire changé avec succès",
|
||||||
"Successfully deleted": "Supprimé avec succès",
|
"Successfully deleted": "Supprimé avec succès",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Mis à jour avec succès",
|
"Successfully updated": "Mis à jour avec succès",
|
||||||
"Successfully updated categories": "Catégories mises à jour avec succès",
|
"Successfully updated categories": "Catégories mises à jour avec succès",
|
||||||
"Successfully updated playlist membership": "Adhésion à la playlist mise à jour avec succès",
|
"Successfully updated playlist membership": "Adhésion à la playlist mise à jour avec succès",
|
||||||
@@ -233,6 +253,9 @@ translation_strings = {
|
|||||||
"Upload media": "Télécharger des médias",
|
"Upload media": "Télécharger des médias",
|
||||||
"Uploads": "Téléchargements",
|
"Uploads": "Téléchargements",
|
||||||
"Users": "Utilisateurs",
|
"Users": "Utilisateurs",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "VOIR TOUT",
|
"VIEW ALL": "VOIR TOUT",
|
||||||
"Video": "Vidéo",
|
"Video": "Vidéo",
|
||||||
"View all": "Voir tout",
|
"View all": "Voir tout",
|
||||||
@@ -241,6 +264,7 @@ translation_strings = {
|
|||||||
"Welcome": "Bienvenue",
|
"Welcome": "Bienvenue",
|
||||||
"You are going to copy": "Vous allez copier",
|
"You are going to copy": "Vous allez copier",
|
||||||
"You are going to delete": "Vous allez supprimer",
|
"You are going to delete": "Vous allez supprimer",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Vous allez désactiver les commentaires de",
|
"You are going to disable comments to": "Vous allez désactiver les commentaires de",
|
||||||
"You are going to disable download for": "Vous allez désactiver le téléchargement de",
|
"You are going to disable download for": "Vous allez désactiver le téléchargement de",
|
||||||
"You are going to enable comments to": "Vous allez activer les commentaires de",
|
"You are going to enable comments to": "Vous allez activer les commentaires de",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "מחק מדיה",
|
"DELETE MEDIA": "מחק מדיה",
|
||||||
"DOWNLOAD": "הורד",
|
"DOWNLOAD": "הורד",
|
||||||
"DURATION": "משך",
|
"DURATION": "משך",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "",
|
"Delete Media": "",
|
||||||
"Delete media": "מחק מדיה",
|
"Delete media": "מחק מדיה",
|
||||||
"Disable Comments": "",
|
"Disable Comments": "",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "",
|
"Failed to change owner. Please try again.": "",
|
||||||
"Failed to copy media.": "העתקת המדיה נכשלה.",
|
"Failed to copy media.": "העתקת המדיה נכשלה.",
|
||||||
"Failed to create playlist": "",
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "מחיקת המדיה נכשלה. אנא נסה שוב.",
|
"Failed to delete media. Please try again.": "מחיקת המדיה נכשלה. אנא נסה שוב.",
|
||||||
"Failed to disable comments.": "ביטול התגובות נכשל.",
|
"Failed to disable comments.": "ביטול התגובות נכשל.",
|
||||||
"Failed to disable download.": "ביטול ההורדה נכשל.",
|
"Failed to disable download.": "ביטול ההורדה נכשל.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "",
|
"Filter existing users...": "",
|
||||||
"Filter playlists...": "",
|
"Filter playlists...": "",
|
||||||
"Filters": "מסננים",
|
"Filters": "מסננים",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "בצע",
|
"Go": "בצע",
|
||||||
"History": "היסטוריה",
|
"History": "היסטוריה",
|
||||||
"Home": "דף הבית",
|
"Home": "דף הבית",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "ניהול תגובות",
|
"Manage comments": "ניהול תגובות",
|
||||||
"Manage media": "ניהול מדיה",
|
"Manage media": "ניהול מדיה",
|
||||||
"Manage users": "ניהול משתמשים",
|
"Manage users": "ניהול משתמשים",
|
||||||
|
"Management": "",
|
||||||
"Media": "מדיה",
|
"Media": "מדיה",
|
||||||
"Media I own": "",
|
"Media I own": "",
|
||||||
"Media was edited": "המדיה נערכה",
|
"Media was edited": "המדיה נערכה",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "אין תוצאות עבור",
|
"No results for": "אין תוצאות עבור",
|
||||||
"No tags": "",
|
"No tags": "",
|
||||||
"No users to add": "",
|
"No users to add": "",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "פלייליסטים",
|
"PLAYLISTS": "פלייליסטים",
|
||||||
"PUBLISH STATE": "מצב פרסום",
|
"PUBLISH STATE": "מצב פרסום",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "פורסם בתאריך",
|
"Published on": "פורסם בתאריך",
|
||||||
"Recent uploads": "העלאות אחרונות",
|
"Recent uploads": "העלאות אחרונות",
|
||||||
"Recommended": "מומלץ",
|
"Recommended": "מומלץ",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "הקלטת מסך",
|
"Record Screen": "הקלטת מסך",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "הרשמה",
|
"Register": "הרשמה",
|
||||||
"Remove category": "",
|
"Remove category": "",
|
||||||
"Remove from list": "",
|
"Remove from list": "",
|
||||||
"Remove tag": "",
|
"Remove tag": "",
|
||||||
"Remove user": "",
|
"Remove user": "",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "שמור",
|
"SAVE": "שמור",
|
||||||
"SEARCH": "חפש",
|
"SEARCH": "חפש",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "",
|
"Select all media": "",
|
||||||
"Select publish state:": "",
|
"Select publish state:": "",
|
||||||
"Selected": "",
|
"Selected": "",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "שותף על ידי",
|
"Shared by me": "שותף על ידי",
|
||||||
"Shared with me": "שותף איתי",
|
"Shared with me": "שותף איתי",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "התחבר",
|
"Sign in": "התחבר",
|
||||||
"Sign out": "התנתק",
|
"Sign out": "התנתק",
|
||||||
"Sort By": "מיין לפי",
|
"Sort By": "מיין לפי",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "התגובות הופעלו בהצלחה",
|
"Successfully Enabled comments": "התגובות הופעלו בהצלחה",
|
||||||
"Successfully changed owner": "",
|
"Successfully changed owner": "",
|
||||||
"Successfully deleted": "נמחק בהצלחה",
|
"Successfully deleted": "נמחק בהצלחה",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "",
|
"Successfully updated": "",
|
||||||
"Successfully updated categories": "",
|
"Successfully updated categories": "",
|
||||||
"Successfully updated playlist membership": "",
|
"Successfully updated playlist membership": "",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "העלה מדיה",
|
"Upload media": "העלה מדיה",
|
||||||
"Uploads": "העלאות",
|
"Uploads": "העלאות",
|
||||||
"Users": "",
|
"Users": "",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "הצג הכל",
|
"VIEW ALL": "הצג הכל",
|
||||||
"Video": "וידאו",
|
"Video": "וידאו",
|
||||||
"View all": "הצג הכל",
|
"View all": "הצג הכל",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "ברוך הבא",
|
"Welcome": "ברוך הבא",
|
||||||
"You are going to copy": "אתה עומד להעתיק",
|
"You are going to copy": "אתה עומד להעתיק",
|
||||||
"You are going to delete": "אתה עומד למחוק",
|
"You are going to delete": "אתה עומד למחוק",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "אתה עומד לבטל תגובות ל",
|
"You are going to disable comments to": "אתה עומד לבטל תגובות ל",
|
||||||
"You are going to disable download for": "אתה עומד לבטל הורדה עבור",
|
"You are going to disable download for": "אתה עומד לבטל הורדה עבור",
|
||||||
"You are going to enable comments to": "אתה עומד להפעיל תגובות ל",
|
"You are going to enable comments to": "אתה עומד להפעיל תגובות ל",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "मीडिया हटाएं",
|
"DELETE MEDIA": "मीडिया हटाएं",
|
||||||
"DOWNLOAD": "डाउनलोड करें",
|
"DOWNLOAD": "डाउनलोड करें",
|
||||||
"DURATION": "अवधि",
|
"DURATION": "अवधि",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "मीडिया हटाएं",
|
"Delete Media": "मीडिया हटाएं",
|
||||||
"Delete media": "मीडिया हटाएं",
|
"Delete media": "मीडिया हटाएं",
|
||||||
"Disable Comments": "टिप्पणियां अक्षम करें",
|
"Disable Comments": "टिप्पणियां अक्षम करें",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "स्वामी बदलने में विफल। कृपया पुनः प्रयास करें।",
|
"Failed to change owner. Please try again.": "स्वामी बदलने में विफल। कृपया पुनः प्रयास करें।",
|
||||||
"Failed to copy media.": "मीडिया कॉपी करने में विफल।",
|
"Failed to copy media.": "मीडिया कॉपी करने में विफल।",
|
||||||
"Failed to create playlist": "प्लेलिस्ट बनाने में विफल",
|
"Failed to create playlist": "प्लेलिस्ट बनाने में विफल",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "मीडिया हटाने में विफल। कृपया पुनः प्रयास करें।",
|
"Failed to delete media. Please try again.": "मीडिया हटाने में विफल। कृपया पुनः प्रयास करें।",
|
||||||
"Failed to disable comments.": "टिप्पणियों को अक्षम करने में विफल।",
|
"Failed to disable comments.": "टिप्पणियों को अक्षम करने में विफल।",
|
||||||
"Failed to disable download.": "डाउनलोड अक्षम करने में विफल।",
|
"Failed to disable download.": "डाउनलोड अक्षम करने में विफल।",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "मौजूदा उपयोगकर्ताओं को फ़िल्टर करें...",
|
"Filter existing users...": "मौजूदा उपयोगकर्ताओं को फ़िल्टर करें...",
|
||||||
"Filter playlists...": "प्लेलिस्ट फ़िल्टर करें...",
|
"Filter playlists...": "प्लेलिस्ट फ़िल्टर करें...",
|
||||||
"Filters": "फ़िल्टर",
|
"Filters": "फ़िल्टर",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "जाएं",
|
"Go": "जाएं",
|
||||||
"History": "इतिहास",
|
"History": "इतिहास",
|
||||||
"Home": "मुख्य पृष्ठ",
|
"Home": "मुख्य पृष्ठ",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "टिप्पणियाँ प्रबंधित करें",
|
"Manage comments": "टिप्पणियाँ प्रबंधित करें",
|
||||||
"Manage media": "मीडिया प्रबंधित करें",
|
"Manage media": "मीडिया प्रबंधित करें",
|
||||||
"Manage users": "उपयोगकर्ताओं को प्रबंधित करें",
|
"Manage users": "उपयोगकर्ताओं को प्रबंधित करें",
|
||||||
|
"Management": "",
|
||||||
"Media": "मीडिया",
|
"Media": "मीडिया",
|
||||||
"Media I own": "मेरे स्वामित्व वाली मीडिया",
|
"Media I own": "मेरे स्वामित्व वाली मीडिया",
|
||||||
"Media was edited": "मीडिया संपादित किया गया था",
|
"Media was edited": "मीडिया संपादित किया गया था",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "के लिए कोई परिणाम नहीं",
|
"No results for": "के लिए कोई परिणाम नहीं",
|
||||||
"No tags": "कोई टैग नहीं",
|
"No tags": "कोई टैग नहीं",
|
||||||
"No users to add": "जोड़ने के लिए कोई उपयोगकर्ता नहीं",
|
"No users to add": "जोड़ने के लिए कोई उपयोगकर्ता नहीं",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "प्लेलिस्ट",
|
"PLAYLISTS": "प्लेलिस्ट",
|
||||||
"PUBLISH STATE": "प्रकाशन स्थिति",
|
"PUBLISH STATE": "प्रकाशन स्थिति",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "पर प्रकाशित",
|
"Published on": "पर प्रकाशित",
|
||||||
"Recent uploads": "हाल के अपलोड",
|
"Recent uploads": "हाल के अपलोड",
|
||||||
"Recommended": "अनुशंसित",
|
"Recommended": "अनुशंसित",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "स्क्रीन रिकॉर्ड करें",
|
"Record Screen": "स्क्रीन रिकॉर्ड करें",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "पंजीकरण करें",
|
"Register": "पंजीकरण करें",
|
||||||
"Remove category": "श्रेणी हटाएं",
|
"Remove category": "श्रेणी हटाएं",
|
||||||
"Remove from list": "सूची से हटाएं",
|
"Remove from list": "सूची से हटाएं",
|
||||||
"Remove tag": "टैग हटाएं",
|
"Remove tag": "टैग हटाएं",
|
||||||
"Remove user": "उपयोगकर्ता हटाएं",
|
"Remove user": "उपयोगकर्ता हटाएं",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "सहेजें",
|
"SAVE": "सहेजें",
|
||||||
"SEARCH": "खोजें",
|
"SEARCH": "खोजें",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "सभी मीडिया चुनें",
|
"Select all media": "सभी मीडिया चुनें",
|
||||||
"Select publish state:": "प्रकाशन स्थिति चुनें:",
|
"Select publish state:": "प्रकाशन स्थिति चुनें:",
|
||||||
"Selected": "चयनित",
|
"Selected": "चयनित",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "मेरे द्वारा साझा किया गया",
|
"Shared by me": "मेरे द्वारा साझा किया गया",
|
||||||
"Shared with me": "मेरे साथ साझा किया गया",
|
"Shared with me": "मेरे साथ साझा किया गया",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "साइन इन करें",
|
"Sign in": "साइन इन करें",
|
||||||
"Sign out": "साइन आउट करें",
|
"Sign out": "साइन आउट करें",
|
||||||
"Sort By": "इसके अनुसार क्रमबद्ध करें",
|
"Sort By": "इसके अनुसार क्रमबद्ध करें",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "टिप्पणियां सफलतापूर्वक सक्षम की गईं",
|
"Successfully Enabled comments": "टिप्पणियां सफलतापूर्वक सक्षम की गईं",
|
||||||
"Successfully changed owner": "स्वामी सफलतापूर्वक बदला गया",
|
"Successfully changed owner": "स्वामी सफलतापूर्वक बदला गया",
|
||||||
"Successfully deleted": "सफलतापूर्वक हटाया गया",
|
"Successfully deleted": "सफलतापूर्वक हटाया गया",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "सफलतापूर्वक अपडेट किया गया",
|
"Successfully updated": "सफलतापूर्वक अपडेट किया गया",
|
||||||
"Successfully updated categories": "श्रेणियां सफलतापूर्वक अपडेट की गईं",
|
"Successfully updated categories": "श्रेणियां सफलतापूर्वक अपडेट की गईं",
|
||||||
"Successfully updated playlist membership": "प्लेलिस्ट सदस्यता सफलतापूर्वक अपडेट की गई",
|
"Successfully updated playlist membership": "प्लेलिस्ट सदस्यता सफलतापूर्वक अपडेट की गई",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "मीडिया अपलोड करें",
|
"Upload media": "मीडिया अपलोड करें",
|
||||||
"Uploads": "अपलोड",
|
"Uploads": "अपलोड",
|
||||||
"Users": "उपयोगकर्ता",
|
"Users": "उपयोगकर्ता",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "सभी देखें",
|
"VIEW ALL": "सभी देखें",
|
||||||
"Video": "वीडियो",
|
"Video": "वीडियो",
|
||||||
"View all": "सभी देखें",
|
"View all": "सभी देखें",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "स्वागत है",
|
"Welcome": "स्वागत है",
|
||||||
"You are going to copy": "आप कॉपी करने जा रहे हैं",
|
"You are going to copy": "आप कॉपी करने जा रहे हैं",
|
||||||
"You are going to delete": "आप हटाने जा रहे हैं",
|
"You are going to delete": "आप हटाने जा रहे हैं",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "आप टिप्पणियों को अक्षम करने जा रहे हैं",
|
"You are going to disable comments to": "आप टिप्पणियों को अक्षम करने जा रहे हैं",
|
||||||
"You are going to disable download for": "आप डाउनलोड को अक्षम करने जा रहे हैं",
|
"You are going to disable download for": "आप डाउनलोड को अक्षम करने जा रहे हैं",
|
||||||
"You are going to enable comments to": "आप टिप्पणियों को सक्षम करने जा रहे हैं",
|
"You are going to enable comments to": "आप टिप्पणियों को सक्षम करने जा रहे हैं",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "HAPUS MEDIA",
|
"DELETE MEDIA": "HAPUS MEDIA",
|
||||||
"DOWNLOAD": "UNDUH",
|
"DOWNLOAD": "UNDUH",
|
||||||
"DURATION": "DURASI",
|
"DURATION": "DURASI",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Hapus Media",
|
"Delete Media": "Hapus Media",
|
||||||
"Delete media": "Hapus media",
|
"Delete media": "Hapus media",
|
||||||
"Disable Comments": "Nonaktifkan Komentar",
|
"Disable Comments": "Nonaktifkan Komentar",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Gagal mengganti pemilik. Silakan coba lagi.",
|
"Failed to change owner. Please try again.": "Gagal mengganti pemilik. Silakan coba lagi.",
|
||||||
"Failed to copy media.": "Gagal menyalin media.",
|
"Failed to copy media.": "Gagal menyalin media.",
|
||||||
"Failed to create playlist": "Gagal membuat daftar putar",
|
"Failed to create playlist": "Gagal membuat daftar putar",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Gagal menghapus media. Silakan coba lagi.",
|
"Failed to delete media. Please try again.": "Gagal menghapus media. Silakan coba lagi.",
|
||||||
"Failed to disable comments.": "Gagal menonaktifkan komentar.",
|
"Failed to disable comments.": "Gagal menonaktifkan komentar.",
|
||||||
"Failed to disable download.": "Gagal menonaktifkan unduhan.",
|
"Failed to disable download.": "Gagal menonaktifkan unduhan.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filter pengguna yang ada...",
|
"Filter existing users...": "Filter pengguna yang ada...",
|
||||||
"Filter playlists...": "Filter daftar putar...",
|
"Filter playlists...": "Filter daftar putar...",
|
||||||
"Filters": "Filter",
|
"Filters": "Filter",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Pergi",
|
"Go": "Pergi",
|
||||||
"History": "Riwayat",
|
"History": "Riwayat",
|
||||||
"Home": "Beranda",
|
"Home": "Beranda",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Kelola komentar",
|
"Manage comments": "Kelola komentar",
|
||||||
"Manage media": "Kelola media",
|
"Manage media": "Kelola media",
|
||||||
"Manage users": "Kelola pengguna",
|
"Manage users": "Kelola pengguna",
|
||||||
|
"Management": "",
|
||||||
"Media": "Media",
|
"Media": "Media",
|
||||||
"Media I own": "Media yang saya miliki",
|
"Media I own": "Media yang saya miliki",
|
||||||
"Media was edited": "Media telah diedit",
|
"Media was edited": "Media telah diedit",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Tidak ada hasil untuk",
|
"No results for": "Tidak ada hasil untuk",
|
||||||
"No tags": "Tidak ada tag",
|
"No tags": "Tidak ada tag",
|
||||||
"No users to add": "Tidak ada pengguna untuk ditambahkan",
|
"No users to add": "Tidak ada pengguna untuk ditambahkan",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "DAFTAR PUTAR",
|
"PLAYLISTS": "DAFTAR PUTAR",
|
||||||
"PUBLISH STATE": "STATUS PUBLIKASI",
|
"PUBLISH STATE": "STATUS PUBLIKASI",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Diterbitkan pada",
|
"Published on": "Diterbitkan pada",
|
||||||
"Recent uploads": "Unggahan terbaru",
|
"Recent uploads": "Unggahan terbaru",
|
||||||
"Recommended": "Direkomendasikan",
|
"Recommended": "Direkomendasikan",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Rekam Layar",
|
"Record Screen": "Rekam Layar",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Daftar",
|
"Register": "Daftar",
|
||||||
"Remove category": "Hapus kategori",
|
"Remove category": "Hapus kategori",
|
||||||
"Remove from list": "Hapus dari daftar",
|
"Remove from list": "Hapus dari daftar",
|
||||||
"Remove tag": "Hapus tag",
|
"Remove tag": "Hapus tag",
|
||||||
"Remove user": "Hapus pengguna",
|
"Remove user": "Hapus pengguna",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "SIMPAN",
|
"SAVE": "SIMPAN",
|
||||||
"SEARCH": "CARI",
|
"SEARCH": "CARI",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Pilih semua media",
|
"Select all media": "Pilih semua media",
|
||||||
"Select publish state:": "Pilih status publikasi:",
|
"Select publish state:": "Pilih status publikasi:",
|
||||||
"Selected": "Dipilih",
|
"Selected": "Dipilih",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Dibagikan oleh saya",
|
"Shared by me": "Dibagikan oleh saya",
|
||||||
"Shared with me": "Dibagikan dengan saya",
|
"Shared with me": "Dibagikan dengan saya",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Masuk",
|
"Sign in": "Masuk",
|
||||||
"Sign out": "Keluar",
|
"Sign out": "Keluar",
|
||||||
"Sort By": "Urutkan Berdasarkan",
|
"Sort By": "Urutkan Berdasarkan",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Komentar berhasil diaktifkan",
|
"Successfully Enabled comments": "Komentar berhasil diaktifkan",
|
||||||
"Successfully changed owner": "Berhasil mengganti pemilik",
|
"Successfully changed owner": "Berhasil mengganti pemilik",
|
||||||
"Successfully deleted": "Berhasil dihapus",
|
"Successfully deleted": "Berhasil dihapus",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Berhasil diperbarui",
|
"Successfully updated": "Berhasil diperbarui",
|
||||||
"Successfully updated categories": "Kategori berhasil diperbarui",
|
"Successfully updated categories": "Kategori berhasil diperbarui",
|
||||||
"Successfully updated playlist membership": "Keanggotaan daftar putar berhasil diperbarui",
|
"Successfully updated playlist membership": "Keanggotaan daftar putar berhasil diperbarui",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Unggah media",
|
"Upload media": "Unggah media",
|
||||||
"Uploads": "Unggahan",
|
"Uploads": "Unggahan",
|
||||||
"Users": "Pengguna",
|
"Users": "Pengguna",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "LIHAT SEMUA",
|
"VIEW ALL": "LIHAT SEMUA",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Lihat semua",
|
"View all": "Lihat semua",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Selamat datang",
|
"Welcome": "Selamat datang",
|
||||||
"You are going to copy": "Anda akan menyalin",
|
"You are going to copy": "Anda akan menyalin",
|
||||||
"You are going to delete": "Anda akan menghapus",
|
"You are going to delete": "Anda akan menghapus",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Anda akan menonaktifkan komentar untuk",
|
"You are going to disable comments to": "Anda akan menonaktifkan komentar untuk",
|
||||||
"You are going to disable download for": "Anda akan menonaktifkan unduhan untuk",
|
"You are going to disable download for": "Anda akan menonaktifkan unduhan untuk",
|
||||||
"You are going to enable comments to": "Anda akan mengaktifkan komentar untuk",
|
"You are going to enable comments to": "Anda akan mengaktifkan komentar untuk",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "ELIMINA MEDIA",
|
"DELETE MEDIA": "ELIMINA MEDIA",
|
||||||
"DOWNLOAD": "SCARICA",
|
"DOWNLOAD": "SCARICA",
|
||||||
"DURATION": "DURATA",
|
"DURATION": "DURATA",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Elimina Media",
|
"Delete Media": "Elimina Media",
|
||||||
"Delete media": "Elimina media",
|
"Delete media": "Elimina media",
|
||||||
"Disable Comments": "Disabilita Commenti",
|
"Disable Comments": "Disabilita Commenti",
|
||||||
@@ -71,6 +72,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Impossibile cambiare proprietario. Riprova.",
|
"Failed to change owner. Please try again.": "Impossibile cambiare proprietario. Riprova.",
|
||||||
"Failed to copy media.": "Impossibile copiare il media.",
|
"Failed to copy media.": "Impossibile copiare il media.",
|
||||||
"Failed to create playlist": "Impossibile creare playlist",
|
"Failed to create playlist": "Impossibile creare playlist",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Impossibile eliminare il media. Riprova.",
|
"Failed to delete media. Please try again.": "Impossibile eliminare il media. Riprova.",
|
||||||
"Failed to disable comments.": "Impossibile disabilitare i commenti.",
|
"Failed to disable comments.": "Impossibile disabilitare i commenti.",
|
||||||
"Failed to disable download.": "Impossibile disabilitare il download.",
|
"Failed to disable download.": "Impossibile disabilitare il download.",
|
||||||
@@ -102,6 +104,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filtra utenti esistenti...",
|
"Filter existing users...": "Filtra utenti esistenti...",
|
||||||
"Filter playlists...": "Filtra playlist...",
|
"Filter playlists...": "Filtra playlist...",
|
||||||
"Filters": "Filtri",
|
"Filters": "Filtri",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Vai",
|
"Go": "Vai",
|
||||||
"History": "Cronologia",
|
"History": "Cronologia",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
@@ -122,6 +127,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Gestisci i commenti",
|
"Manage comments": "Gestisci i commenti",
|
||||||
"Manage media": "Gestisci i media",
|
"Manage media": "Gestisci i media",
|
||||||
"Manage users": "Gestisci gli utenti",
|
"Manage users": "Gestisci gli utenti",
|
||||||
|
"Management": "",
|
||||||
"Media": "Media",
|
"Media": "Media",
|
||||||
"Media I own": "Media di mia proprietà",
|
"Media I own": "Media di mia proprietà",
|
||||||
"Media was edited": "Il media è stato modificato",
|
"Media was edited": "Il media è stato modificato",
|
||||||
@@ -138,6 +144,7 @@ translation_strings = {
|
|||||||
"No results for": "Nessun risultato per",
|
"No results for": "Nessun risultato per",
|
||||||
"No tags": "Nessun tag",
|
"No tags": "Nessun tag",
|
||||||
"No users to add": "Nessun utente da aggiungere",
|
"No users to add": "Nessun utente da aggiungere",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "PLAYLIST",
|
"PLAYLISTS": "PLAYLIST",
|
||||||
"PUBLISH STATE": "STATO DI PUBBLICAZIONE",
|
"PUBLISH STATE": "STATO DI PUBBLICAZIONE",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -157,12 +164,17 @@ translation_strings = {
|
|||||||
"Published on": "Pubblicato il",
|
"Published on": "Pubblicato il",
|
||||||
"Recent uploads": "Caricamenti recenti",
|
"Recent uploads": "Caricamenti recenti",
|
||||||
"Recommended": "Raccomandati",
|
"Recommended": "Raccomandati",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Registra schermo",
|
"Record Screen": "Registra schermo",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registrati",
|
"Register": "Registrati",
|
||||||
"Remove category": "Rimuovi categoria",
|
"Remove category": "Rimuovi categoria",
|
||||||
"Remove from list": "Rimuovi dalla lista",
|
"Remove from list": "Rimuovi dalla lista",
|
||||||
"Remove tag": "Rimuovi tag",
|
"Remove tag": "Rimuovi tag",
|
||||||
"Remove user": "Rimuovi utente",
|
"Remove user": "Rimuovi utente",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "SALVA",
|
"SAVE": "SALVA",
|
||||||
"SEARCH": "CERCA",
|
"SEARCH": "CERCA",
|
||||||
@@ -179,8 +191,15 @@ translation_strings = {
|
|||||||
"Select all media": "Seleziona tutti i media",
|
"Select all media": "Seleziona tutti i media",
|
||||||
"Select publish state:": "Seleziona stato di pubblicazione:",
|
"Select publish state:": "Seleziona stato di pubblicazione:",
|
||||||
"Selected": "Selezionato",
|
"Selected": "Selezionato",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Condiviso da me",
|
"Shared by me": "Condiviso da me",
|
||||||
"Shared with me": "Condiviso con me",
|
"Shared with me": "Condiviso con me",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Login",
|
"Sign in": "Login",
|
||||||
"Sign out": "Logout",
|
"Sign out": "Logout",
|
||||||
"Sort By": "Ordina per",
|
"Sort By": "Ordina per",
|
||||||
@@ -197,6 +216,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Commenti abilitati con successo",
|
"Successfully Enabled comments": "Commenti abilitati con successo",
|
||||||
"Successfully changed owner": "Proprietario cambiato con successo",
|
"Successfully changed owner": "Proprietario cambiato con successo",
|
||||||
"Successfully deleted": "Eliminato con successo",
|
"Successfully deleted": "Eliminato con successo",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Aggiornato con successo",
|
"Successfully updated": "Aggiornato con successo",
|
||||||
"Successfully updated categories": "Categorie aggiornate con successo",
|
"Successfully updated categories": "Categorie aggiornate con successo",
|
||||||
"Successfully updated playlist membership": "Appartenenza alla playlist aggiornata con successo",
|
"Successfully updated playlist membership": "Appartenenza alla playlist aggiornata con successo",
|
||||||
@@ -233,6 +253,9 @@ translation_strings = {
|
|||||||
"Upload media": "Carica i media",
|
"Upload media": "Carica i media",
|
||||||
"Uploads": "Caricamenti",
|
"Uploads": "Caricamenti",
|
||||||
"Users": "Utenti",
|
"Users": "Utenti",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "MOSTRA TUTTI",
|
"VIEW ALL": "MOSTRA TUTTI",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Mostra tutti",
|
"View all": "Mostra tutti",
|
||||||
@@ -241,6 +264,7 @@ translation_strings = {
|
|||||||
"Welcome": "Benvenuto",
|
"Welcome": "Benvenuto",
|
||||||
"You are going to copy": "Stai per copiare",
|
"You are going to copy": "Stai per copiare",
|
||||||
"You are going to delete": "Stai per eliminare",
|
"You are going to delete": "Stai per eliminare",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Stai per disabilitare i commenti di",
|
"You are going to disable comments to": "Stai per disabilitare i commenti di",
|
||||||
"You are going to disable download for": "Stai per disabilitare il download di",
|
"You are going to disable download for": "Stai per disabilitare il download di",
|
||||||
"You are going to enable comments to": "Stai per abilitare i commenti di",
|
"You are going to enable comments to": "Stai per abilitare i commenti di",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "メディアを削除",
|
"DELETE MEDIA": "メディアを削除",
|
||||||
"DOWNLOAD": "ダウンロード",
|
"DOWNLOAD": "ダウンロード",
|
||||||
"DURATION": "期間",
|
"DURATION": "期間",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "メディアを削除",
|
"Delete Media": "メディアを削除",
|
||||||
"Delete media": "メディアを削除",
|
"Delete media": "メディアを削除",
|
||||||
"Disable Comments": "コメントを無効化",
|
"Disable Comments": "コメントを無効化",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "所有者の変更に失敗しました。もう一度お試しください。",
|
"Failed to change owner. Please try again.": "所有者の変更に失敗しました。もう一度お試しください。",
|
||||||
"Failed to copy media.": "メディアのコピーに失敗しました。",
|
"Failed to copy media.": "メディアのコピーに失敗しました。",
|
||||||
"Failed to create playlist": "プレイリストの作成に失敗しました",
|
"Failed to create playlist": "プレイリストの作成に失敗しました",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "メディアの削除に失敗しました。もう一度お試しください。",
|
"Failed to delete media. Please try again.": "メディアの削除に失敗しました。もう一度お試しください。",
|
||||||
"Failed to disable comments.": "コメントの無効化に失敗しました。",
|
"Failed to disable comments.": "コメントの無効化に失敗しました。",
|
||||||
"Failed to disable download.": "ダウンロードの無効化に失敗しました。",
|
"Failed to disable download.": "ダウンロードの無効化に失敗しました。",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "既存ユーザーをフィルター...",
|
"Filter existing users...": "既存ユーザーをフィルター...",
|
||||||
"Filter playlists...": "プレイリストをフィルター...",
|
"Filter playlists...": "プレイリストをフィルター...",
|
||||||
"Filters": "フィルター",
|
"Filters": "フィルター",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "行く",
|
"Go": "行く",
|
||||||
"History": "履歴",
|
"History": "履歴",
|
||||||
"Home": "ホーム",
|
"Home": "ホーム",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "コメントを管理",
|
"Manage comments": "コメントを管理",
|
||||||
"Manage media": "メディアを管理",
|
"Manage media": "メディアを管理",
|
||||||
"Manage users": "ユーザーを管理",
|
"Manage users": "ユーザーを管理",
|
||||||
|
"Management": "",
|
||||||
"Media": "メディア",
|
"Media": "メディア",
|
||||||
"Media I own": "自分が所有するメディア",
|
"Media I own": "自分が所有するメディア",
|
||||||
"Media was edited": "メディアが編集されました",
|
"Media was edited": "メディアが編集されました",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "の結果はありません",
|
"No results for": "の結果はありません",
|
||||||
"No tags": "タグなし",
|
"No tags": "タグなし",
|
||||||
"No users to add": "追加するユーザーなし",
|
"No users to add": "追加するユーザーなし",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "プレイリスト",
|
"PLAYLISTS": "プレイリスト",
|
||||||
"PUBLISH STATE": "公開状態",
|
"PUBLISH STATE": "公開状態",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "公開日",
|
"Published on": "公開日",
|
||||||
"Recent uploads": "最近のアップロード",
|
"Recent uploads": "最近のアップロード",
|
||||||
"Recommended": "おすすめ",
|
"Recommended": "おすすめ",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "画面を録画",
|
"Record Screen": "画面を録画",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "登録",
|
"Register": "登録",
|
||||||
"Remove category": "カテゴリーを削除",
|
"Remove category": "カテゴリーを削除",
|
||||||
"Remove from list": "リストから削除",
|
"Remove from list": "リストから削除",
|
||||||
"Remove tag": "タグを削除",
|
"Remove tag": "タグを削除",
|
||||||
"Remove user": "ユーザーを削除",
|
"Remove user": "ユーザーを削除",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "保存",
|
"SAVE": "保存",
|
||||||
"SEARCH": "検索",
|
"SEARCH": "検索",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "すべてのメディアを選択",
|
"Select all media": "すべてのメディアを選択",
|
||||||
"Select publish state:": "公開状態を選択:",
|
"Select publish state:": "公開状態を選択:",
|
||||||
"Selected": "選択済み",
|
"Selected": "選択済み",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "自分が共有",
|
"Shared by me": "自分が共有",
|
||||||
"Shared with me": "共有されたもの",
|
"Shared with me": "共有されたもの",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "サインイン",
|
"Sign in": "サインイン",
|
||||||
"Sign out": "サインアウト",
|
"Sign out": "サインアウト",
|
||||||
"Sort By": "並び替え",
|
"Sort By": "並び替え",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "コメントが正常に有効化されました",
|
"Successfully Enabled comments": "コメントが正常に有効化されました",
|
||||||
"Successfully changed owner": "所有者が正常に変更されました",
|
"Successfully changed owner": "所有者が正常に変更されました",
|
||||||
"Successfully deleted": "正常に削除されました",
|
"Successfully deleted": "正常に削除されました",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "正常に更新されました",
|
"Successfully updated": "正常に更新されました",
|
||||||
"Successfully updated categories": "カテゴリーが正常に更新されました",
|
"Successfully updated categories": "カテゴリーが正常に更新されました",
|
||||||
"Successfully updated playlist membership": "プレイリストメンバーシップが正常に更新されました",
|
"Successfully updated playlist membership": "プレイリストメンバーシップが正常に更新されました",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "メディアをアップロード",
|
"Upload media": "メディアをアップロード",
|
||||||
"Uploads": "アップロード",
|
"Uploads": "アップロード",
|
||||||
"Users": "ユーザー",
|
"Users": "ユーザー",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "すべて表示",
|
"VIEW ALL": "すべて表示",
|
||||||
"Video": "ビデオ",
|
"Video": "ビデオ",
|
||||||
"View all": "すべて表示",
|
"View all": "すべて表示",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "ようこそ",
|
"Welcome": "ようこそ",
|
||||||
"You are going to copy": "コピーします",
|
"You are going to copy": "コピーします",
|
||||||
"You are going to delete": "削除します",
|
"You are going to delete": "削除します",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "コメントを無効化します",
|
"You are going to disable comments to": "コメントを無効化します",
|
||||||
"You are going to disable download for": "ダウンロードを無効化します",
|
"You are going to disable download for": "ダウンロードを無効化します",
|
||||||
"You are going to enable comments to": "コメントを有効化します",
|
"You are going to enable comments to": "コメントを有効化します",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "미디어 삭제",
|
"DELETE MEDIA": "미디어 삭제",
|
||||||
"DOWNLOAD": "다운로드",
|
"DOWNLOAD": "다운로드",
|
||||||
"DURATION": "재생 시간",
|
"DURATION": "재생 시간",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "미디어 삭제",
|
"Delete Media": "미디어 삭제",
|
||||||
"Delete media": "미디어 삭제",
|
"Delete media": "미디어 삭제",
|
||||||
"Disable Comments": "댓글 비활성화",
|
"Disable Comments": "댓글 비활성화",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "소유자 변경에 실패했습니다. 다시 시도해주세요.",
|
"Failed to change owner. Please try again.": "소유자 변경에 실패했습니다. 다시 시도해주세요.",
|
||||||
"Failed to copy media.": "미디어 복사에 실패했습니다.",
|
"Failed to copy media.": "미디어 복사에 실패했습니다.",
|
||||||
"Failed to create playlist": "재생 목록 만들기 실패",
|
"Failed to create playlist": "재생 목록 만들기 실패",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "미디어 삭제에 실패했습니다. 다시 시도해주세요.",
|
"Failed to delete media. Please try again.": "미디어 삭제에 실패했습니다. 다시 시도해주세요.",
|
||||||
"Failed to disable comments.": "댓글 비활성화에 실패했습니다.",
|
"Failed to disable comments.": "댓글 비활성화에 실패했습니다.",
|
||||||
"Failed to disable download.": "다운로드 비활성화에 실패했습니다.",
|
"Failed to disable download.": "다운로드 비활성화에 실패했습니다.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "기존 사용자 필터링...",
|
"Filter existing users...": "기존 사용자 필터링...",
|
||||||
"Filter playlists...": "재생 목록 필터링...",
|
"Filter playlists...": "재생 목록 필터링...",
|
||||||
"Filters": "필터",
|
"Filters": "필터",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "이동",
|
"Go": "이동",
|
||||||
"History": "기록",
|
"History": "기록",
|
||||||
"Home": "홈",
|
"Home": "홈",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "댓글 관리",
|
"Manage comments": "댓글 관리",
|
||||||
"Manage media": "미디어 관리",
|
"Manage media": "미디어 관리",
|
||||||
"Manage users": "사용자 관리",
|
"Manage users": "사용자 관리",
|
||||||
|
"Management": "",
|
||||||
"Media": "미디어",
|
"Media": "미디어",
|
||||||
"Media I own": "내가 소유한 미디어",
|
"Media I own": "내가 소유한 미디어",
|
||||||
"Media was edited": "미디어가 편집되었습니다",
|
"Media was edited": "미디어가 편집되었습니다",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "결과 없음",
|
"No results for": "결과 없음",
|
||||||
"No tags": "태그 없음",
|
"No tags": "태그 없음",
|
||||||
"No users to add": "추가할 사용자 없음",
|
"No users to add": "추가할 사용자 없음",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "재생 목록",
|
"PLAYLISTS": "재생 목록",
|
||||||
"PUBLISH STATE": "게시 상태",
|
"PUBLISH STATE": "게시 상태",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "게시일",
|
"Published on": "게시일",
|
||||||
"Recent uploads": "최근 업로드",
|
"Recent uploads": "최근 업로드",
|
||||||
"Recommended": "추천",
|
"Recommended": "추천",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "화면 녹화",
|
"Record Screen": "화면 녹화",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "등록",
|
"Register": "등록",
|
||||||
"Remove category": "카테고리 제거",
|
"Remove category": "카테고리 제거",
|
||||||
"Remove from list": "목록에서 제거",
|
"Remove from list": "목록에서 제거",
|
||||||
"Remove tag": "태그 제거",
|
"Remove tag": "태그 제거",
|
||||||
"Remove user": "사용자 제거",
|
"Remove user": "사용자 제거",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "저장",
|
"SAVE": "저장",
|
||||||
"SEARCH": "검색",
|
"SEARCH": "검색",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "모든 미디어 선택",
|
"Select all media": "모든 미디어 선택",
|
||||||
"Select publish state:": "게시 상태 선택:",
|
"Select publish state:": "게시 상태 선택:",
|
||||||
"Selected": "선택됨",
|
"Selected": "선택됨",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "내가 공유함",
|
"Shared by me": "내가 공유함",
|
||||||
"Shared with me": "나와 공유됨",
|
"Shared with me": "나와 공유됨",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "로그인",
|
"Sign in": "로그인",
|
||||||
"Sign out": "로그아웃",
|
"Sign out": "로그아웃",
|
||||||
"Sort By": "정렬",
|
"Sort By": "정렬",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "댓글이 활성화되었습니다",
|
"Successfully Enabled comments": "댓글이 활성화되었습니다",
|
||||||
"Successfully changed owner": "소유자가 변경되었습니다",
|
"Successfully changed owner": "소유자가 변경되었습니다",
|
||||||
"Successfully deleted": "삭제 성공",
|
"Successfully deleted": "삭제 성공",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "업데이트 성공",
|
"Successfully updated": "업데이트 성공",
|
||||||
"Successfully updated categories": "카테고리가 업데이트되었습니다",
|
"Successfully updated categories": "카테고리가 업데이트되었습니다",
|
||||||
"Successfully updated playlist membership": "재생 목록 멤버십이 업데이트되었습니다",
|
"Successfully updated playlist membership": "재생 목록 멤버십이 업데이트되었습니다",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "미디어 업로드",
|
"Upload media": "미디어 업로드",
|
||||||
"Uploads": "업로드",
|
"Uploads": "업로드",
|
||||||
"Users": "사용자",
|
"Users": "사용자",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "모두 보기",
|
"VIEW ALL": "모두 보기",
|
||||||
"Video": "비디오",
|
"Video": "비디오",
|
||||||
"View all": "모두 보기",
|
"View all": "모두 보기",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "환영합니다",
|
"Welcome": "환영합니다",
|
||||||
"You are going to copy": "복사하려고 합니다",
|
"You are going to copy": "복사하려고 합니다",
|
||||||
"You are going to delete": "삭제하려고 합니다",
|
"You are going to delete": "삭제하려고 합니다",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "댓글을 비활성화하려고 합니다",
|
"You are going to disable comments to": "댓글을 비활성화하려고 합니다",
|
||||||
"You are going to disable download for": "다운로드를 비활성화하려고 합니다",
|
"You are going to disable download for": "다운로드를 비활성화하려고 합니다",
|
||||||
"You are going to enable comments to": "댓글을 활성화하려고 합니다",
|
"You are going to enable comments to": "댓글을 활성화하려고 합니다",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "MEDIA VERWIJDEREN",
|
"DELETE MEDIA": "MEDIA VERWIJDEREN",
|
||||||
"DOWNLOAD": "DOWNLOADEN",
|
"DOWNLOAD": "DOWNLOADEN",
|
||||||
"DURATION": "DUUR",
|
"DURATION": "DUUR",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Media verwijderen",
|
"Delete Media": "Media verwijderen",
|
||||||
"Delete media": "Media verwijderen",
|
"Delete media": "Media verwijderen",
|
||||||
"Disable Comments": "Reacties uitschakelen",
|
"Disable Comments": "Reacties uitschakelen",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Eigenaar wijzigen mislukt. Probeer het opnieuw.",
|
"Failed to change owner. Please try again.": "Eigenaar wijzigen mislukt. Probeer het opnieuw.",
|
||||||
"Failed to copy media.": "Media kopiëren mislukt.",
|
"Failed to copy media.": "Media kopiëren mislukt.",
|
||||||
"Failed to create playlist": "Afspeellijst maken mislukt",
|
"Failed to create playlist": "Afspeellijst maken mislukt",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Media verwijderen mislukt. Probeer het opnieuw.",
|
"Failed to delete media. Please try again.": "Media verwijderen mislukt. Probeer het opnieuw.",
|
||||||
"Failed to disable comments.": "Reacties uitschakelen mislukt.",
|
"Failed to disable comments.": "Reacties uitschakelen mislukt.",
|
||||||
"Failed to disable download.": "Download uitschakelen mislukt.",
|
"Failed to disable download.": "Download uitschakelen mislukt.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filter bestaande gebruikers...",
|
"Filter existing users...": "Filter bestaande gebruikers...",
|
||||||
"Filter playlists...": "Filter afspeellijsten...",
|
"Filter playlists...": "Filter afspeellijsten...",
|
||||||
"Filters": "Filters",
|
"Filters": "Filters",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Ga",
|
"Go": "Ga",
|
||||||
"History": "Geschiedenis",
|
"History": "Geschiedenis",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Reacties beheren",
|
"Manage comments": "Reacties beheren",
|
||||||
"Manage media": "Media beheren",
|
"Manage media": "Media beheren",
|
||||||
"Manage users": "Gebruikers beheren",
|
"Manage users": "Gebruikers beheren",
|
||||||
|
"Management": "",
|
||||||
"Media": "Media",
|
"Media": "Media",
|
||||||
"Media I own": "Media die ik bezit",
|
"Media I own": "Media die ik bezit",
|
||||||
"Media was edited": "Media is bewerkt",
|
"Media was edited": "Media is bewerkt",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Geen resultaten voor",
|
"No results for": "Geen resultaten voor",
|
||||||
"No tags": "Geen tags",
|
"No tags": "Geen tags",
|
||||||
"No users to add": "Geen gebruikers om toe te voegen",
|
"No users to add": "Geen gebruikers om toe te voegen",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "AFSPEELLIJSTEN",
|
"PLAYLISTS": "AFSPEELLIJSTEN",
|
||||||
"PUBLISH STATE": "PUBLICATIESTATUS",
|
"PUBLISH STATE": "PUBLICATIESTATUS",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Gepubliceerd op",
|
"Published on": "Gepubliceerd op",
|
||||||
"Recent uploads": "Recente uploads",
|
"Recent uploads": "Recente uploads",
|
||||||
"Recommended": "Aanbevolen",
|
"Recommended": "Aanbevolen",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Scherm opnemen",
|
"Record Screen": "Scherm opnemen",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registreren",
|
"Register": "Registreren",
|
||||||
"Remove category": "Categorie verwijderen",
|
"Remove category": "Categorie verwijderen",
|
||||||
"Remove from list": "Verwijderen uit lijst",
|
"Remove from list": "Verwijderen uit lijst",
|
||||||
"Remove tag": "Tag verwijderen",
|
"Remove tag": "Tag verwijderen",
|
||||||
"Remove user": "Gebruiker verwijderen",
|
"Remove user": "Gebruiker verwijderen",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "OPSLAAN",
|
"SAVE": "OPSLAAN",
|
||||||
"SEARCH": "ZOEKEN",
|
"SEARCH": "ZOEKEN",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Alle media selecteren",
|
"Select all media": "Alle media selecteren",
|
||||||
"Select publish state:": "Selecteer publicatiestatus:",
|
"Select publish state:": "Selecteer publicatiestatus:",
|
||||||
"Selected": "Geselecteerd",
|
"Selected": "Geselecteerd",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Gedeeld door mij",
|
"Shared by me": "Gedeeld door mij",
|
||||||
"Shared with me": "Gedeeld met mij",
|
"Shared with me": "Gedeeld met mij",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Inloggen",
|
"Sign in": "Inloggen",
|
||||||
"Sign out": "Uitloggen",
|
"Sign out": "Uitloggen",
|
||||||
"Sort By": "Sorteer op",
|
"Sort By": "Sorteer op",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Reacties succesvol ingeschakeld",
|
"Successfully Enabled comments": "Reacties succesvol ingeschakeld",
|
||||||
"Successfully changed owner": "Eigenaar succesvol gewijzigd",
|
"Successfully changed owner": "Eigenaar succesvol gewijzigd",
|
||||||
"Successfully deleted": "Succesvol verwijderd",
|
"Successfully deleted": "Succesvol verwijderd",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Succesvol bijgewerkt",
|
"Successfully updated": "Succesvol bijgewerkt",
|
||||||
"Successfully updated categories": "Categorieën succesvol bijgewerkt",
|
"Successfully updated categories": "Categorieën succesvol bijgewerkt",
|
||||||
"Successfully updated playlist membership": "Afspeellijstlidmaatschap succesvol bijgewerkt",
|
"Successfully updated playlist membership": "Afspeellijstlidmaatschap succesvol bijgewerkt",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Media uploaden",
|
"Upload media": "Media uploaden",
|
||||||
"Uploads": "Uploads",
|
"Uploads": "Uploads",
|
||||||
"Users": "Gebruikers",
|
"Users": "Gebruikers",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "BEKIJK ALLES",
|
"VIEW ALL": "BEKIJK ALLES",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Bekijk alles",
|
"View all": "Bekijk alles",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Welkom",
|
"Welcome": "Welkom",
|
||||||
"You are going to copy": "Je gaat kopiëren",
|
"You are going to copy": "Je gaat kopiëren",
|
||||||
"You are going to delete": "Je gaat verwijderen",
|
"You are going to delete": "Je gaat verwijderen",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Je gaat reacties uitschakelen voor",
|
"You are going to disable comments to": "Je gaat reacties uitschakelen voor",
|
||||||
"You are going to disable download for": "Je gaat download uitschakelen voor",
|
"You are going to disable download for": "Je gaat download uitschakelen voor",
|
||||||
"You are going to enable comments to": "Je gaat reacties inschakelen voor",
|
"You are going to enable comments to": "Je gaat reacties inschakelen voor",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "EXCLUIR MÍDIA",
|
"DELETE MEDIA": "EXCLUIR MÍDIA",
|
||||||
"DOWNLOAD": "BAIXAR",
|
"DOWNLOAD": "BAIXAR",
|
||||||
"DURATION": "DURAÇÃO",
|
"DURATION": "DURAÇÃO",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Excluir mídia",
|
"Delete Media": "Excluir mídia",
|
||||||
"Delete media": "Excluir mídia",
|
"Delete media": "Excluir mídia",
|
||||||
"Disable Comments": "Desativar comentários",
|
"Disable Comments": "Desativar comentários",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Falha ao mudar proprietário. Por favor, tente novamente.",
|
"Failed to change owner. Please try again.": "Falha ao mudar proprietário. Por favor, tente novamente.",
|
||||||
"Failed to copy media.": "Falha ao copiar mídia.",
|
"Failed to copy media.": "Falha ao copiar mídia.",
|
||||||
"Failed to create playlist": "Falha ao criar playlist",
|
"Failed to create playlist": "Falha ao criar playlist",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Falha ao excluir mídia. Por favor, tente novamente.",
|
"Failed to delete media. Please try again.": "Falha ao excluir mídia. Por favor, tente novamente.",
|
||||||
"Failed to disable comments.": "Falha ao desativar comentários.",
|
"Failed to disable comments.": "Falha ao desativar comentários.",
|
||||||
"Failed to disable download.": "Falha ao desativar download.",
|
"Failed to disable download.": "Falha ao desativar download.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filtrar usuários existentes...",
|
"Filter existing users...": "Filtrar usuários existentes...",
|
||||||
"Filter playlists...": "Filtrar playlists...",
|
"Filter playlists...": "Filtrar playlists...",
|
||||||
"Filters": "Filtros",
|
"Filters": "Filtros",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Ir",
|
"Go": "Ir",
|
||||||
"History": "Histórico",
|
"History": "Histórico",
|
||||||
"Home": "Início",
|
"Home": "Início",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Gerenciar comentários",
|
"Manage comments": "Gerenciar comentários",
|
||||||
"Manage media": "Gerenciar mídia",
|
"Manage media": "Gerenciar mídia",
|
||||||
"Manage users": "Gerenciar usuários",
|
"Manage users": "Gerenciar usuários",
|
||||||
|
"Management": "",
|
||||||
"Media": "Mídia",
|
"Media": "Mídia",
|
||||||
"Media I own": "Mídia que possuo",
|
"Media I own": "Mídia que possuo",
|
||||||
"Media was edited": "Mídia foi editada",
|
"Media was edited": "Mídia foi editada",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Nenhum resultado para",
|
"No results for": "Nenhum resultado para",
|
||||||
"No tags": "Nenhuma tag",
|
"No tags": "Nenhuma tag",
|
||||||
"No users to add": "Nenhum usuário para adicionar",
|
"No users to add": "Nenhum usuário para adicionar",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "PLAYLISTS",
|
"PLAYLISTS": "PLAYLISTS",
|
||||||
"PUBLISH STATE": "ESTADO DE PUBLICAÇÃO",
|
"PUBLISH STATE": "ESTADO DE PUBLICAÇÃO",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Publicado em",
|
"Published on": "Publicado em",
|
||||||
"Recent uploads": "Uploads recentes",
|
"Recent uploads": "Uploads recentes",
|
||||||
"Recommended": "Recomendado",
|
"Recommended": "Recomendado",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Gravar tela",
|
"Record Screen": "Gravar tela",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registrar",
|
"Register": "Registrar",
|
||||||
"Remove category": "Remover categoria",
|
"Remove category": "Remover categoria",
|
||||||
"Remove from list": "Remover da lista",
|
"Remove from list": "Remover da lista",
|
||||||
"Remove tag": "Remover tag",
|
"Remove tag": "Remover tag",
|
||||||
"Remove user": "Remover usuário",
|
"Remove user": "Remover usuário",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "SALVAR",
|
"SAVE": "SALVAR",
|
||||||
"SEARCH": "PESQUISAR",
|
"SEARCH": "PESQUISAR",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Selecionar todas as mídias",
|
"Select all media": "Selecionar todas as mídias",
|
||||||
"Select publish state:": "Selecionar estado de publicação:",
|
"Select publish state:": "Selecionar estado de publicação:",
|
||||||
"Selected": "Selecionado",
|
"Selected": "Selecionado",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Compartilhado por mim",
|
"Shared by me": "Compartilhado por mim",
|
||||||
"Shared with me": "Compartilhado comigo",
|
"Shared with me": "Compartilhado comigo",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Entrar",
|
"Sign in": "Entrar",
|
||||||
"Sign out": "Sair",
|
"Sign out": "Sair",
|
||||||
"Sort By": "Ordenar por",
|
"Sort By": "Ordenar por",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Comentários ativados com sucesso",
|
"Successfully Enabled comments": "Comentários ativados com sucesso",
|
||||||
"Successfully changed owner": "Proprietário alterado com sucesso",
|
"Successfully changed owner": "Proprietário alterado com sucesso",
|
||||||
"Successfully deleted": "Excluído com sucesso",
|
"Successfully deleted": "Excluído com sucesso",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Atualizado com sucesso",
|
"Successfully updated": "Atualizado com sucesso",
|
||||||
"Successfully updated categories": "Categorias atualizadas com sucesso",
|
"Successfully updated categories": "Categorias atualizadas com sucesso",
|
||||||
"Successfully updated playlist membership": "Associação da playlist atualizada com sucesso",
|
"Successfully updated playlist membership": "Associação da playlist atualizada com sucesso",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Carregar mídia",
|
"Upload media": "Carregar mídia",
|
||||||
"Uploads": "Uploads",
|
"Uploads": "Uploads",
|
||||||
"Users": "Usuários",
|
"Users": "Usuários",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "VER TODOS",
|
"VIEW ALL": "VER TODOS",
|
||||||
"Video": "Vídeo",
|
"Video": "Vídeo",
|
||||||
"View all": "Ver todos",
|
"View all": "Ver todos",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Bem-vindo",
|
"Welcome": "Bem-vindo",
|
||||||
"You are going to copy": "Você vai copiar",
|
"You are going to copy": "Você vai copiar",
|
||||||
"You are going to delete": "Você vai excluir",
|
"You are going to delete": "Você vai excluir",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Você vai desativar comentários de",
|
"You are going to disable comments to": "Você vai desativar comentários de",
|
||||||
"You are going to disable download for": "Você vai desativar download de",
|
"You are going to disable download for": "Você vai desativar download de",
|
||||||
"You are going to enable comments to": "Você vai ativar comentários de",
|
"You are going to enable comments to": "Você vai ativar comentários de",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "УДАЛИТЬ МЕДИА",
|
"DELETE MEDIA": "УДАЛИТЬ МЕДИА",
|
||||||
"DOWNLOAD": "СКАЧАТЬ",
|
"DOWNLOAD": "СКАЧАТЬ",
|
||||||
"DURATION": "ДЛИТЕЛЬНОСТЬ",
|
"DURATION": "ДЛИТЕЛЬНОСТЬ",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Удалить медиа",
|
"Delete Media": "Удалить медиа",
|
||||||
"Delete media": "Удалить медиа",
|
"Delete media": "Удалить медиа",
|
||||||
"Disable Comments": "Отключить комментарии",
|
"Disable Comments": "Отключить комментарии",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Не удалось изменить владельца. Пожалуйста, попробуйте снова.",
|
"Failed to change owner. Please try again.": "Не удалось изменить владельца. Пожалуйста, попробуйте снова.",
|
||||||
"Failed to copy media.": "Не удалось скопировать медиа.",
|
"Failed to copy media.": "Не удалось скопировать медиа.",
|
||||||
"Failed to create playlist": "Не удалось создать плейлист",
|
"Failed to create playlist": "Не удалось создать плейлист",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Не удалось удалить медиа. Пожалуйста, попробуйте снова.",
|
"Failed to delete media. Please try again.": "Не удалось удалить медиа. Пожалуйста, попробуйте снова.",
|
||||||
"Failed to disable comments.": "Не удалось отключить комментарии.",
|
"Failed to disable comments.": "Не удалось отключить комментарии.",
|
||||||
"Failed to disable download.": "Не удалось отключить загрузку.",
|
"Failed to disable download.": "Не удалось отключить загрузку.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Фильтровать существующих пользователей...",
|
"Filter existing users...": "Фильтровать существующих пользователей...",
|
||||||
"Filter playlists...": "Фильтровать плейлисты...",
|
"Filter playlists...": "Фильтровать плейлисты...",
|
||||||
"Filters": "Фильтры",
|
"Filters": "Фильтры",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Перейти",
|
"Go": "Перейти",
|
||||||
"History": "История",
|
"History": "История",
|
||||||
"Home": "Главная",
|
"Home": "Главная",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Управление комментариями",
|
"Manage comments": "Управление комментариями",
|
||||||
"Manage media": "Управление медиа",
|
"Manage media": "Управление медиа",
|
||||||
"Manage users": "Управление пользователями",
|
"Manage users": "Управление пользователями",
|
||||||
|
"Management": "",
|
||||||
"Media": "Медиа",
|
"Media": "Медиа",
|
||||||
"Media I own": "Медиа, которыми я владею",
|
"Media I own": "Медиа, которыми я владею",
|
||||||
"Media was edited": "Медиа было отредактировано",
|
"Media was edited": "Медиа было отредактировано",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Нет результатов для",
|
"No results for": "Нет результатов для",
|
||||||
"No tags": "Нет тегов",
|
"No tags": "Нет тегов",
|
||||||
"No users to add": "Нет пользователей для добавления",
|
"No users to add": "Нет пользователей для добавления",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "ПЛЕЙЛИСТЫ",
|
"PLAYLISTS": "ПЛЕЙЛИСТЫ",
|
||||||
"PUBLISH STATE": "СОСТОЯНИЕ ПУБЛИКАЦИИ",
|
"PUBLISH STATE": "СОСТОЯНИЕ ПУБЛИКАЦИИ",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Опубликовано",
|
"Published on": "Опубликовано",
|
||||||
"Recent uploads": "Недавние загрузки",
|
"Recent uploads": "Недавние загрузки",
|
||||||
"Recommended": "Рекомендуемое",
|
"Recommended": "Рекомендуемое",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Запись экрана",
|
"Record Screen": "Запись экрана",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Регистрация",
|
"Register": "Регистрация",
|
||||||
"Remove category": "Удалить категорию",
|
"Remove category": "Удалить категорию",
|
||||||
"Remove from list": "Удалить из списка",
|
"Remove from list": "Удалить из списка",
|
||||||
"Remove tag": "Удалить тег",
|
"Remove tag": "Удалить тег",
|
||||||
"Remove user": "Удалить пользователя",
|
"Remove user": "Удалить пользователя",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "СОХРАНИТЬ",
|
"SAVE": "СОХРАНИТЬ",
|
||||||
"SEARCH": "ПОИСК",
|
"SEARCH": "ПОИСК",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Выбрать все медиа",
|
"Select all media": "Выбрать все медиа",
|
||||||
"Select publish state:": "Выберите состояние публикации:",
|
"Select publish state:": "Выберите состояние публикации:",
|
||||||
"Selected": "Выбрано",
|
"Selected": "Выбрано",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Мной поделено",
|
"Shared by me": "Мной поделено",
|
||||||
"Shared with me": "Поделено со мной",
|
"Shared with me": "Поделено со мной",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Войти",
|
"Sign in": "Войти",
|
||||||
"Sign out": "Выйти",
|
"Sign out": "Выйти",
|
||||||
"Sort By": "Сортировать по",
|
"Sort By": "Сортировать по",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Комментарии успешно включены",
|
"Successfully Enabled comments": "Комментарии успешно включены",
|
||||||
"Successfully changed owner": "Владелец успешно изменен",
|
"Successfully changed owner": "Владелец успешно изменен",
|
||||||
"Successfully deleted": "Успешно удалено",
|
"Successfully deleted": "Успешно удалено",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Успешно обновлено",
|
"Successfully updated": "Успешно обновлено",
|
||||||
"Successfully updated categories": "Категории успешно обновлены",
|
"Successfully updated categories": "Категории успешно обновлены",
|
||||||
"Successfully updated playlist membership": "Членство в плейлисте успешно обновлено",
|
"Successfully updated playlist membership": "Членство в плейлисте успешно обновлено",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Загрузить медиа",
|
"Upload media": "Загрузить медиа",
|
||||||
"Uploads": "Загрузки",
|
"Uploads": "Загрузки",
|
||||||
"Users": "Пользователи",
|
"Users": "Пользователи",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "ПОКАЗАТЬ ВСЕ",
|
"VIEW ALL": "ПОКАЗАТЬ ВСЕ",
|
||||||
"Video": "Видео",
|
"Video": "Видео",
|
||||||
"View all": "Показать все",
|
"View all": "Показать все",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Добро пожаловать",
|
"Welcome": "Добро пожаловать",
|
||||||
"You are going to copy": "Вы собираетесь скопировать",
|
"You are going to copy": "Вы собираетесь скопировать",
|
||||||
"You are going to delete": "Вы собираетесь удалить",
|
"You are going to delete": "Вы собираетесь удалить",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Вы собираетесь отключить комментарии для",
|
"You are going to disable comments to": "Вы собираетесь отключить комментарии для",
|
||||||
"You are going to disable download for": "Вы собираетесь отключить загрузку для",
|
"You are going to disable download for": "Вы собираетесь отключить загрузку для",
|
||||||
"You are going to enable comments to": "Вы собираетесь включить комментарии для",
|
"You are going to enable comments to": "Вы собираетесь включить комментарии для",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "IZBRIŠI MEDIJ",
|
"DELETE MEDIA": "IZBRIŠI MEDIJ",
|
||||||
"DOWNLOAD": "PRENESI",
|
"DOWNLOAD": "PRENESI",
|
||||||
"DURATION": "TRAJANJE",
|
"DURATION": "TRAJANJE",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Izbriši Medij",
|
"Delete Media": "Izbriši Medij",
|
||||||
"Delete media": "Izbriši medij",
|
"Delete media": "Izbriši medij",
|
||||||
"Disable Comments": "Onemogoči Komentarje",
|
"Disable Comments": "Onemogoči Komentarje",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Spreminjanje lastnika ni uspelo. Prosim poskusite ponovno.",
|
"Failed to change owner. Please try again.": "Spreminjanje lastnika ni uspelo. Prosim poskusite ponovno.",
|
||||||
"Failed to copy media.": "Kopiranje medija ni uspelo.",
|
"Failed to copy media.": "Kopiranje medija ni uspelo.",
|
||||||
"Failed to create playlist": "Ustvarjanje seznama predvajanja ni uspelo",
|
"Failed to create playlist": "Ustvarjanje seznama predvajanja ni uspelo",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Brisanje medija ni uspelo. Prosim poskusite ponovno.",
|
"Failed to delete media. Please try again.": "Brisanje medija ni uspelo. Prosim poskusite ponovno.",
|
||||||
"Failed to disable comments.": "Onemogočanje komentarjev ni uspelo.",
|
"Failed to disable comments.": "Onemogočanje komentarjev ni uspelo.",
|
||||||
"Failed to disable download.": "Onemogočanje prenosa ni uspelo.",
|
"Failed to disable download.": "Onemogočanje prenosa ni uspelo.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Filtriraj obstoječe uporabnike...",
|
"Filter existing users...": "Filtriraj obstoječe uporabnike...",
|
||||||
"Filter playlists...": "Filtriraj sezname predvajanja...",
|
"Filter playlists...": "Filtriraj sezname predvajanja...",
|
||||||
"Filters": "Filtri",
|
"Filters": "Filtri",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Pojdi",
|
"Go": "Pojdi",
|
||||||
"History": "Zgodovina",
|
"History": "Zgodovina",
|
||||||
"Home": "Domov",
|
"Home": "Domov",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Upravljaj komentarje",
|
"Manage comments": "Upravljaj komentarje",
|
||||||
"Manage media": "Upravljaj medije",
|
"Manage media": "Upravljaj medije",
|
||||||
"Manage users": "Upravljaj uporabnike",
|
"Manage users": "Upravljaj uporabnike",
|
||||||
|
"Management": "",
|
||||||
"Media": "Mediji",
|
"Media": "Mediji",
|
||||||
"Media I own": "Mediji, ki jih posedujam",
|
"Media I own": "Mediji, ki jih posedujam",
|
||||||
"Media was edited": "Medij je bil urejen",
|
"Media was edited": "Medij je bil urejen",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Ni rezultatov za",
|
"No results for": "Ni rezultatov za",
|
||||||
"No tags": "Brez oznak",
|
"No tags": "Brez oznak",
|
||||||
"No users to add": "Ni uporabnikov za dodajanje",
|
"No users to add": "Ni uporabnikov za dodajanje",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "SEZNAMI PREDVAJANJA",
|
"PLAYLISTS": "SEZNAMI PREDVAJANJA",
|
||||||
"PUBLISH STATE": "STANJE OBJAVE",
|
"PUBLISH STATE": "STANJE OBJAVE",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Objavljeno",
|
"Published on": "Objavljeno",
|
||||||
"Recent uploads": "Nedavne naložitve",
|
"Recent uploads": "Nedavne naložitve",
|
||||||
"Recommended": "Priporočeno",
|
"Recommended": "Priporočeno",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Snemanje zaslona",
|
"Record Screen": "Snemanje zaslona",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Registracija",
|
"Register": "Registracija",
|
||||||
"Remove category": "Odstrani kategorijo",
|
"Remove category": "Odstrani kategorijo",
|
||||||
"Remove from list": "Odstrani s seznama",
|
"Remove from list": "Odstrani s seznama",
|
||||||
"Remove tag": "Odstrani oznako",
|
"Remove tag": "Odstrani oznako",
|
||||||
"Remove user": "Odstrani uporabnika",
|
"Remove user": "Odstrani uporabnika",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "SHRANI",
|
"SAVE": "SHRANI",
|
||||||
"SEARCH": "ISKANJE",
|
"SEARCH": "ISKANJE",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Izberi vse medije",
|
"Select all media": "Izberi vse medije",
|
||||||
"Select publish state:": "Izberi stanje objave:",
|
"Select publish state:": "Izberi stanje objave:",
|
||||||
"Selected": "Izbrano",
|
"Selected": "Izbrano",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Deljeno z moje strani",
|
"Shared by me": "Deljeno z moje strani",
|
||||||
"Shared with me": "Deljeno z mano",
|
"Shared with me": "Deljeno z mano",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Prijava",
|
"Sign in": "Prijava",
|
||||||
"Sign out": "Odjava",
|
"Sign out": "Odjava",
|
||||||
"Sort By": "Razvrsti po",
|
"Sort By": "Razvrsti po",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Komentarji uspešno omogočeni",
|
"Successfully Enabled comments": "Komentarji uspešno omogočeni",
|
||||||
"Successfully changed owner": "Lastnik uspešno spremenjen",
|
"Successfully changed owner": "Lastnik uspešno spremenjen",
|
||||||
"Successfully deleted": "Uspešno izbrisano",
|
"Successfully deleted": "Uspešno izbrisano",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Uspešno posodobljeno",
|
"Successfully updated": "Uspešno posodobljeno",
|
||||||
"Successfully updated categories": "Kategorije uspešno posodobljene",
|
"Successfully updated categories": "Kategorije uspešno posodobljene",
|
||||||
"Successfully updated playlist membership": "Članstvo seznama predvajanja uspešno posodobljeno",
|
"Successfully updated playlist membership": "Članstvo seznama predvajanja uspešno posodobljeno",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Naloži medij",
|
"Upload media": "Naloži medij",
|
||||||
"Uploads": "Naloženi",
|
"Uploads": "Naloženi",
|
||||||
"Users": "Uporabniki",
|
"Users": "Uporabniki",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "PRIKAŽI VSE",
|
"VIEW ALL": "PRIKAŽI VSE",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Prikaži vse",
|
"View all": "Prikaži vse",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Dobrodošli",
|
"Welcome": "Dobrodošli",
|
||||||
"You are going to copy": "Kopirate",
|
"You are going to copy": "Kopirate",
|
||||||
"You are going to delete": "Brišete",
|
"You are going to delete": "Brišete",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Onemogočate komentarje za",
|
"You are going to disable comments to": "Onemogočate komentarje za",
|
||||||
"You are going to disable download for": "Onemogočate prenos za",
|
"You are going to disable download for": "Onemogočate prenos za",
|
||||||
"You are going to enable comments to": "Omogočate komentarje za",
|
"You are going to enable comments to": "Omogočate komentarje za",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "MEDYAYI SİL",
|
"DELETE MEDIA": "MEDYAYI SİL",
|
||||||
"DOWNLOAD": "İNDİR",
|
"DOWNLOAD": "İNDİR",
|
||||||
"DURATION": "SÜRE",
|
"DURATION": "SÜRE",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "Medyayı Sil",
|
"Delete Media": "Medyayı Sil",
|
||||||
"Delete media": "Medyayı sil",
|
"Delete media": "Medyayı sil",
|
||||||
"Disable Comments": "Yorumları Devre Dışı Bırak",
|
"Disable Comments": "Yorumları Devre Dışı Bırak",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "Sahip değiştirilemedi. Lütfen tekrar deneyin.",
|
"Failed to change owner. Please try again.": "Sahip değiştirilemedi. Lütfen tekrar deneyin.",
|
||||||
"Failed to copy media.": "Medya kopyalanamadı.",
|
"Failed to copy media.": "Medya kopyalanamadı.",
|
||||||
"Failed to create playlist": "Çalma listesi oluşturulamadı",
|
"Failed to create playlist": "Çalma listesi oluşturulamadı",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "Medya silinemedi. Lütfen tekrar deneyin.",
|
"Failed to delete media. Please try again.": "Medya silinemedi. Lütfen tekrar deneyin.",
|
||||||
"Failed to disable comments.": "Yorumlar devre dışı bırakılamadı.",
|
"Failed to disable comments.": "Yorumlar devre dışı bırakılamadı.",
|
||||||
"Failed to disable download.": "İndirme devre dışı bırakılamadı.",
|
"Failed to disable download.": "İndirme devre dışı bırakılamadı.",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "Mevcut kullanıcıları filtrele...",
|
"Filter existing users...": "Mevcut kullanıcıları filtrele...",
|
||||||
"Filter playlists...": "Çalma listelerini filtrele...",
|
"Filter playlists...": "Çalma listelerini filtrele...",
|
||||||
"Filters": "Filtreler",
|
"Filters": "Filtreler",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "Git",
|
"Go": "Git",
|
||||||
"History": "Geçmiş",
|
"History": "Geçmiş",
|
||||||
"Home": "Ana Sayfa",
|
"Home": "Ana Sayfa",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "Yorumları yönet",
|
"Manage comments": "Yorumları yönet",
|
||||||
"Manage media": "Medyayı yönet",
|
"Manage media": "Medyayı yönet",
|
||||||
"Manage users": "Kullanıcıları yönet",
|
"Manage users": "Kullanıcıları yönet",
|
||||||
|
"Management": "",
|
||||||
"Media": "Medya",
|
"Media": "Medya",
|
||||||
"Media I own": "Sahip olduğum medya",
|
"Media I own": "Sahip olduğum medya",
|
||||||
"Media was edited": "Medya düzenlendi",
|
"Media was edited": "Medya düzenlendi",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "Sonuç bulunamadı",
|
"No results for": "Sonuç bulunamadı",
|
||||||
"No tags": "Etiket yok",
|
"No tags": "Etiket yok",
|
||||||
"No users to add": "Eklenecek kullanıcı yok",
|
"No users to add": "Eklenecek kullanıcı yok",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "ÇALMA LİSTELERİ",
|
"PLAYLISTS": "ÇALMA LİSTELERİ",
|
||||||
"PUBLISH STATE": "YAYINLANMA DURUMU",
|
"PUBLISH STATE": "YAYINLANMA DURUMU",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "Yayınlanma tarihi",
|
"Published on": "Yayınlanma tarihi",
|
||||||
"Recent uploads": "Son yüklemeler",
|
"Recent uploads": "Son yüklemeler",
|
||||||
"Recommended": "Önerilen",
|
"Recommended": "Önerilen",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "Ekranı Kaydet",
|
"Record Screen": "Ekranı Kaydet",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "Kayıt Ol",
|
"Register": "Kayıt Ol",
|
||||||
"Remove category": "Kategoriyi kaldır",
|
"Remove category": "Kategoriyi kaldır",
|
||||||
"Remove from list": "Listeden kaldır",
|
"Remove from list": "Listeden kaldır",
|
||||||
"Remove tag": "Etiketi kaldır",
|
"Remove tag": "Etiketi kaldır",
|
||||||
"Remove user": "Kullanıcıyı kaldır",
|
"Remove user": "Kullanıcıyı kaldır",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "KAYDET",
|
"SAVE": "KAYDET",
|
||||||
"SEARCH": "ARA",
|
"SEARCH": "ARA",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "Tüm medyayı seç",
|
"Select all media": "Tüm medyayı seç",
|
||||||
"Select publish state:": "Yayınlanma durumunu seç:",
|
"Select publish state:": "Yayınlanma durumunu seç:",
|
||||||
"Selected": "Seçildi",
|
"Selected": "Seçildi",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "Paylaştıklarım",
|
"Shared by me": "Paylaştıklarım",
|
||||||
"Shared with me": "Benimle paylaşılanlar",
|
"Shared with me": "Benimle paylaşılanlar",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "Giriş Yap",
|
"Sign in": "Giriş Yap",
|
||||||
"Sign out": "Çıkış Yap",
|
"Sign out": "Çıkış Yap",
|
||||||
"Sort By": "Sırala",
|
"Sort By": "Sırala",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "Yorumlar başarıyla etkinleştirildi",
|
"Successfully Enabled comments": "Yorumlar başarıyla etkinleştirildi",
|
||||||
"Successfully changed owner": "Sahip başarıyla değiştirildi",
|
"Successfully changed owner": "Sahip başarıyla değiştirildi",
|
||||||
"Successfully deleted": "Başarıyla silindi",
|
"Successfully deleted": "Başarıyla silindi",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "Başarıyla güncellendi",
|
"Successfully updated": "Başarıyla güncellendi",
|
||||||
"Successfully updated categories": "Kategoriler başarıyla güncellendi",
|
"Successfully updated categories": "Kategoriler başarıyla güncellendi",
|
||||||
"Successfully updated playlist membership": "Çalma listesi üyeliği başarıyla güncellendi",
|
"Successfully updated playlist membership": "Çalma listesi üyeliği başarıyla güncellendi",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "Medya yükle",
|
"Upload media": "Medya yükle",
|
||||||
"Uploads": "Yüklemeler",
|
"Uploads": "Yüklemeler",
|
||||||
"Users": "Kullanıcılar",
|
"Users": "Kullanıcılar",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "HEPSİNİ GÖR",
|
"VIEW ALL": "HEPSİNİ GÖR",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"View all": "Hepsini gör",
|
"View all": "Hepsini gör",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "Hoş geldiniz",
|
"Welcome": "Hoş geldiniz",
|
||||||
"You are going to copy": "Kopyalayacaksınız",
|
"You are going to copy": "Kopyalayacaksınız",
|
||||||
"You are going to delete": "Sileceksiniz",
|
"You are going to delete": "Sileceksiniz",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "Yorumları devre dışı bırakacaksınız",
|
"You are going to disable comments to": "Yorumları devre dışı bırakacaksınız",
|
||||||
"You are going to disable download for": "İndirmeyi devre dışı bırakacaksınız",
|
"You are going to disable download for": "İndirmeyi devre dışı bırakacaksınız",
|
||||||
"You are going to enable comments to": "Yorumları etkinleştireceksiniz",
|
"You are going to enable comments to": "Yorumları etkinleştireceksiniz",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "میڈیا حذف کریں",
|
"DELETE MEDIA": "میڈیا حذف کریں",
|
||||||
"DOWNLOAD": "ڈاؤن لوڈ",
|
"DOWNLOAD": "ڈاؤن لوڈ",
|
||||||
"DURATION": "دورانیہ",
|
"DURATION": "دورانیہ",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "میڈیا حذف کریں",
|
"Delete Media": "میڈیا حذف کریں",
|
||||||
"Delete media": "میڈیا حذف کریں",
|
"Delete media": "میڈیا حذف کریں",
|
||||||
"Disable Comments": "تبصرے غیر فعال کریں",
|
"Disable Comments": "تبصرے غیر فعال کریں",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "مالک تبدیل کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
|
"Failed to change owner. Please try again.": "مالک تبدیل کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
|
||||||
"Failed to copy media.": "میڈیا کاپی کرنے میں ناکام۔",
|
"Failed to copy media.": "میڈیا کاپی کرنے میں ناکام۔",
|
||||||
"Failed to create playlist": "پلے لسٹ بنانے میں ناکام",
|
"Failed to create playlist": "پلے لسٹ بنانے میں ناکام",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "میڈیا حذف کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
|
"Failed to delete media. Please try again.": "میڈیا حذف کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
|
||||||
"Failed to disable comments.": "تبصرے غیر فعال کرنے میں ناکام۔",
|
"Failed to disable comments.": "تبصرے غیر فعال کرنے میں ناکام۔",
|
||||||
"Failed to disable download.": "ڈاؤن لوڈ غیر فعال کرنے میں ناکام۔",
|
"Failed to disable download.": "ڈاؤن لوڈ غیر فعال کرنے میں ناکام۔",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "موجودہ صارفین فلٹر کریں...",
|
"Filter existing users...": "موجودہ صارفین فلٹر کریں...",
|
||||||
"Filter playlists...": "پلے لسٹس فلٹر کریں...",
|
"Filter playlists...": "پلے لسٹس فلٹر کریں...",
|
||||||
"Filters": "فلٹرز",
|
"Filters": "فلٹرز",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "جائیں",
|
"Go": "جائیں",
|
||||||
"History": "تاریخ",
|
"History": "تاریخ",
|
||||||
"Home": "ہوم",
|
"Home": "ہوم",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "تبصرے منظم کریں",
|
"Manage comments": "تبصرے منظم کریں",
|
||||||
"Manage media": "میڈیا منظم کریں",
|
"Manage media": "میڈیا منظم کریں",
|
||||||
"Manage users": "صارفین منظم کریں",
|
"Manage users": "صارفین منظم کریں",
|
||||||
|
"Management": "",
|
||||||
"Media": "میڈیا",
|
"Media": "میڈیا",
|
||||||
"Media I own": "",
|
"Media I own": "",
|
||||||
"Media was edited": "میڈیا ترمیم کیا گیا",
|
"Media was edited": "میڈیا ترمیم کیا گیا",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "کے لئے کوئی نتائج نہیں",
|
"No results for": "کے لئے کوئی نتائج نہیں",
|
||||||
"No tags": "کوئی ٹیگز نہیں",
|
"No tags": "کوئی ٹیگز نہیں",
|
||||||
"No users to add": "شامل کرنے کے لیے کوئی صارف نہیں",
|
"No users to add": "شامل کرنے کے لیے کوئی صارف نہیں",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "پلے لسٹس",
|
"PLAYLISTS": "پلے لسٹس",
|
||||||
"PUBLISH STATE": "اشاعت کی حالت",
|
"PUBLISH STATE": "اشاعت کی حالت",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "پر شائع ہوا",
|
"Published on": "پر شائع ہوا",
|
||||||
"Recent uploads": "حالیہ اپ لوڈز",
|
"Recent uploads": "حالیہ اپ لوڈز",
|
||||||
"Recommended": "تجویز کردہ",
|
"Recommended": "تجویز کردہ",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "اسکرین ریکارڈ کریں",
|
"Record Screen": "اسکرین ریکارڈ کریں",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "رجسٹر کریں",
|
"Register": "رجسٹر کریں",
|
||||||
"Remove category": "قسم ہٹائیں",
|
"Remove category": "قسم ہٹائیں",
|
||||||
"Remove from list": "فہرست سے ہٹائیں",
|
"Remove from list": "فہرست سے ہٹائیں",
|
||||||
"Remove tag": "ٹیگ ہٹائیں",
|
"Remove tag": "ٹیگ ہٹائیں",
|
||||||
"Remove user": "صارف ہٹائیں",
|
"Remove user": "صارف ہٹائیں",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "محفوظ کریں",
|
"SAVE": "محفوظ کریں",
|
||||||
"SEARCH": "تلاش کریں",
|
"SEARCH": "تلاش کریں",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "تمام میڈیا منتخب کریں",
|
"Select all media": "تمام میڈیا منتخب کریں",
|
||||||
"Select publish state:": "اشاعت کی حالت منتخب کریں:",
|
"Select publish state:": "اشاعت کی حالت منتخب کریں:",
|
||||||
"Selected": "منتخب شدہ",
|
"Selected": "منتخب شدہ",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "میری طرف سے شیئر کیا گیا",
|
"Shared by me": "میری طرف سے شیئر کیا گیا",
|
||||||
"Shared with me": "میرے ساتھ شیئر کیا گیا",
|
"Shared with me": "میرے ساتھ شیئر کیا گیا",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "سائن ان کریں",
|
"Sign in": "سائن ان کریں",
|
||||||
"Sign out": "سائن آؤٹ کریں",
|
"Sign out": "سائن آؤٹ کریں",
|
||||||
"Sort By": "ترتیب دیں",
|
"Sort By": "ترتیب دیں",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "تبصرے کامیابی سے فعال ہو گئے",
|
"Successfully Enabled comments": "تبصرے کامیابی سے فعال ہو گئے",
|
||||||
"Successfully changed owner": "مالک کامیابی سے تبدیل ہو گیا",
|
"Successfully changed owner": "مالک کامیابی سے تبدیل ہو گیا",
|
||||||
"Successfully deleted": "کامیابی سے حذف ہو گیا",
|
"Successfully deleted": "کامیابی سے حذف ہو گیا",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "کامیابی سے اپ ڈیٹ ہو گیا",
|
"Successfully updated": "کامیابی سے اپ ڈیٹ ہو گیا",
|
||||||
"Successfully updated categories": "اقسام کامیابی سے اپ ڈیٹ ہو گئیں",
|
"Successfully updated categories": "اقسام کامیابی سے اپ ڈیٹ ہو گئیں",
|
||||||
"Successfully updated playlist membership": "پلے لسٹ ممبرشپ کامیابی سے اپ ڈیٹ ہو گئی",
|
"Successfully updated playlist membership": "پلے لسٹ ممبرشپ کامیابی سے اپ ڈیٹ ہو گئی",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "میڈیا اپ لوڈ کریں",
|
"Upload media": "میڈیا اپ لوڈ کریں",
|
||||||
"Uploads": "اپ لوڈز",
|
"Uploads": "اپ لوڈز",
|
||||||
"Users": "صارفین",
|
"Users": "صارفین",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "سب دیکھیں",
|
"VIEW ALL": "سب دیکھیں",
|
||||||
"Video": "ویڈیو",
|
"Video": "ویڈیو",
|
||||||
"View all": "سب دیکھیں",
|
"View all": "سب دیکھیں",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "خوش آمدید",
|
"Welcome": "خوش آمدید",
|
||||||
"You are going to copy": "آپ کاپی کرنے جا رہے ہیں",
|
"You are going to copy": "آپ کاپی کرنے جا رہے ہیں",
|
||||||
"You are going to delete": "آپ حذف کرنے جا رہے ہیں",
|
"You are going to delete": "آپ حذف کرنے جا رہے ہیں",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "آپ تبصرے غیر فعال کرنے جا رہے ہیں",
|
"You are going to disable comments to": "آپ تبصرے غیر فعال کرنے جا رہے ہیں",
|
||||||
"You are going to disable download for": "آپ ڈاؤن لوڈ غیر فعال کرنے جا رہے ہیں",
|
"You are going to disable download for": "آپ ڈاؤن لوڈ غیر فعال کرنے جا رہے ہیں",
|
||||||
"You are going to enable comments to": "آپ تبصرے فعال کرنے جا رہے ہیں",
|
"You are going to enable comments to": "آپ تبصرے فعال کرنے جا رہے ہیں",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "删除媒体",
|
"DELETE MEDIA": "删除媒体",
|
||||||
"DOWNLOAD": "下载",
|
"DOWNLOAD": "下载",
|
||||||
"DURATION": "时长",
|
"DURATION": "时长",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "",
|
"Delete Media": "",
|
||||||
"Delete media": "删除媒体",
|
"Delete media": "删除媒体",
|
||||||
"Disable Comments": "",
|
"Disable Comments": "",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "",
|
"Failed to change owner. Please try again.": "",
|
||||||
"Failed to copy media.": "复制媒体失败。",
|
"Failed to copy media.": "复制媒体失败。",
|
||||||
"Failed to create playlist": "",
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "删除媒体失败。请重试。",
|
"Failed to delete media. Please try again.": "删除媒体失败。请重试。",
|
||||||
"Failed to disable comments.": "禁用评论失败。",
|
"Failed to disable comments.": "禁用评论失败。",
|
||||||
"Failed to disable download.": "禁用下载失败。",
|
"Failed to disable download.": "禁用下载失败。",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "",
|
"Filter existing users...": "",
|
||||||
"Filter playlists...": "",
|
"Filter playlists...": "",
|
||||||
"Filters": "筛选",
|
"Filters": "筛选",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "去",
|
"Go": "去",
|
||||||
"History": "历史",
|
"History": "历史",
|
||||||
"Home": "主页",
|
"Home": "主页",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "管理评论",
|
"Manage comments": "管理评论",
|
||||||
"Manage media": "管理媒体",
|
"Manage media": "管理媒体",
|
||||||
"Manage users": "管理用户",
|
"Manage users": "管理用户",
|
||||||
|
"Management": "",
|
||||||
"Media": "媒体",
|
"Media": "媒体",
|
||||||
"Media I own": "",
|
"Media I own": "",
|
||||||
"Media was edited": "媒体已编辑",
|
"Media was edited": "媒体已编辑",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "没有结果",
|
"No results for": "没有结果",
|
||||||
"No tags": "",
|
"No tags": "",
|
||||||
"No users to add": "",
|
"No users to add": "",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "播放列表",
|
"PLAYLISTS": "播放列表",
|
||||||
"PUBLISH STATE": "发布状态",
|
"PUBLISH STATE": "发布状态",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "发布于",
|
"Published on": "发布于",
|
||||||
"Recent uploads": "最近上传",
|
"Recent uploads": "最近上传",
|
||||||
"Recommended": "推荐",
|
"Recommended": "推荐",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "录制屏幕",
|
"Record Screen": "录制屏幕",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "注册",
|
"Register": "注册",
|
||||||
"Remove category": "",
|
"Remove category": "",
|
||||||
"Remove from list": "",
|
"Remove from list": "",
|
||||||
"Remove tag": "",
|
"Remove tag": "",
|
||||||
"Remove user": "",
|
"Remove user": "",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "保存",
|
"SAVE": "保存",
|
||||||
"SEARCH": "搜索",
|
"SEARCH": "搜索",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "",
|
"Select all media": "",
|
||||||
"Select publish state:": "",
|
"Select publish state:": "",
|
||||||
"Selected": "",
|
"Selected": "",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "我分享的",
|
"Shared by me": "我分享的",
|
||||||
"Shared with me": "分享给我的",
|
"Shared with me": "分享给我的",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "登录",
|
"Sign in": "登录",
|
||||||
"Sign out": "登出",
|
"Sign out": "登出",
|
||||||
"Sort By": "排序方式",
|
"Sort By": "排序方式",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "评论已成功启用",
|
"Successfully Enabled comments": "评论已成功启用",
|
||||||
"Successfully changed owner": "",
|
"Successfully changed owner": "",
|
||||||
"Successfully deleted": "删除成功",
|
"Successfully deleted": "删除成功",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "",
|
"Successfully updated": "",
|
||||||
"Successfully updated categories": "",
|
"Successfully updated categories": "",
|
||||||
"Successfully updated playlist membership": "",
|
"Successfully updated playlist membership": "",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "上传媒体",
|
"Upload media": "上传媒体",
|
||||||
"Uploads": "上传",
|
"Uploads": "上传",
|
||||||
"Users": "",
|
"Users": "",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "查看全部",
|
"VIEW ALL": "查看全部",
|
||||||
"Video": "视频",
|
"Video": "视频",
|
||||||
"View all": "查看全部",
|
"View all": "查看全部",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "欢迎",
|
"Welcome": "欢迎",
|
||||||
"You are going to copy": "您将复制",
|
"You are going to copy": "您将复制",
|
||||||
"You are going to delete": "您将删除",
|
"You are going to delete": "您将删除",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "您将禁用评论",
|
"You are going to disable comments to": "您将禁用评论",
|
||||||
"You are going to disable download for": "您将禁用下载",
|
"You are going to disable download for": "您将禁用下载",
|
||||||
"You are going to enable comments to": "您将启用评论",
|
"You are going to enable comments to": "您将启用评论",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ translation_strings = {
|
|||||||
"DELETE MEDIA": "刪除影片",
|
"DELETE MEDIA": "刪除影片",
|
||||||
"DOWNLOAD": "下載",
|
"DOWNLOAD": "下載",
|
||||||
"DURATION": "時長",
|
"DURATION": "時長",
|
||||||
|
"Delete Comments": "",
|
||||||
"Delete Media": "",
|
"Delete Media": "",
|
||||||
"Delete media": "刪除媒體",
|
"Delete media": "刪除媒體",
|
||||||
"Disable Comments": "",
|
"Disable Comments": "",
|
||||||
@@ -70,6 +71,7 @@ translation_strings = {
|
|||||||
"Failed to change owner. Please try again.": "",
|
"Failed to change owner. Please try again.": "",
|
||||||
"Failed to copy media.": "複製媒體失敗。",
|
"Failed to copy media.": "複製媒體失敗。",
|
||||||
"Failed to create playlist": "",
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete comments.": "",
|
||||||
"Failed to delete media. Please try again.": "刪除媒體失敗。請再試一次。",
|
"Failed to delete media. Please try again.": "刪除媒體失敗。請再試一次。",
|
||||||
"Failed to disable comments.": "停用留言失敗。",
|
"Failed to disable comments.": "停用留言失敗。",
|
||||||
"Failed to disable download.": "停用下載失敗。",
|
"Failed to disable download.": "停用下載失敗。",
|
||||||
@@ -101,6 +103,9 @@ translation_strings = {
|
|||||||
"Filter existing users...": "",
|
"Filter existing users...": "",
|
||||||
"Filter playlists...": "",
|
"Filter playlists...": "",
|
||||||
"Filters": "篩選器",
|
"Filters": "篩選器",
|
||||||
|
"Give users editor permissions to your media by adding them to the below list.": "",
|
||||||
|
"Give users owner permissions to your media, except for deleting the media, by adding them to the below list.": "",
|
||||||
|
"Give users viewer permissions to your media by adding them to the below list.": "",
|
||||||
"Go": "執行",
|
"Go": "執行",
|
||||||
"History": "觀看紀錄",
|
"History": "觀看紀錄",
|
||||||
"Home": "首頁",
|
"Home": "首頁",
|
||||||
@@ -121,6 +126,7 @@ translation_strings = {
|
|||||||
"Manage comments": "留言管理",
|
"Manage comments": "留言管理",
|
||||||
"Manage media": "媒體管理",
|
"Manage media": "媒體管理",
|
||||||
"Manage users": "使用者管理",
|
"Manage users": "使用者管理",
|
||||||
|
"Management": "",
|
||||||
"Media": "媒體",
|
"Media": "媒體",
|
||||||
"Media I own": "",
|
"Media I own": "",
|
||||||
"Media was edited": "媒體已更新",
|
"Media was edited": "媒體已更新",
|
||||||
@@ -137,6 +143,7 @@ translation_strings = {
|
|||||||
"No results for": "查無相關結果:",
|
"No results for": "查無相關結果:",
|
||||||
"No tags": "",
|
"No tags": "",
|
||||||
"No users to add": "",
|
"No users to add": "",
|
||||||
|
"Organization": "",
|
||||||
"PLAYLISTS": "播放清單",
|
"PLAYLISTS": "播放清單",
|
||||||
"PUBLISH STATE": "發布狀態",
|
"PUBLISH STATE": "發布狀態",
|
||||||
"Pdf": "PDF",
|
"Pdf": "PDF",
|
||||||
@@ -156,12 +163,17 @@ translation_strings = {
|
|||||||
"Published on": "發布日期為",
|
"Published on": "發布日期為",
|
||||||
"Recent uploads": "最近上傳",
|
"Recent uploads": "最近上傳",
|
||||||
"Recommended": "推薦內容",
|
"Recommended": "推薦內容",
|
||||||
|
"Record": "",
|
||||||
"Record Screen": "螢幕錄製",
|
"Record Screen": "螢幕錄製",
|
||||||
|
"Record Screen with Audio": "",
|
||||||
"Register": "註冊",
|
"Register": "註冊",
|
||||||
"Remove category": "",
|
"Remove category": "",
|
||||||
"Remove from list": "",
|
"Remove from list": "",
|
||||||
"Remove tag": "",
|
"Remove tag": "",
|
||||||
"Remove user": "",
|
"Remove user": "",
|
||||||
|
"Remove users from the list to remove editor permissions": "",
|
||||||
|
"Remove users from the list to remove owner permissions": "",
|
||||||
|
"Remove users from the list to remove viewer permissions": "",
|
||||||
"Replace": "",
|
"Replace": "",
|
||||||
"SAVE": "儲存",
|
"SAVE": "儲存",
|
||||||
"SEARCH": "搜尋",
|
"SEARCH": "搜尋",
|
||||||
@@ -178,8 +190,15 @@ translation_strings = {
|
|||||||
"Select all media": "",
|
"Select all media": "",
|
||||||
"Select publish state:": "",
|
"Select publish state:": "",
|
||||||
"Selected": "",
|
"Selected": "",
|
||||||
|
"Settings": "",
|
||||||
|
"Share with": "",
|
||||||
|
"Share with Co-Editors": "",
|
||||||
|
"Share with Co-Owners": "",
|
||||||
|
"Share with Co-Viewers": "",
|
||||||
|
"Share with Course Members": "",
|
||||||
"Shared by me": "我分享的",
|
"Shared by me": "我分享的",
|
||||||
"Shared with me": "與我分享",
|
"Shared with me": "與我分享",
|
||||||
|
"Sharing": "",
|
||||||
"Sign in": "登入",
|
"Sign in": "登入",
|
||||||
"Sign out": "登出",
|
"Sign out": "登出",
|
||||||
"Sort By": "排序方式",
|
"Sort By": "排序方式",
|
||||||
@@ -196,6 +215,7 @@ translation_strings = {
|
|||||||
"Successfully Enabled comments": "成功啟用留言",
|
"Successfully Enabled comments": "成功啟用留言",
|
||||||
"Successfully changed owner": "",
|
"Successfully changed owner": "",
|
||||||
"Successfully deleted": "成功刪除",
|
"Successfully deleted": "成功刪除",
|
||||||
|
"Successfully deleted comments": "",
|
||||||
"Successfully updated": "",
|
"Successfully updated": "",
|
||||||
"Successfully updated categories": "",
|
"Successfully updated categories": "",
|
||||||
"Successfully updated playlist membership": "",
|
"Successfully updated playlist membership": "",
|
||||||
@@ -232,6 +252,9 @@ translation_strings = {
|
|||||||
"Upload media": "上傳媒體",
|
"Upload media": "上傳媒體",
|
||||||
"Uploads": "上傳內容",
|
"Uploads": "上傳內容",
|
||||||
"Users": "",
|
"Users": "",
|
||||||
|
"Users can edit your media via: My Media > Shared with Me > particular media > Edit...": "",
|
||||||
|
"Users can manage your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
|
"Users can view your media via: My Media > Shared with Me > particular media > ...": "",
|
||||||
"VIEW ALL": "查看全部",
|
"VIEW ALL": "查看全部",
|
||||||
"Video": "影片",
|
"Video": "影片",
|
||||||
"View all": "瀏覽全部",
|
"View all": "瀏覽全部",
|
||||||
@@ -240,6 +263,7 @@ translation_strings = {
|
|||||||
"Welcome": "歡迎",
|
"Welcome": "歡迎",
|
||||||
"You are going to copy": "您即將複製",
|
"You are going to copy": "您即將複製",
|
||||||
"You are going to delete": "您即將刪除",
|
"You are going to delete": "您即將刪除",
|
||||||
|
"You are going to delete all comments from": "",
|
||||||
"You are going to disable comments to": "您即將停用留言",
|
"You are going to disable comments to": "您即將停用留言",
|
||||||
"You are going to disable download for": "您即將停用下載",
|
"You are going to disable download for": "您即將停用下載",
|
||||||
"You are going to enable comments to": "您即將啟用留言",
|
"You are going to enable comments to": "您即將啟用留言",
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ class Media(models.Model):
|
|||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for cat in self.category.all():
|
for cat in self.category.all():
|
||||||
ret.append({"title": cat.title, "url": cat.get_absolute_url()})
|
ret.append({"title": cat.title, "url": cat.get_absolute_url(), "is_lms_course": cat.is_lms_course})
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ class CategorySerializer(serializers.ModelSerializer):
|
|||||||
"media_count",
|
"media_count",
|
||||||
"user",
|
"user",
|
||||||
"thumbnail_url",
|
"thumbnail_url",
|
||||||
|
"is_lms_course",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,14 @@ class CategoryListContributor(APIView):
|
|||||||
"""List categories where user has contributor access"""
|
"""List categories where user has contributor access"""
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
manual_parameters=[],
|
manual_parameters=[
|
||||||
|
openapi.Parameter(
|
||||||
|
name='lms_courses_only',
|
||||||
|
type=openapi.TYPE_BOOLEAN,
|
||||||
|
in_=openapi.IN_QUERY,
|
||||||
|
description='Filter to show only LMS courses (categories with is_lms_course=True)',
|
||||||
|
),
|
||||||
|
],
|
||||||
tags=['Categories'],
|
tags=['Categories'],
|
||||||
operation_summary='Lists Categories for Contributors',
|
operation_summary='Lists Categories for Contributors',
|
||||||
operation_description='Lists all categories where the user has contributor access',
|
operation_description='Lists all categories where the user has contributor access',
|
||||||
@@ -61,15 +68,17 @@ class CategoryListContributor(APIView):
|
|||||||
|
|
||||||
categories = Category.objects.none()
|
categories = Category.objects.none()
|
||||||
|
|
||||||
# Get global/public categories (non-RBAC)
|
# Filter for LMS courses only if requested
|
||||||
public_categories = Category.objects.filter(is_rbac_category=False).prefetch_related("user")
|
lms_courses_only = request.GET.get('lms_courses_only', '').lower() in ['true', '1', 'yes']
|
||||||
|
if lms_courses_only:
|
||||||
|
categories = categories.filter(is_lms_course=True)
|
||||||
|
else:
|
||||||
|
categories = Category.objects.filter(is_rbac_category=False).prefetch_related("user")
|
||||||
|
|
||||||
# Get RBAC categories where user has contributor access
|
# Get RBAC categories where user has contributor access
|
||||||
if getattr(settings, 'USE_RBAC', False):
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
rbac_categories = request.user.get_rbac_categories_as_contributor()
|
rbac_categories = request.user.get_rbac_categories_as_contributor()
|
||||||
categories = public_categories.union(rbac_categories)
|
categories = categories.union(rbac_categories)
|
||||||
else:
|
|
||||||
categories = public_categories
|
|
||||||
|
|
||||||
categories = categories.order_by("title")
|
categories = categories.order_by("title")
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from ..methods import (
|
|||||||
)
|
)
|
||||||
from ..models import (
|
from ..models import (
|
||||||
Category,
|
Category,
|
||||||
|
Comment,
|
||||||
EncodeProfile,
|
EncodeProfile,
|
||||||
Media,
|
Media,
|
||||||
MediaPermission,
|
MediaPermission,
|
||||||
@@ -164,7 +165,14 @@ class MediaList(APIView):
|
|||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
media = Media.objects.none()
|
media = Media.objects.none()
|
||||||
else:
|
else:
|
||||||
media = Media.objects.filter(permissions__owner_user=self.request.user).prefetch_related("user", "tags").distinct()
|
base_queryset = Media.objects.prefetch_related("user", "tags")
|
||||||
|
conditions = Q(permissions__owner_user=self.request.user)
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
rbac_categories = request.user.get_rbac_categories_as_contributor()
|
||||||
|
conditions |= Q(category__in=rbac_categories, user=self.request.user)
|
||||||
|
|
||||||
|
media = base_queryset.filter(conditions).distinct()
|
||||||
elif show_param == "shared_with_me":
|
elif show_param == "shared_with_me":
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
media = Media.objects.none()
|
media = Media.objects.none()
|
||||||
@@ -178,7 +186,7 @@ class MediaList(APIView):
|
|||||||
rbac_categories = request.user.get_rbac_categories_as_member()
|
rbac_categories = request.user.get_rbac_categories_as_member()
|
||||||
conditions |= Q(category__in=rbac_categories)
|
conditions |= Q(category__in=rbac_categories)
|
||||||
|
|
||||||
media = base_queryset.filter(conditions).distinct()
|
media = base_queryset.filter(conditions).exclude(user=request.user).distinct()
|
||||||
elif author_param:
|
elif author_param:
|
||||||
user_queryset = User.objects.all()
|
user_queryset = User.objects.all()
|
||||||
user = get_object_or_404(user_queryset, username=author_param)
|
user = get_object_or_404(user_queryset, username=author_param)
|
||||||
@@ -295,6 +303,7 @@ class MediaBulkUserActions(APIView):
|
|||||||
enum=[
|
enum=[
|
||||||
"enable_comments",
|
"enable_comments",
|
||||||
"disable_comments",
|
"disable_comments",
|
||||||
|
"delete_comments",
|
||||||
"delete_media",
|
"delete_media",
|
||||||
"enable_download",
|
"enable_download",
|
||||||
"disable_download",
|
"disable_download",
|
||||||
@@ -379,6 +388,10 @@ class MediaBulkUserActions(APIView):
|
|||||||
media.update(enable_comments=False)
|
media.update(enable_comments=False)
|
||||||
return Response({"detail": f"Comments disabled for {media.count()} media items"})
|
return Response({"detail": f"Comments disabled for {media.count()} media items"})
|
||||||
|
|
||||||
|
elif action == "delete_comments":
|
||||||
|
deleted_count, _ = Comment.objects.filter(media__in=media).delete()
|
||||||
|
return Response({"detail": f"{deleted_count} comments deleted"})
|
||||||
|
|
||||||
elif action == "delete_media":
|
elif action == "delete_media":
|
||||||
count = media.count()
|
count = media.count()
|
||||||
media.delete()
|
media.delete()
|
||||||
@@ -492,8 +505,9 @@ class MediaBulkUserActions(APIView):
|
|||||||
|
|
||||||
users = (
|
users = (
|
||||||
MediaPermission.objects.filter(media__in=media, permission=ownership_type)
|
MediaPermission.objects.filter(media__in=media, permission=ownership_type)
|
||||||
|
.exclude(user=request.user)
|
||||||
.values('user__name', 'user__username')
|
.values('user__name', 'user__username')
|
||||||
.annotate(media_count=Count('media', distinct=True))
|
.annotate(media_count=Count('media'))
|
||||||
.filter(media_count=media_count)
|
.filter(media_count=media_count)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -574,12 +588,31 @@ class MediaBulkUserActions(APIView):
|
|||||||
|
|
||||||
elif action == "add_to_category":
|
elif action == "add_to_category":
|
||||||
category_uids = request.data.get('category_uids', [])
|
category_uids = request.data.get('category_uids', [])
|
||||||
if not category_uids:
|
lti_context_id = request.data.get('lti_context_id')
|
||||||
return Response({"detail": "category_uids is required for add_to_category action"}, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
if not category_uids and not lti_context_id:
|
||||||
|
return Response({"detail": "category_uids or lti_context_id is required for add_to_category action"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
categories = Category.objects.none()
|
||||||
|
|
||||||
|
# Prioritize category_uids
|
||||||
|
if category_uids:
|
||||||
|
categories = Category.objects.filter(uid__in=category_uids)
|
||||||
|
elif lti_context_id:
|
||||||
|
# Filter categories by lti_context_id and ensure they ARE RBAC categories
|
||||||
|
potential_categories = Category.objects.filter(lti_context_id=lti_context_id, is_rbac_category=True)
|
||||||
|
|
||||||
|
# Check user access (must have contributor access)
|
||||||
|
valid_category_ids = []
|
||||||
|
for cat in potential_categories:
|
||||||
|
if request.user.has_contributor_access_to_category(cat):
|
||||||
|
valid_category_ids.append(cat.id)
|
||||||
|
|
||||||
|
if valid_category_ids:
|
||||||
|
categories = Category.objects.filter(id__in=valid_category_ids)
|
||||||
|
|
||||||
categories = Category.objects.filter(uid__in=category_uids)
|
|
||||||
if not categories:
|
if not categories:
|
||||||
return Response({"detail": "No matching categories found"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"detail": "No matching categories found or access denied"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
added_count = 0
|
added_count = 0
|
||||||
for category in categories:
|
for category in categories:
|
||||||
@@ -697,12 +730,9 @@ class MediaDetail(APIView):
|
|||||||
return media
|
return media
|
||||||
|
|
||||||
serializer = SingleMediaSerializer(media, context={"request": request})
|
serializer = SingleMediaSerializer(media, context={"request": request})
|
||||||
if media.state == "private":
|
related_media = show_related_media(media, request=request, limit=100)
|
||||||
related_media = []
|
related_media_serializer = MediaSerializer(related_media, many=True, context={"request": request})
|
||||||
else:
|
related_media = related_media_serializer.data
|
||||||
related_media = show_related_media(media, request=request, limit=100)
|
|
||||||
related_media_serializer = MediaSerializer(related_media, many=True, context={"request": request})
|
|
||||||
related_media = related_media_serializer.data
|
|
||||||
ret = serializer.data
|
ret = serializer.data
|
||||||
|
|
||||||
# update rattings info with user specific ratings
|
# update rattings info with user specific ratings
|
||||||
|
|||||||
@@ -350,13 +350,13 @@ def publish_media(request):
|
|||||||
return HttpResponseRedirect(media.get_absolute_url())
|
return HttpResponseRedirect(media.get_absolute_url())
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = MediaPublishForm(request.user, request.POST, request.FILES, instance=media)
|
form = MediaPublishForm(request.user, request.POST, request.FILES, instance=media, request=request)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
media = form.save()
|
media = form.save()
|
||||||
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Media was edited"))
|
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Media was edited"))
|
||||||
return HttpResponseRedirect(media.get_absolute_url())
|
return HttpResponseRedirect(media.get_absolute_url())
|
||||||
else:
|
else:
|
||||||
form = MediaPublishForm(request.user, instance=media)
|
form = MediaPublishForm(request.user, instance=media, request=request)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import json
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
from .models import Category
|
||||||
|
|
||||||
|
|
||||||
class CategoryModalWidget(forms.SelectMultiple):
|
class CategoryModalWidget(forms.SelectMultiple):
|
||||||
"""Two-panel category selector with modal"""
|
"""Two-panel category selector with modal"""
|
||||||
@@ -12,28 +14,42 @@ class CategoryModalWidget(forms.SelectMultiple):
|
|||||||
js = ('js/category_modal.js',)
|
js = ('js/category_modal.js',)
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, renderer=None):
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
|
is_lms_mode = getattr(self, 'is_lms_mode', False)
|
||||||
|
|
||||||
# Get all categories as JSON
|
# Get all categories as JSON
|
||||||
categories = []
|
categories = []
|
||||||
for opt_value, opt_label in self.choices:
|
for opt_value, opt_label in self.choices:
|
||||||
if opt_value: # Skip empty choice
|
if opt_value: # Skip empty choice
|
||||||
categories.append({'id': str(opt_value), 'title': str(opt_label)})
|
# Extract the actual ID value from ModelChoiceIteratorValue if needed
|
||||||
|
category_id = opt_value.value if hasattr(opt_value, 'value') else opt_value
|
||||||
|
|
||||||
|
# Get is_lms_course info from the Category object
|
||||||
|
try:
|
||||||
|
cat_obj = Category.objects.get(id=category_id)
|
||||||
|
categories.append({'id': str(category_id), 'title': str(opt_label), 'is_lms_course': cat_obj.is_lms_course})
|
||||||
|
except Category.DoesNotExist:
|
||||||
|
categories.append({'id': str(category_id), 'title': str(opt_label), 'is_lms_course': False})
|
||||||
|
|
||||||
all_categories_json = json.dumps(categories)
|
all_categories_json = json.dumps(categories)
|
||||||
selected_ids_json = json.dumps([str(v) for v in (value or [])])
|
selected_ids_json = json.dumps([str(v) for v in (value or [])])
|
||||||
|
lms_mode_json = json.dumps(is_lms_mode)
|
||||||
|
|
||||||
|
search_placeholder = "Search courses..." if is_lms_mode else "Search categories..."
|
||||||
|
selected_header = "Selected Courses" if is_lms_mode else "Selected Categories"
|
||||||
|
|
||||||
html = f'''<div class="category-widget" data-name="{name}">
|
html = f'''<div class="category-widget" data-name="{name}">
|
||||||
<div class="category-content">
|
<div class="category-content">
|
||||||
<div class="category-panel">
|
<div class="category-panel">
|
||||||
<input type="text" class="category-search" placeholder="Search categories...">
|
<input type="text" class="category-search" placeholder="{search_placeholder}">
|
||||||
<div class="category-list scrollable" data-panel="left"></div>
|
<div class="category-list scrollable" data-panel="left"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="category-panel">
|
<div class="category-panel">
|
||||||
<h3>Selected Categories</h3>
|
<h3>{selected_header}</h3>
|
||||||
<div class="category-list scrollable" data-panel="right"></div>
|
<div class="category-list scrollable" data-panel="right"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-inputs"></div>
|
<div class="hidden-inputs"></div>
|
||||||
<script type="application/json" class="category-data">{{"all":{all_categories_json},"selected":{selected_ids_json}}}</script>
|
<script type="application/json" class="category-data">{{"all":{all_categories_json},"selected":{selected_ids_json},"lms_mode":{lms_mode_json}}}</script>
|
||||||
</div>'''
|
</div>'''
|
||||||
|
|
||||||
return mark_safe(html)
|
return mark_safe(html)
|
||||||
|
|||||||
34
frontend-tools/video-js/examples/full-screen-video.html
Normal file
34
frontend-tools/video-js/examples/full-screen-video.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" style="height: 100%">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Embedded Video - Full Screen</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe
|
||||||
|
src="https://demo.mediacms.io/embed?m=zK2nirNLC"
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
max-width: calc(100vh * 16 / 9);
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
border: 0;
|
||||||
|
"
|
||||||
|
allowfullscreen
|
||||||
|
></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -204,6 +204,54 @@ class SeekIndicator extends Component {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
textEl.textContent = 'Pause';
|
textEl.textContent = 'Pause';
|
||||||
|
} else if (direction === 'copy-url') {
|
||||||
|
iconEl.innerHTML = `
|
||||||
|
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||||
|
<div style="
|
||||||
|
width: ${circleSize};
|
||||||
|
height: ${circleSize};
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
">
|
||||||
|
<svg viewBox="0 0 24 24" width="${iconSize}" height="${iconSize}" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
textEl.textContent = '';
|
||||||
|
} else if (direction === 'copy-embed') {
|
||||||
|
iconEl.innerHTML = `
|
||||||
|
<div style="display: flex; align-items: center; justify-content: center; animation: youtubeSeekPulse 0.3s ease-out;">
|
||||||
|
<div style="
|
||||||
|
width: ${circleSize};
|
||||||
|
height: ${circleSize};
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
">
|
||||||
|
<svg viewBox="0 0 24 24" width="${iconSize}" height="${iconSize}" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style="filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));">
|
||||||
|
<path d="M16 18l6-6-6-6"/>
|
||||||
|
<path d="M8 6l-6 6 6 6"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
textEl.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear any text content in the text element
|
// Clear any text content in the text element
|
||||||
@@ -239,6 +287,11 @@ class SeekIndicator extends Component {
|
|||||||
this.showTimeout = setTimeout(() => {
|
this.showTimeout = setTimeout(() => {
|
||||||
this.hide();
|
this.hide();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
} else if (direction === 'copy-url' || direction === 'copy-embed') {
|
||||||
|
// Copy operations: 500ms (same as play/pause)
|
||||||
|
this.showTimeout = setTimeout(() => {
|
||||||
|
this.hide();
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,22 @@ class EmbedInfoOverlay extends Component {
|
|||||||
this.authorThumbnail = options.authorThumbnail || '';
|
this.authorThumbnail = options.authorThumbnail || '';
|
||||||
this.videoTitle = options.videoTitle || 'Video';
|
this.videoTitle = options.videoTitle || 'Video';
|
||||||
this.videoUrl = options.videoUrl || '';
|
this.videoUrl = options.videoUrl || '';
|
||||||
|
this.showTitle = options.showTitle !== undefined ? options.showTitle : true;
|
||||||
|
this.showRelated = options.showRelated !== undefined ? options.showRelated : true;
|
||||||
|
this.showUserAvatar = options.showUserAvatar !== undefined ? options.showUserAvatar : true;
|
||||||
|
this.linkTitle = options.linkTitle !== undefined ? options.linkTitle : true;
|
||||||
|
|
||||||
// Initialize after player is ready
|
// Initialize after player is ready
|
||||||
this.player().ready(() => {
|
this.player().ready(() => {
|
||||||
this.createOverlay();
|
if (this.showTitle) {
|
||||||
|
this.createOverlay();
|
||||||
|
} else {
|
||||||
|
// Hide overlay element if showTitle is false
|
||||||
|
const overlay = this.el();
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
overlay.style.opacity = '0';
|
||||||
|
overlay.style.visibility = 'hidden';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +61,7 @@ class EmbedInfoOverlay extends Component {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Create avatar container
|
// Create avatar container
|
||||||
if (this.authorThumbnail) {
|
if (this.authorThumbnail && this.showUserAvatar) {
|
||||||
const avatarContainer = document.createElement('div');
|
const avatarContainer = document.createElement('div');
|
||||||
avatarContainer.className = 'embed-avatar-container';
|
avatarContainer.className = 'embed-avatar-container';
|
||||||
avatarContainer.style.cssText = `
|
avatarContainer.style.cssText = `
|
||||||
@@ -125,7 +137,7 @@ class EmbedInfoOverlay extends Component {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (this.videoUrl) {
|
if (this.videoUrl && this.linkTitle) {
|
||||||
const titleLink = document.createElement('a');
|
const titleLink = document.createElement('a');
|
||||||
titleLink.href = this.videoUrl;
|
titleLink.href = this.videoUrl;
|
||||||
titleLink.target = '_blank';
|
titleLink.target = '_blank';
|
||||||
@@ -186,10 +198,16 @@ class EmbedInfoOverlay extends Component {
|
|||||||
const player = this.player();
|
const player = this.player();
|
||||||
const overlay = this.el();
|
const overlay = this.el();
|
||||||
|
|
||||||
|
// If showTitle is false, ensure overlay is hidden
|
||||||
|
if (!this.showTitle) {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
overlay.style.opacity = '0';
|
||||||
|
overlay.style.visibility = 'hidden';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Sync overlay visibility with control bar visibility
|
// Sync overlay visibility with control bar visibility
|
||||||
const updateOverlayVisibility = () => {
|
const updateOverlayVisibility = () => {
|
||||||
const controlBar = player.getChild('controlBar');
|
|
||||||
|
|
||||||
if (!player.hasStarted()) {
|
if (!player.hasStarted()) {
|
||||||
// Show overlay when video hasn't started (poster is showing) - like before
|
// Show overlay when video hasn't started (poster is showing) - like before
|
||||||
overlay.style.opacity = '1';
|
overlay.style.opacity = '1';
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.video-context-menu {
|
||||||
|
position: fixed;
|
||||||
|
background-color: #282828;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 0;
|
||||||
|
min-width: 240px;
|
||||||
|
z-index: 10000;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-context-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-context-menu-item:hover {
|
||||||
|
background-color: #3d3d3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-context-menu-item:active {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-context-menu-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
stroke: currentColor;
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-context-menu-item span {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import './VideoContextMenu.css';
|
||||||
|
|
||||||
|
function VideoContextMenu({ visible, position, onClose, onCopyVideoUrl, onCopyVideoUrlAtTime, onCopyEmbedCode }) {
|
||||||
|
const menuRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && menuRef.current) {
|
||||||
|
// Position the menu
|
||||||
|
menuRef.current.style.left = `${position.x}px`;
|
||||||
|
menuRef.current.style.top = `${position.y}px`;
|
||||||
|
|
||||||
|
// Adjust if menu goes off screen
|
||||||
|
const rect = menuRef.current.getBoundingClientRect();
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
|
||||||
|
if (rect.right > windowWidth) {
|
||||||
|
menuRef.current.style.left = `${position.x - rect.width}px`;
|
||||||
|
}
|
||||||
|
if (rect.bottom > windowHeight) {
|
||||||
|
menuRef.current.style.top = `${position.y - rect.height}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [visible, position]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (e) => {
|
||||||
|
if (visible && menuRef.current && !menuRef.current.contains(e.target)) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEscape = (e) => {
|
||||||
|
if (e.key === 'Escape' && visible) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
// Use capture phase to catch events earlier, before they can be stopped
|
||||||
|
// Listen to both mousedown and click to ensure we catch all clicks
|
||||||
|
document.addEventListener('mousedown', handleClickOutside, true);
|
||||||
|
document.addEventListener('click', handleClickOutside, true);
|
||||||
|
document.addEventListener('keydown', handleEscape);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside, true);
|
||||||
|
document.removeEventListener('click', handleClickOutside, true);
|
||||||
|
document.removeEventListener('keydown', handleEscape);
|
||||||
|
};
|
||||||
|
}, [visible, onClose]);
|
||||||
|
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={menuRef} className="video-context-menu" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="video-context-menu-item" onClick={onCopyVideoUrl}>
|
||||||
|
<svg className="video-context-menu-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span>Copy video URL</span>
|
||||||
|
</div>
|
||||||
|
<div className="video-context-menu-item" onClick={onCopyVideoUrlAtTime}>
|
||||||
|
<svg className="video-context-menu-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span>Copy video URL at current time</span>
|
||||||
|
</div>
|
||||||
|
<div className="video-context-menu-item" onClick={onCopyEmbedCode}>
|
||||||
|
<svg className="video-context-menu-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16 18l6-6-6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M8 6l-6 6 6 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span>Copy embed code</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoContextMenu;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useMemo } from 'react';
|
import React, { useEffect, useRef, useMemo, useState, useCallback } from 'react';
|
||||||
import videojs from 'video.js';
|
import videojs from 'video.js';
|
||||||
import 'video.js/dist/video-js.css';
|
import 'video.js/dist/video-js.css';
|
||||||
import '../../styles/embed.css';
|
import '../../styles/embed.css';
|
||||||
@@ -17,6 +17,7 @@ import CustomRemainingTime from '../controls/CustomRemainingTime';
|
|||||||
import CustomChaptersOverlay from '../controls/CustomChaptersOverlay';
|
import CustomChaptersOverlay from '../controls/CustomChaptersOverlay';
|
||||||
import CustomSettingsMenu from '../controls/CustomSettingsMenu';
|
import CustomSettingsMenu from '../controls/CustomSettingsMenu';
|
||||||
import SeekIndicator from '../controls/SeekIndicator';
|
import SeekIndicator from '../controls/SeekIndicator';
|
||||||
|
import VideoContextMenu from '../overlays/VideoContextMenu';
|
||||||
import UserPreferences from '../../utils/UserPreferences';
|
import UserPreferences from '../../utils/UserPreferences';
|
||||||
import PlayerConfig from '../../config/playerConfig';
|
import PlayerConfig from '../../config/playerConfig';
|
||||||
import { AutoplayHandler } from '../../utils/AutoplayHandler';
|
import { AutoplayHandler } from '../../utils/AutoplayHandler';
|
||||||
@@ -169,7 +170,7 @@ const enableStandardButtonTooltips = (player) => {
|
|||||||
}, 500); // Delay to ensure all components are ready
|
}, 500); // Delay to ensure all components are ready
|
||||||
};
|
};
|
||||||
|
|
||||||
function VideoJSPlayer({ videoId = 'default-video' }) {
|
function VideoJSPlayer({ videoId = 'default-video', showTitle = true, showRelated = true, showUserAvatar = true, linkTitle = true, urlTimestamp = null }) {
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
const playerRef = useRef(null); // Track the player instance
|
const playerRef = useRef(null); // Track the player instance
|
||||||
const userPreferences = useRef(new UserPreferences()); // User preferences instance
|
const userPreferences = useRef(new UserPreferences()); // User preferences instance
|
||||||
@@ -177,25 +178,17 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const keyboardHandler = useRef(null); // Keyboard handler instance
|
const keyboardHandler = useRef(null); // Keyboard handler instance
|
||||||
const playbackEventHandler = useRef(null); // Playback event handler instance
|
const playbackEventHandler = useRef(null); // Playback event handler instance
|
||||||
|
|
||||||
|
// Context menu state
|
||||||
|
const [contextMenuVisible, setContextMenuVisible] = useState(false);
|
||||||
|
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
// Check if this is an embed player (disable next video and autoplay features)
|
// Check if this is an embed player (disable next video and autoplay features)
|
||||||
const isEmbedPlayer = videoId === 'video-embed';
|
const isEmbedPlayer = videoId === 'video-embed';
|
||||||
|
|
||||||
// Utility function to detect touch devices
|
|
||||||
const isTouchDevice = useMemo(() => {
|
|
||||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Utility function to detect iOS devices
|
|
||||||
const isIOS = useMemo(() => {
|
|
||||||
return (
|
|
||||||
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
||||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Environment-based development mode configuration
|
// Environment-based development mode configuration
|
||||||
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
|
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
|
||||||
// Safely access window.MEDIA_DATA with fallback using useMemo
|
|
||||||
|
// Read options from window.MEDIA_DATA if available (for consistency with embed logic)
|
||||||
const mediaData = useMemo(
|
const mediaData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
typeof window !== 'undefined' && window.MEDIA_DATA
|
typeof window !== 'undefined' && window.MEDIA_DATA
|
||||||
@@ -214,12 +207,37 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
},
|
},
|
||||||
siteUrl: 'https://deic.mediacms.io',
|
siteUrl: 'https://deic.mediacms.io',
|
||||||
nextLink: 'https://deic.mediacms.io/view?m=elygiagorgechania',
|
nextLink: 'https://deic.mediacms.io/view?m=elygiagorgechania',
|
||||||
urlAutoplay: true,
|
|
||||||
urlMuted: false,
|
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Helper to get effective value (prop or MEDIA_DATA or default)
|
||||||
|
const getOption = (propKey, mediaDataKey, defaultValue) => {
|
||||||
|
if (isEmbedPlayer) {
|
||||||
|
if (mediaData[mediaDataKey] !== undefined) return mediaData[mediaDataKey];
|
||||||
|
}
|
||||||
|
return propKey !== undefined ? propKey : defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalShowTitle = getOption(showTitle, 'showTitle', true);
|
||||||
|
const finalShowRelated = getOption(showRelated, 'showRelated', true);
|
||||||
|
const finalShowUserAvatar = getOption(showUserAvatar, 'showUserAvatar', true);
|
||||||
|
const finalLinkTitle = getOption(linkTitle, 'linkTitle', true);
|
||||||
|
const finalTimestamp = getOption(urlTimestamp, 'urlTimestamp', null);
|
||||||
|
|
||||||
|
// Utility function to detect touch devices
|
||||||
|
const isTouchDevice = useMemo(() => {
|
||||||
|
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Utility function to detect iOS devices
|
||||||
|
const isIOS = useMemo(() => {
|
||||||
|
return (
|
||||||
|
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
||||||
|
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Define chapters as JSON object
|
// Define chapters as JSON object
|
||||||
// Note: The sample-chapters.vtt file is no longer needed as chapters are now loaded from this JSON
|
// Note: The sample-chapters.vtt file is no longer needed as chapters are now loaded from this JSON
|
||||||
// CONDITIONAL LOGIC:
|
// CONDITIONAL LOGIC:
|
||||||
@@ -531,8 +549,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
isPlayList: mediaData?.isPlayList,
|
isPlayList: mediaData?.isPlayList,
|
||||||
related_media: mediaData.data?.related_media || [],
|
related_media: mediaData.data?.related_media || [],
|
||||||
nextLink: mediaData?.nextLink || null,
|
nextLink: mediaData?.nextLink || null,
|
||||||
urlAutoplay: mediaData?.urlAutoplay || true,
|
|
||||||
urlMuted: mediaData?.urlMuted || false,
|
|
||||||
sources: getVideoSources(),
|
sources: getVideoSources(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -738,6 +754,212 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Context menu handlers
|
||||||
|
const handleContextMenu = useCallback((e) => {
|
||||||
|
// Only handle if clicking on video player area
|
||||||
|
const target = e.target;
|
||||||
|
const isVideoPlayerArea =
|
||||||
|
target.closest('.video-js') ||
|
||||||
|
target.classList.contains('vjs-tech') ||
|
||||||
|
target.tagName === 'VIDEO' ||
|
||||||
|
target.closest('video');
|
||||||
|
|
||||||
|
if (isVideoPlayerArea) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
setContextMenuPosition({ x: e.clientX, y: e.clientY });
|
||||||
|
setContextMenuVisible(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeContextMenu = () => {
|
||||||
|
setContextMenuVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get media ID
|
||||||
|
const getMediaId = () => {
|
||||||
|
if (typeof window !== 'undefined' && window.MEDIA_DATA?.data?.friendly_token) {
|
||||||
|
return window.MEDIA_DATA.data.friendly_token;
|
||||||
|
}
|
||||||
|
if (mediaData?.data?.friendly_token) {
|
||||||
|
return mediaData.data.friendly_token;
|
||||||
|
}
|
||||||
|
// Try to get from URL (works for both main page and embed page)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const mediaIdFromUrl = urlParams.get('m');
|
||||||
|
if (mediaIdFromUrl) {
|
||||||
|
return mediaIdFromUrl;
|
||||||
|
}
|
||||||
|
// Also check if we're on an embed page with media ID in path
|
||||||
|
const pathMatch = window.location.pathname.match(/\/embed\/([^/?]+)/);
|
||||||
|
if (pathMatch) {
|
||||||
|
return pathMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentVideo.id || 'default-video';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get base origin URL (handles embed mode)
|
||||||
|
const getBaseOrigin = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// In embed mode, try to get origin from parent window if possible
|
||||||
|
// Otherwise use current window origin
|
||||||
|
try {
|
||||||
|
// Check if we're in an iframe and can access parent
|
||||||
|
if (window.parent !== window && window.parent.location.origin) {
|
||||||
|
return window.parent.location.origin;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Cross-origin iframe, use current origin
|
||||||
|
}
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
return mediaData.siteUrl || 'https://deic.mediacms.io';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get embed URL
|
||||||
|
const getEmbedUrl = () => {
|
||||||
|
const mediaId = getMediaId();
|
||||||
|
const origin = getBaseOrigin();
|
||||||
|
|
||||||
|
// Try to get embed URL from config or construct it
|
||||||
|
if (typeof window !== 'undefined' && window.MediaCMS?.config?.url?.embed) {
|
||||||
|
return window.MediaCMS.config.url.embed + mediaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: construct embed URL (check if current URL is embed format)
|
||||||
|
if (typeof window !== 'undefined' && window.location.pathname.includes('/embed')) {
|
||||||
|
// If we're already on an embed page, use current URL format
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
currentUrl.searchParams.set('m', mediaId);
|
||||||
|
return currentUrl.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default embed URL format
|
||||||
|
return `${origin}/embed?m=${mediaId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy video URL to clipboard
|
||||||
|
const handleCopyVideoUrl = async () => {
|
||||||
|
const mediaId = getMediaId();
|
||||||
|
const origin = getBaseOrigin();
|
||||||
|
const videoUrl = `${origin}/view?m=${mediaId}`;
|
||||||
|
|
||||||
|
// Show copy icon
|
||||||
|
if (customComponents.current?.seekIndicator) {
|
||||||
|
customComponents.current.seekIndicator.show('copy-url');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(videoUrl);
|
||||||
|
closeContextMenu();
|
||||||
|
// You can add a notification here if needed
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy video URL:', err);
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = videoUrl;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
closeContextMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy video URL at current time to clipboard
|
||||||
|
const handleCopyVideoUrlAtTime = async () => {
|
||||||
|
if (!playerRef.current) {
|
||||||
|
closeContextMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTime = Math.floor(playerRef.current.currentTime() || 0);
|
||||||
|
const mediaId = getMediaId();
|
||||||
|
const origin = getBaseOrigin();
|
||||||
|
const videoUrl = `${origin}/view?m=${mediaId}&t=${currentTime}`;
|
||||||
|
|
||||||
|
// Show copy icon
|
||||||
|
if (customComponents.current?.seekIndicator) {
|
||||||
|
customComponents.current.seekIndicator.show('copy-url');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(videoUrl);
|
||||||
|
closeContextMenu();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy video URL at time:', err);
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = videoUrl;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
closeContextMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy embed code to clipboard
|
||||||
|
const handleCopyEmbedCode = async () => {
|
||||||
|
const embedUrl = getEmbedUrl();
|
||||||
|
const embedCode = `<iframe width="560" height="315" src="${embedUrl}" frameborder="0" allowfullscreen></iframe>`;
|
||||||
|
|
||||||
|
// Show copy embed icon
|
||||||
|
if (customComponents.current?.seekIndicator) {
|
||||||
|
customComponents.current.seekIndicator.show('copy-embed');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(embedCode);
|
||||||
|
closeContextMenu();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy embed code:', err);
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = embedCode;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
closeContextMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add context menu handler directly to video element and document (works before and after Video.js initialization)
|
||||||
|
useEffect(() => {
|
||||||
|
const videoElement = videoRef.current;
|
||||||
|
|
||||||
|
// Attach to document with capture to catch all contextmenu events, then filter
|
||||||
|
const documentHandler = (e) => {
|
||||||
|
// Check if the event originated from within the video player
|
||||||
|
const target = e.target;
|
||||||
|
const playerWrapper =
|
||||||
|
videoElement?.closest('.video-js') || document.querySelector(`#${videoId}`)?.closest('.video-js');
|
||||||
|
|
||||||
|
if (playerWrapper && (playerWrapper.contains(target) || target === playerWrapper)) {
|
||||||
|
handleContextMenu(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use capture phase on document to catch before anything else
|
||||||
|
document.addEventListener('contextmenu', documentHandler, true);
|
||||||
|
|
||||||
|
// Also attach directly to video element
|
||||||
|
if (videoElement) {
|
||||||
|
videoElement.addEventListener('contextmenu', handleContextMenu, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('contextmenu', documentHandler, true);
|
||||||
|
if (videoElement) {
|
||||||
|
videoElement.removeEventListener('contextmenu', handleContextMenu, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [handleContextMenu, videoId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only initialize if we don't already have a player and element exists
|
// Only initialize if we don't already have a player and element exists
|
||||||
if (videoRef.current && !playerRef.current) {
|
if (videoRef.current && !playerRef.current) {
|
||||||
@@ -1078,6 +1300,9 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
currentVideo,
|
currentVideo,
|
||||||
relatedVideos,
|
relatedVideos,
|
||||||
goToNextVideo,
|
goToNextVideo,
|
||||||
|
showRelated: finalShowRelated,
|
||||||
|
showUserAvatar: finalShowUserAvatar,
|
||||||
|
linkTitle: finalLinkTitle,
|
||||||
});
|
});
|
||||||
customComponents.current.endScreenHandler = endScreenHandler; // Store for cleanup
|
customComponents.current.endScreenHandler = endScreenHandler; // Store for cleanup
|
||||||
|
|
||||||
@@ -1098,8 +1323,8 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle URL timestamp parameter
|
// Handle URL timestamp parameter
|
||||||
if (mediaData.urlTimestamp !== null && mediaData.urlTimestamp >= 0) {
|
if (finalTimestamp !== null && finalTimestamp >= 0) {
|
||||||
const timestamp = mediaData.urlTimestamp;
|
const timestamp = finalTimestamp;
|
||||||
|
|
||||||
// Wait for video metadata to be loaded before seeking
|
// Wait for video metadata to be loaded before seeking
|
||||||
if (playerRef.current.readyState() >= 1) {
|
if (playerRef.current.readyState() >= 1) {
|
||||||
@@ -1997,6 +2222,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
authorThumbnail: currentVideo.author_thumbnail,
|
authorThumbnail: currentVideo.author_thumbnail,
|
||||||
videoTitle: currentVideo.title,
|
videoTitle: currentVideo.title,
|
||||||
videoUrl: currentVideo.url,
|
videoUrl: currentVideo.url,
|
||||||
|
showTitle: finalShowTitle,
|
||||||
|
showRelated: finalShowRelated,
|
||||||
|
showUserAvatar: finalShowUserAvatar,
|
||||||
|
linkTitle: finalLinkTitle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// END: Add Embed Info Overlay Component
|
// END: Add Embed Info Overlay Component
|
||||||
@@ -2083,52 +2312,113 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
// Make the video element focusable
|
// Make the video element focusable
|
||||||
const videoElement = playerRef.current.el();
|
const videoElement = playerRef.current.el();
|
||||||
videoElement.setAttribute('tabindex', '0');
|
videoElement.setAttribute('tabindex', '0');
|
||||||
videoElement.focus();
|
|
||||||
|
if (!isEmbedPlayer) {
|
||||||
|
videoElement.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add context menu (right-click) handler to the player wrapper and video element
|
||||||
|
// Attach to player wrapper (this catches all clicks on the player)
|
||||||
|
videoElement.addEventListener('contextmenu', handleContextMenu, true);
|
||||||
|
|
||||||
|
// Also try to attach to the actual video tech element
|
||||||
|
const attachContextMenu = () => {
|
||||||
|
const techElement =
|
||||||
|
playerRef.current.el().querySelector('.vjs-tech') ||
|
||||||
|
playerRef.current.el().querySelector('video') ||
|
||||||
|
(playerRef.current.tech() && playerRef.current.tech().el());
|
||||||
|
|
||||||
|
if (techElement && techElement !== videoRef.current && techElement !== videoElement) {
|
||||||
|
// Use capture phase to catch before Video.js might prevent it
|
||||||
|
techElement.addEventListener('contextmenu', handleContextMenu, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to attach immediately
|
||||||
|
attachContextMenu();
|
||||||
|
|
||||||
|
// Also try after a short delay in case elements aren't ready yet
|
||||||
|
setTimeout(() => {
|
||||||
|
attachContextMenu();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Also try when video is loaded
|
||||||
|
playerRef.current.one('loadedmetadata', () => {
|
||||||
|
attachContextMenu();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//}, 0);
|
//}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup: Remove context menu event listener
|
||||||
|
return () => {
|
||||||
|
if (playerRef.current && playerRef.current.el()) {
|
||||||
|
const playerEl = playerRef.current.el();
|
||||||
|
playerEl.removeEventListener('contextmenu', handleContextMenu, true);
|
||||||
|
|
||||||
|
const techElement =
|
||||||
|
playerEl.querySelector('.vjs-tech') ||
|
||||||
|
playerEl.querySelector('video') ||
|
||||||
|
(playerRef.current.tech() && playerRef.current.tech().el());
|
||||||
|
if (techElement) {
|
||||||
|
techElement.removeEventListener('contextmenu', handleContextMenu, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<>
|
||||||
ref={videoRef}
|
<video
|
||||||
id={videoId}
|
ref={videoRef}
|
||||||
controls={true}
|
id={videoId}
|
||||||
className={`video-js vjs-fluid vjs-default-skin${currentVideo.useRoundedCorners ? ' video-js-rounded-corners' : ''}`}
|
controls={true}
|
||||||
preload="auto"
|
className={`video-js ${isEmbedPlayer ? 'vjs-fill' : 'vjs-fluid'} vjs-default-skin${currentVideo.useRoundedCorners ? ' video-js-rounded-corners' : ''}`}
|
||||||
poster={currentVideo.poster}
|
preload="auto"
|
||||||
tabIndex="0"
|
poster={currentVideo.poster}
|
||||||
>
|
tabIndex="0"
|
||||||
{/* <source src="/videos/sample-video.mp4" type="video/mp4" />
|
>
|
||||||
<source src="/videos/sample-video.webm" type="video/webm" /> */}
|
{/* <source src="/videos/sample-video.mp4" type="video/mp4" />
|
||||||
<p className="vjs-no-js">
|
<source src="/videos/sample-video.webm" type="video/webm" /> */}
|
||||||
To view this video please enable JavaScript, and consider upgrading to a web browser that
|
<p className="vjs-no-js">
|
||||||
<a href="https://videojs.com/html5-video-support/" target="_blank">
|
To view this video please enable JavaScript, and consider upgrading to a web browser that
|
||||||
supports HTML5 video
|
<a href="https://videojs.com/html5-video-support/" target="_blank">
|
||||||
</a>
|
supports HTML5 video
|
||||||
</p>
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
{/* Add subtitle tracks */}
|
{/* Add subtitle tracks */}
|
||||||
{/* {subtitleTracks &&
|
{/* {subtitleTracks &&
|
||||||
subtitleTracks.map((track, index) => (
|
subtitleTracks.map((track, index) => (
|
||||||
<track
|
<track
|
||||||
key={index}
|
key={index}
|
||||||
kind={track.kind}
|
kind={track.kind}
|
||||||
src={track.src}
|
src={track.src}
|
||||||
srcLang={track.srclang}
|
srcLang={track.srclang}
|
||||||
label={track.label}
|
label={track.label}
|
||||||
default={track.default}
|
default={track.default}
|
||||||
/>
|
/>
|
||||||
))} */}
|
))} */}
|
||||||
{/*
|
{/*
|
||||||
<track kind="chapters" src="/sample-chapters.vtt" /> */}
|
<track kind="chapters" src="/sample-chapters.vtt" /> */}
|
||||||
{/* Add chapters track */}
|
{/* Add chapters track */}
|
||||||
{/* {chaptersData &&
|
{/* {chaptersData &&
|
||||||
chaptersData.length > 0 &&
|
chaptersData.length > 0 &&
|
||||||
(console.log('chaptersData', chaptersData), (<track kind="chapters" src="/sample-chapters.vtt" />))} */}
|
(console.log('chaptersData', chaptersData), (<track kind="chapters" src="/sample-chapters.vtt" />))} */}
|
||||||
</video>
|
</video>
|
||||||
|
<VideoContextMenu
|
||||||
|
visible={contextMenuVisible}
|
||||||
|
position={contextMenuPosition}
|
||||||
|
onClose={closeContextMenu}
|
||||||
|
onCopyVideoUrl={handleCopyVideoUrl}
|
||||||
|
onCopyVideoUrlAtTime={handleCopyVideoUrlAtTime}
|
||||||
|
onCopyEmbedCode={handleCopyEmbedCode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,17 @@ export class EndScreenHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleVideoEnded() {
|
handleVideoEnded() {
|
||||||
const { isEmbedPlayer, userPreferences, mediaData, currentVideo, relatedVideos, goToNextVideo } = this.options;
|
const {
|
||||||
|
isEmbedPlayer,
|
||||||
|
userPreferences,
|
||||||
|
mediaData,
|
||||||
|
currentVideo,
|
||||||
|
relatedVideos,
|
||||||
|
goToNextVideo,
|
||||||
|
showRelated,
|
||||||
|
showUserAvatar,
|
||||||
|
linkTitle,
|
||||||
|
} = this.options;
|
||||||
|
|
||||||
// For embed players, show big play button when video ends
|
// For embed players, show big play button when video ends
|
||||||
if (isEmbedPlayer) {
|
if (isEmbedPlayer) {
|
||||||
@@ -73,6 +83,34 @@ export class EndScreenHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If showRelated is false, we don't show the end screen or autoplay countdown
|
||||||
|
if (showRelated === false) {
|
||||||
|
// But we still want to keep the control bar visible and hide the poster
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.player && !this.player.isDisposed()) {
|
||||||
|
const playerEl = this.player.el();
|
||||||
|
if (playerEl) {
|
||||||
|
// Hide poster elements
|
||||||
|
const posterElements = playerEl.querySelectorAll('.vjs-poster');
|
||||||
|
posterElements.forEach((posterEl) => {
|
||||||
|
posterEl.style.display = 'none';
|
||||||
|
posterEl.style.visibility = 'hidden';
|
||||||
|
posterEl.style.opacity = '0';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keep control bar visible
|
||||||
|
const controlBar = this.player.getChild('controlBar');
|
||||||
|
if (controlBar) {
|
||||||
|
controlBar.show();
|
||||||
|
controlBar.el().style.opacity = '1';
|
||||||
|
controlBar.el().style.pointerEvents = 'auto';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Keep controls active after video ends
|
// Keep controls active after video ends
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.player && !this.player.isDisposed()) {
|
if (this.player && !this.player.isDisposed()) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import './BulkActionCategoryModal.scss';
|
import './BulkActionCategoryModal.scss';
|
||||||
import { translateString } from '../utils/helpers/';
|
import { translateString } from '../utils/helpers/';
|
||||||
|
import { inEmbeddedApp } from '../utils/helpers/embeddedApp';
|
||||||
|
|
||||||
interface Category {
|
interface Category {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -24,6 +25,7 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
onError,
|
onError,
|
||||||
csrfToken,
|
csrfToken,
|
||||||
}) => {
|
}) => {
|
||||||
|
const isLmsMode = inEmbeddedApp();
|
||||||
const [existingCategories, setExistingCategories] = useState<Category[]>([]);
|
const [existingCategories, setExistingCategories] = useState<Category[]>([]);
|
||||||
const [allCategories, setAllCategories] = useState<Category[]>([]);
|
const [allCategories, setAllCategories] = useState<Category[]>([]);
|
||||||
const [categoriesToAdd, setCategoriesToAdd] = useState<Category[]>([]);
|
const [categoriesToAdd, setCategoriesToAdd] = useState<Category[]>([]);
|
||||||
@@ -66,20 +68,27 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
const existingData = await existingResponse.json();
|
const existingData = await existingResponse.json();
|
||||||
const existing = existingData.results || [];
|
const existing = existingData.results || [];
|
||||||
|
|
||||||
// Fetch all categories
|
// Fetch all categories (or LMS courses only in embed mode)
|
||||||
const allResponse = await fetch('/api/v1/categories');
|
const categoriesUrl = isLmsMode
|
||||||
|
? '/api/v1/categories/contributor?lms_courses_only=true'
|
||||||
|
: '/api/v1/categories';
|
||||||
|
const allResponse = await fetch(categoriesUrl);
|
||||||
if (!allResponse.ok) {
|
if (!allResponse.ok) {
|
||||||
throw new Error(translateString('Failed to fetch all categories'));
|
throw new Error(isLmsMode ? translateString('Failed to fetch courses') : translateString('Failed to fetch all categories'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const allData = await allResponse.json();
|
const allData = await allResponse.json();
|
||||||
const all = allData.results || allData;
|
const all = allData.results || allData;
|
||||||
|
|
||||||
setExistingCategories(existing);
|
// In LMS mode, filter existing to only show LMS course categories
|
||||||
|
const allUids = new Set(all.map((c: Category) => c.uid));
|
||||||
|
const filteredExisting = isLmsMode ? existing.filter((c: Category) => allUids.has(c.uid)) : existing;
|
||||||
|
|
||||||
|
setExistingCategories(filteredExisting);
|
||||||
setAllCategories(all);
|
setAllCategories(all);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching categories:', error);
|
console.error('Error fetching categories:', error);
|
||||||
onError(translateString('Failed to load categories'));
|
onError(isLmsMode ? translateString('Failed to load courses') : translateString('Failed to load categories'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -126,7 +135,7 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!addResponse.ok) {
|
if (!addResponse.ok) {
|
||||||
throw new Error(translateString('Failed to add categories'));
|
throw new Error(isLmsMode ? translateString('Failed to add courses') : translateString('Failed to add categories'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,15 +156,15 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!removeResponse.ok) {
|
if (!removeResponse.ok) {
|
||||||
throw new Error(translateString('Failed to remove categories'));
|
throw new Error(isLmsMode ? translateString('Failed to remove courses') : translateString('Failed to remove categories'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess(translateString('Successfully updated categories'));
|
onSuccess(isLmsMode ? translateString('Successfully updated courses') : translateString('Successfully updated categories'));
|
||||||
onCancel();
|
onCancel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing categories:', error);
|
console.error('Error processing categories:', error);
|
||||||
onError(translateString('Failed to update categories. Please try again.'));
|
onError(isLmsMode ? translateString('Failed to update courses. Please try again.') : translateString('Failed to update categories. Please try again.'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
}
|
}
|
||||||
@@ -184,7 +193,7 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
<div className="category-modal-overlay">
|
<div className="category-modal-overlay">
|
||||||
<div className="category-modal">
|
<div className="category-modal">
|
||||||
<div className="category-modal-header">
|
<div className="category-modal-header">
|
||||||
<h2>{translateString('Add / Remove from Categories')}</h2>
|
<h2>{isLmsMode ? translateString('Share with Course') : translateString('Add / Remove from Categories')}</h2>
|
||||||
<button className="category-modal-close" onClick={onCancel}>
|
<button className="category-modal-close" onClick={onCancel}>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@@ -192,14 +201,14 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
|
|
||||||
<div className="category-modal-content">
|
<div className="category-modal-content">
|
||||||
<div className="category-panel">
|
<div className="category-panel">
|
||||||
<h3>{translateString('Categories')}</h3>
|
<h3>{isLmsMode ? translateString('Courses') : translateString('Categories')}</h3>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="loading-message">{translateString('Loading categories...')}</div>
|
<div className="loading-message">{isLmsMode ? translateString('Loading courses...') : translateString('Loading categories...')}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="category-list scrollable">
|
<div className="category-list scrollable">
|
||||||
{leftPanelCategories.length === 0 ? (
|
{leftPanelCategories.length === 0 ? (
|
||||||
<div className="empty-message">{translateString('All categories already added')}</div>
|
<div className="empty-message">{isLmsMode ? translateString('All courses already added') : translateString('All categories already added')}</div>
|
||||||
) : (
|
) : (
|
||||||
leftPanelCategories.map((category) => (
|
leftPanelCategories.map((category) => (
|
||||||
<div
|
<div
|
||||||
@@ -227,11 +236,11 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="loading-message">{translateString('Loading categories...')}</div>
|
<div className="loading-message">{isLmsMode ? translateString('Loading courses...') : translateString('Loading categories...')}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="category-list scrollable">
|
<div className="category-list scrollable">
|
||||||
{rightPanelCategories.length === 0 ? (
|
{rightPanelCategories.length === 0 ? (
|
||||||
<div className="empty-message">{translateString('No categories')}</div>
|
<div className="empty-message">{isLmsMode ? translateString('No courses') : translateString('No categories')}</div>
|
||||||
) : (
|
) : (
|
||||||
rightPanelCategories.map((category) => {
|
rightPanelCategories.map((category) => {
|
||||||
const isExisting = existingCategories.some((c) => c.uid === category.uid);
|
const isExisting = existingCategories.some((c) => c.uid === category.uid);
|
||||||
@@ -251,7 +260,7 @@ export const BulkActionCategoryModal: React.FC<BulkActionCategoryModalProps> = (
|
|||||||
removeCategoryFromAddList(category);
|
removeCategoryFromAddList(category);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={isMarkedForRemoval ? translateString('Undo removal') : isExisting ? translateString('Remove category') : translateString('Remove from list')}
|
title={isMarkedForRemoval ? translateString('Undo removal') : isExisting ? (isLmsMode ? translateString('Remove course') : translateString('Remove category')) : translateString('Remove from list')}
|
||||||
>
|
>
|
||||||
{isMarkedForRemoval ? '↺' : '×'}
|
{isMarkedForRemoval ? '↺' : '×'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}`);
|
const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}&exclude_self=True`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(translateString('Failed to search users'));
|
throw new Error(translateString('Failed to search users'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
.permission-modal-header {
|
.permission-modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
|
||||||
@@ -102,6 +102,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.permission-modal-subtitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #777;
|
||||||
|
|
||||||
|
.dark_theme & {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.permission-modal-content {
|
.permission-modal-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
|
|||||||
@@ -226,7 +226,42 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
|||||||
<div className="permission-modal-overlay">
|
<div className="permission-modal-overlay">
|
||||||
<div className="permission-modal">
|
<div className="permission-modal">
|
||||||
<div className="permission-modal-header">
|
<div className="permission-modal-header">
|
||||||
<h2>{translateString('Manage')} {permissionLabel}</h2>
|
<div>
|
||||||
|
<h2>{translateString('Share with')} {permissionLabel}</h2>
|
||||||
|
{permissionType === 'viewer' && (
|
||||||
|
<div className="permission-modal-subtitle">
|
||||||
|
<span>{translateString('Give users viewer permissions to your media by adding them to the below list.')}</span>
|
||||||
|
<span
|
||||||
|
className="info-tooltip"
|
||||||
|
title={translateString("Users can view your media via: My Media > Shared with Me > particular media > ...")}
|
||||||
|
>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{permissionType === 'editor' && (
|
||||||
|
<div className="permission-modal-subtitle">
|
||||||
|
<span>{translateString('Give users editor permissions to your media by adding them to the below list.')}</span>
|
||||||
|
<span
|
||||||
|
className="info-tooltip"
|
||||||
|
title={translateString("Users can edit your media via: My Media > Shared with Me > particular media > Edit...")}
|
||||||
|
>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{permissionType === 'owner' && (
|
||||||
|
<div className="permission-modal-subtitle">
|
||||||
|
<span>{translateString('Give users owner permissions to your media, except for deleting the media, by adding them to the below list.')}</span>
|
||||||
|
<span
|
||||||
|
className="info-tooltip"
|
||||||
|
title={translateString("Users can manage your media via: My Media > Shared with Me > particular media > ...")}
|
||||||
|
>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<button className="permission-modal-close" onClick={onCancel}>
|
<button className="permission-modal-close" onClick={onCancel}>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@@ -284,6 +319,21 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
|||||||
?
|
?
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{permissionType === 'viewer' && (
|
||||||
|
<span className="info-tooltip" title={translateString('Remove users from the list to remove viewer permissions')}>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{permissionType === 'editor' && (
|
||||||
|
<span className="info-tooltip" title={translateString('Remove users from the list to remove editor permissions')}>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{permissionType === 'owner' && (
|
||||||
|
<span className="info-tooltip" title={translateString('Remove users from the list to remove owner permissions')}>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="search-box">
|
<div className="search-box">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -7,21 +7,53 @@ interface BulkActionsDropdownProps {
|
|||||||
onActionSelect: (action: string) => void;
|
onActionSelect: (action: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BULK_ACTIONS = [
|
interface BulkAction {
|
||||||
{ value: 'add-remove-coviewers', label: translateString('Add / Remove Co-Viewers'), enabled: true },
|
value: string;
|
||||||
{ value: 'add-remove-coeditors', label: translateString('Add / Remove Co-Editors'), enabled: true },
|
label: string;
|
||||||
{ value: 'add-remove-coowners', label: translateString('Add / Remove Co-Owners'), enabled: true },
|
enabled: boolean;
|
||||||
{ value: 'add-remove-playlist', label: translateString('Add to / Remove from Playlist'), enabled: true },
|
}
|
||||||
{ value: 'add-remove-category', label: translateString('Add to / Remove from Category'), enabled: true },
|
|
||||||
{ value: 'add-remove-tags', label: translateString('Add / Remove Tags'), enabled: true },
|
interface BulkActionGroup {
|
||||||
{ value: 'enable-comments', label: translateString('Enable Comments'), enabled: true },
|
label: string;
|
||||||
{ value: 'disable-comments', label: translateString('Disable Comments'), enabled: true },
|
actions: BulkAction[];
|
||||||
{ value: 'enable-download', label: translateString('Enable Download'), enabled: true },
|
}
|
||||||
{ value: 'disable-download', label: translateString('Disable Download'), enabled: true },
|
|
||||||
{ value: 'publish-state', label: translateString('Publish State'), enabled: true },
|
const BULK_ACTION_GROUPS: BulkActionGroup[] = [
|
||||||
{ value: 'change-owner', label: translateString('Change Owner'), enabled: true },
|
{
|
||||||
{ value: 'copy-media', label: translateString('Copy Media'), enabled: true },
|
label: translateString('Sharing'),
|
||||||
{ value: 'delete-media', label: translateString('Delete Media'), enabled: true },
|
actions: [
|
||||||
|
{ value: 'add-remove-coviewers', label: translateString('Share with Co-Viewers'), enabled: true },
|
||||||
|
{ value: 'add-remove-coeditors', label: translateString('Share with Co-Editors'), enabled: true },
|
||||||
|
{ value: 'add-remove-coowners', label: translateString('Share with Co-Owners'), enabled: true },
|
||||||
|
{ value: 'add-remove-category', label: translateString('Share with Course Members'), enabled: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translateString('Organization'),
|
||||||
|
actions: [
|
||||||
|
{ value: 'add-remove-playlist', label: translateString('Add to / Remove from Playlist'), enabled: true },
|
||||||
|
{ value: 'add-remove-tags', label: translateString('Add / Remove Tags'), enabled: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translateString('Settings'),
|
||||||
|
actions: [
|
||||||
|
{ value: 'enable-comments', label: translateString('Enable Comments'), enabled: true },
|
||||||
|
{ value: 'disable-comments', label: translateString('Disable Comments'), enabled: true },
|
||||||
|
{ value: 'delete-comments', label: translateString('Delete Comments'), enabled: true },
|
||||||
|
{ value: 'enable-download', label: translateString('Enable Download'), enabled: true },
|
||||||
|
{ value: 'disable-download', label: translateString('Disable Download'), enabled: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: translateString('Management'),
|
||||||
|
actions: [
|
||||||
|
{ value: 'publish-state', label: translateString('Publish State'), enabled: true },
|
||||||
|
{ value: 'change-owner', label: translateString('Change Owner'), enabled: true },
|
||||||
|
{ value: 'copy-media', label: translateString('Copy Media'), enabled: true },
|
||||||
|
{ value: 'delete-media', label: translateString('Delete Media'), enabled: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BulkActionsDropdown: React.FC<BulkActionsDropdownProps> = ({ selectedCount, onActionSelect }) => {
|
export const BulkActionsDropdown: React.FC<BulkActionsDropdownProps> = ({ selectedCount, onActionSelect }) => {
|
||||||
@@ -58,10 +90,14 @@ export const BulkActionsDropdown: React.FC<BulkActionsDropdownProps> = ({ select
|
|||||||
<option value="" disabled>
|
<option value="" disabled>
|
||||||
{displayText}
|
{displayText}
|
||||||
</option>
|
</option>
|
||||||
{BULK_ACTIONS.map((action) => (
|
{BULK_ACTION_GROUPS.map((group) => (
|
||||||
<option key={action.value} value={action.value} disabled={noSelection || !action.enabled}>
|
<optgroup key={group.label} label={group.label}>
|
||||||
{action.label}
|
{group.actions.map((action) => (
|
||||||
</option>
|
<option key={action.value} value={action.value} disabled={noSelection || !action.enabled}>
|
||||||
|
{action.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { translateString } from '../utils/helpers/';
|
import { translateString, inSelectMediaEmbedMode } from '../utils/helpers/';
|
||||||
|
|
||||||
interface MediaListHeaderProps {
|
interface MediaListHeaderProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -11,10 +11,12 @@ interface MediaListHeaderProps {
|
|||||||
|
|
||||||
export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => {
|
export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => {
|
||||||
const viewAllText = props.viewAllText || translateString('VIEW ALL');
|
const viewAllText = props.viewAllText || translateString('VIEW ALL');
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}>
|
<div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}>
|
||||||
<h2>{props.title}</h2>
|
<h2>{props.title}</h2>
|
||||||
{props.viewAllLink ? (
|
{!isSelectMediaMode && props.viewAllLink ? (
|
||||||
<h3>
|
<h3>
|
||||||
{' '}
|
{' '}
|
||||||
<a href={props.viewAllLink} title={viewAllText}>
|
<a href={props.viewAllLink} title={viewAllText}>
|
||||||
|
|||||||
@@ -16,8 +16,17 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
.add-media-button {
|
.add-media-button {
|
||||||
|
position: relative;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import React from 'react';
|
|||||||
import { MediaListRow } from './MediaListRow';
|
import { MediaListRow } from './MediaListRow';
|
||||||
import { BulkActionsDropdown } from './BulkActionsDropdown';
|
import { BulkActionsDropdown } from './BulkActionsDropdown';
|
||||||
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
||||||
import { CircleIconButton, MaterialIcon } from './_shared';
|
import { CircleIconButton, MaterialIcon, PopupMain, NavigationMenuList } from './_shared';
|
||||||
import { LinksConsumer } from '../utils/contexts';
|
import { LinksConsumer } from '../utils/contexts';
|
||||||
|
import { usePopup } from '../utils/hooks';
|
||||||
import { translateString } from '../utils/helpers/';
|
import { translateString } from '../utils/helpers/';
|
||||||
import './MediaListWrapper.scss';
|
import './MediaListWrapper.scss';
|
||||||
|
|
||||||
@@ -37,36 +38,60 @@ export const MediaListWrapper: React.FC<MediaListWrapperProps> = ({
|
|||||||
onSelectAll = () => {},
|
onSelectAll = () => {},
|
||||||
onDeselectAll = () => {},
|
onDeselectAll = () => {},
|
||||||
showAddMediaButton = false,
|
showAddMediaButton = false,
|
||||||
}) => (
|
}) => {
|
||||||
<div className={(className ? className + ' ' : '') + 'media-list-wrapper'} style={style}>
|
const [popupContentRef, PopupContent, PopupTrigger] = usePopup() as [any, any, any];
|
||||||
<MediaListRow title={title} viewAllLink={viewAllLink} viewAllText={viewAllText}>
|
|
||||||
{showBulkActions && (
|
return (
|
||||||
<LinksConsumer>
|
<div className={(className ? className + ' ' : '') + 'media-list-wrapper'} style={style}>
|
||||||
{(links) => (
|
<MediaListRow title={title} viewAllLink={viewAllLink} viewAllText={viewAllText}>
|
||||||
<div className="bulk-actions-container">
|
{showBulkActions && (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
<LinksConsumer>
|
||||||
<BulkActionsDropdown selectedCount={selectedCount} onActionSelect={onBulkAction} />
|
{(links) => {
|
||||||
<SelectAllCheckbox
|
const uploadMenuItems = [
|
||||||
totalCount={totalCount}
|
{
|
||||||
selectedCount={selectedCount}
|
link: links.user.addMedia,
|
||||||
onSelectAll={onSelectAll}
|
icon: 'upload',
|
||||||
onDeselectAll={onDeselectAll}
|
text: translateString('Upload'),
|
||||||
/>
|
},
|
||||||
</div>
|
{
|
||||||
{showAddMediaButton && (
|
link: '/record_screen',
|
||||||
<div className="add-media-button">
|
icon: 'videocam',
|
||||||
<a href={links.user.addMedia} title={translateString('Add media')}>
|
text: translateString('Record'),
|
||||||
<CircleIconButton>
|
},
|
||||||
<MaterialIcon type="video_call" />
|
];
|
||||||
</CircleIconButton>
|
|
||||||
</a>
|
return (
|
||||||
|
<div className="bulk-actions-container">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
|
<BulkActionsDropdown selectedCount={selectedCount} onActionSelect={onBulkAction} />
|
||||||
|
<SelectAllCheckbox
|
||||||
|
totalCount={totalCount}
|
||||||
|
selectedCount={selectedCount}
|
||||||
|
onSelectAll={onSelectAll}
|
||||||
|
onDeselectAll={onDeselectAll}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{showAddMediaButton && (
|
||||||
|
<div className="add-media-button">
|
||||||
|
<PopupTrigger contentRef={popupContentRef}>
|
||||||
|
<CircleIconButton title={translateString('Add media')}>
|
||||||
|
<MaterialIcon type="video_call" />
|
||||||
|
</CircleIconButton>
|
||||||
|
</PopupTrigger>
|
||||||
|
<PopupContent contentRef={popupContentRef}>
|
||||||
|
<PopupMain>
|
||||||
|
<NavigationMenuList items={uploadMenuItems} />
|
||||||
|
</PopupMain>
|
||||||
|
</PopupContent>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
</div>
|
}}
|
||||||
)}
|
</LinksConsumer>
|
||||||
</LinksConsumer>
|
)}
|
||||||
)}
|
{children || null}
|
||||||
{children || null}
|
</MediaListRow>
|
||||||
</MediaListRow>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ const VideoJSEmbed = ({
|
|||||||
poster,
|
poster,
|
||||||
previewSprite,
|
previewSprite,
|
||||||
subtitlesInfo,
|
subtitlesInfo,
|
||||||
enableAutoplay,
|
|
||||||
inEmbed,
|
inEmbed,
|
||||||
|
showTitle,
|
||||||
|
showRelated,
|
||||||
|
showUserAvatar,
|
||||||
|
linkTitle,
|
||||||
hasTheaterMode,
|
hasTheaterMode,
|
||||||
hasNextLink,
|
hasNextLink,
|
||||||
nextLink,
|
nextLink,
|
||||||
@@ -62,8 +65,10 @@ const VideoJSEmbed = ({
|
|||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
// Get URL parameters for autoplay, muted, and timestamp
|
// Get URL parameters for autoplay, muted, and timestamp
|
||||||
const urlTimestamp = getUrlParameter('t');
|
const urlTimestamp = getUrlParameter('t');
|
||||||
const urlAutoplay = getUrlParameter('autoplay');
|
|
||||||
const urlMuted = getUrlParameter('muted');
|
const urlMuted = getUrlParameter('muted');
|
||||||
|
const urlShowRelated = getUrlParameter('showRelated');
|
||||||
|
const urlShowUserAvatar = getUrlParameter('showUserAvatar');
|
||||||
|
const urlLinkTitle = getUrlParameter('linkTitle');
|
||||||
|
|
||||||
window.MEDIA_DATA = {
|
window.MEDIA_DATA = {
|
||||||
data: data || {},
|
data: data || {},
|
||||||
@@ -71,7 +76,7 @@ const VideoJSEmbed = ({
|
|||||||
version: version,
|
version: version,
|
||||||
isPlayList: isPlayList,
|
isPlayList: isPlayList,
|
||||||
playerVolume: playerVolume || 0.5,
|
playerVolume: playerVolume || 0.5,
|
||||||
playerSoundMuted: playerSoundMuted || (urlMuted === '1'),
|
playerSoundMuted: urlMuted === '1',
|
||||||
videoQuality: videoQuality || 'auto',
|
videoQuality: videoQuality || 'auto',
|
||||||
videoPlaybackSpeed: videoPlaybackSpeed || 1,
|
videoPlaybackSpeed: videoPlaybackSpeed || 1,
|
||||||
inTheaterMode: inTheaterMode || false,
|
inTheaterMode: inTheaterMode || false,
|
||||||
@@ -83,8 +88,11 @@ const VideoJSEmbed = ({
|
|||||||
poster: poster || '',
|
poster: poster || '',
|
||||||
previewSprite: previewSprite || null,
|
previewSprite: previewSprite || null,
|
||||||
subtitlesInfo: subtitlesInfo || [],
|
subtitlesInfo: subtitlesInfo || [],
|
||||||
enableAutoplay: enableAutoplay || (urlAutoplay === '1'),
|
|
||||||
inEmbed: inEmbed || false,
|
inEmbed: inEmbed || false,
|
||||||
|
showTitle: showTitle || false,
|
||||||
|
showRelated: showRelated !== undefined ? showRelated : (urlShowRelated === '1' || urlShowRelated === 'true' || urlShowRelated === null),
|
||||||
|
showUserAvatar: showUserAvatar !== undefined ? showUserAvatar : (urlShowUserAvatar === '1' || urlShowUserAvatar === 'true' || urlShowUserAvatar === null),
|
||||||
|
linkTitle: linkTitle !== undefined ? linkTitle : (urlLinkTitle === '1' || urlLinkTitle === 'true' || urlLinkTitle === null),
|
||||||
hasTheaterMode: hasTheaterMode || false,
|
hasTheaterMode: hasTheaterMode || false,
|
||||||
hasNextLink: hasNextLink || false,
|
hasNextLink: hasNextLink || false,
|
||||||
nextLink: nextLink || null,
|
nextLink: nextLink || null,
|
||||||
@@ -92,8 +100,10 @@ const VideoJSEmbed = ({
|
|||||||
errorMessage: errorMessage || '',
|
errorMessage: errorMessage || '',
|
||||||
// URL parameters
|
// URL parameters
|
||||||
urlTimestamp: urlTimestamp ? parseInt(urlTimestamp, 10) : null,
|
urlTimestamp: urlTimestamp ? parseInt(urlTimestamp, 10) : null,
|
||||||
urlAutoplay: urlAutoplay === '1',
|
|
||||||
urlMuted: urlMuted === '1',
|
urlMuted: urlMuted === '1',
|
||||||
|
urlShowRelated: urlShowRelated === '1' || urlShowRelated === 'true',
|
||||||
|
urlShowUserAvatar: urlShowUserAvatar === '1' || urlShowUserAvatar === 'true',
|
||||||
|
urlLinkTitle: urlLinkTitle === '1' || urlLinkTitle === 'true',
|
||||||
onClickNextCallback: onClickNextCallback || null,
|
onClickNextCallback: onClickNextCallback || null,
|
||||||
onClickPreviousCallback: onClickPreviousCallback || null,
|
onClickPreviousCallback: onClickPreviousCallback || null,
|
||||||
onStateUpdateCallback: onStateUpdateCallback || null,
|
onStateUpdateCallback: onStateUpdateCallback || null,
|
||||||
@@ -176,11 +186,17 @@ const VideoJSEmbed = ({
|
|||||||
// Scroll to the video player with smooth behavior
|
// Scroll to the video player with smooth behavior
|
||||||
const videoElement = document.querySelector(inEmbedRef.current ? '#video-embed' : '#video-main');
|
const videoElement = document.querySelector(inEmbedRef.current ? '#video-embed' : '#video-main');
|
||||||
if (videoElement) {
|
if (videoElement) {
|
||||||
videoElement.scrollIntoView({
|
const urlScroll = getUrlParameter('scroll');
|
||||||
behavior: 'smooth',
|
const isIframe = window.parent !== window;
|
||||||
block: 'center',
|
|
||||||
inline: 'nearest'
|
// Only scroll if not in an iframe, OR if explicitly requested via scroll=1 parameter
|
||||||
});
|
if (!isIframe || urlScroll === '1' || urlScroll === 'true') {
|
||||||
|
videoElement.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
inline: 'nearest'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('VideoJS player not found for timestamp navigation');
|
console.warn('VideoJS player not found for timestamp navigation');
|
||||||
@@ -220,7 +236,14 @@ const VideoJSEmbed = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="video-js-wrapper" ref={containerRef}>
|
<div className="video-js-wrapper" ref={containerRef}>
|
||||||
{inEmbed ? <div id="video-js-root-embed" className="video-js-root-embed" /> : <div id="video-js-root-main" className="video-js-root-main" />}
|
{inEmbed ? (
|
||||||
|
<div
|
||||||
|
id="video-js-root-embed"
|
||||||
|
className="video-js-root-embed"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div id="video-js-root-main" className="video-js-root-main" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,50 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMediaItem } from '../../utils/hooks/';
|
import { useMediaItem } from '../../utils/hooks/';
|
||||||
import { PositiveInteger, PositiveIntegerOrZero } from '../../utils/helpers/';
|
import { PositiveInteger, PositiveIntegerOrZero, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
import { MediaItemThumbnailLink, itemClassname } from './includes/items/';
|
import { MediaItemThumbnailLink, itemClassname } from './includes/items/';
|
||||||
import { Item } from './Item';
|
import { Item } from './Item';
|
||||||
|
|
||||||
export function MediaItem(props) {
|
export function MediaItem(props) {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] =
|
const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
|
||||||
useMediaItem({ ...props, type });
|
useMediaItem({ ...props, type });
|
||||||
|
|
||||||
|
// In embed mode, override components to remove links
|
||||||
|
const ItemTitle = ({ title }) => (
|
||||||
|
<h3>
|
||||||
|
<span>{title}</span>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
|
||||||
|
|
||||||
|
const titleComponent = isSelectMediaMode
|
||||||
|
? () => <ItemTitle title={props.title} />
|
||||||
|
: titleComponentOrig;
|
||||||
|
|
||||||
|
const UnderThumbWrapper = isSelectMediaMode ? ItemMain : UnderThumbWrapperOrig;
|
||||||
|
|
||||||
function thumbnailComponent() {
|
function thumbnailComponent() {
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
// In embed mode, render thumbnail without link
|
||||||
|
const thumbStyle = thumbnailUrl ? { backgroundImage: "url('" + thumbnailUrl + "')" } : null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="item-thumb"
|
||||||
|
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||||
|
style={thumbStyle}
|
||||||
|
>
|
||||||
|
{thumbnailUrl ? (
|
||||||
|
<div key="item-type-icon" className="item-type-icon">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
|
return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,11 +57,13 @@ export function MediaItem(props) {
|
|||||||
const finalClassname = containerClassname +
|
const finalClassname = containerClassname +
|
||||||
(props.showSelection ? ' with-selection' : '') +
|
(props.showSelection ? ' with-selection' : '') +
|
||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection || isSelectMediaMode ? ' has-any-selection' : '');
|
||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
// If there's any selection active, clicking the item should toggle selection
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
|
||||||
|
// In select media mode or if there's any selection active, clicking the item should toggle selection
|
||||||
|
if ((isSelectMediaMode || props.hasAnySelection) && props.onCheckboxChange) {
|
||||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
if (e.target.closest('.item-selection-checkbox') ||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
e.target.closest('.item-edit-icon') ||
|
e.target.closest('.item-edit-icon') ||
|
||||||
@@ -59,16 +93,24 @@ export function MediaItem(props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editMediaComponent()}
|
{!isSelectMediaMode && editMediaComponent()}
|
||||||
{viewMediaComponent()}
|
{!isSelectMediaMode && viewMediaComponent()}
|
||||||
|
|
||||||
{thumbnailComponent()}
|
{thumbnailComponent()}
|
||||||
|
|
||||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
{isSelectMediaMode ? (
|
||||||
{titleComponent()}
|
<UnderThumbWrapper>
|
||||||
{metaComponents()}
|
{titleComponent()}
|
||||||
{descriptionComponent()}
|
{metaComponents()}
|
||||||
</UnderThumbWrapper>
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
) : (
|
||||||
|
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||||
|
{titleComponent()}
|
||||||
|
{metaComponents()}
|
||||||
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMediaItem } from '../../utils/hooks/';
|
import { useMediaItem } from '../../utils/hooks/';
|
||||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
import { PositiveIntegerOrZero, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
import { MediaDurationInfo } from '../../utils/classes/';
|
import { MediaDurationInfo } from '../../utils/classes/';
|
||||||
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions';
|
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions';
|
||||||
import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
||||||
@@ -9,10 +9,26 @@ import { MediaItem } from './MediaItem';
|
|||||||
|
|
||||||
export function MediaItemAudio(props) {
|
export function MediaItemAudio(props) {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] =
|
const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
|
||||||
useMediaItem({ ...props, type });
|
useMediaItem({ ...props, type });
|
||||||
|
|
||||||
|
// In embed mode, override components to remove links
|
||||||
|
const ItemTitle = ({ title }) => (
|
||||||
|
<h3>
|
||||||
|
<span>{title}</span>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
|
||||||
|
|
||||||
|
const titleComponent = isSelectMediaMode
|
||||||
|
? () => <ItemTitle title={props.title} />
|
||||||
|
: titleComponentOrig;
|
||||||
|
|
||||||
|
const UnderThumbWrapper = isSelectMediaMode ? ItemMain : UnderThumbWrapperOrig;
|
||||||
|
|
||||||
const _MediaDurationInfo = new MediaDurationInfo();
|
const _MediaDurationInfo = new MediaDurationInfo();
|
||||||
|
|
||||||
_MediaDurationInfo.update(props.duration);
|
_MediaDurationInfo.update(props.duration);
|
||||||
@@ -22,6 +38,21 @@ export function MediaItemAudio(props) {
|
|||||||
const durationISO8601 = _MediaDurationInfo.ISO8601();
|
const durationISO8601 = _MediaDurationInfo.ISO8601();
|
||||||
|
|
||||||
function thumbnailComponent() {
|
function thumbnailComponent() {
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
// In embed mode, render thumbnail without link
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="item-thumb"
|
||||||
|
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||||
|
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
|
||||||
|
>
|
||||||
|
{props.inPlaylistView ? null : (
|
||||||
|
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const attr = {
|
const attr = {
|
||||||
key: 'item-thumb',
|
key: 'item-thumb',
|
||||||
href: props.link,
|
href: props.link,
|
||||||
@@ -68,11 +99,11 @@ export function MediaItemAudio(props) {
|
|||||||
const finalClassname = containerClassname +
|
const finalClassname = containerClassname +
|
||||||
(props.showSelection ? ' with-selection' : '') +
|
(props.showSelection ? ' with-selection' : '') +
|
||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection || isSelectMediaMode ? ' has-any-selection' : '');
|
||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
// If there's any selection active, clicking the item should toggle selection
|
// In embed mode or if there's any selection active, clicking the item should toggle selection
|
||||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
if ((isSelectMediaMode || props.hasAnySelection) && props.onCheckboxChange) {
|
||||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
if (e.target.closest('.item-selection-checkbox') ||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
e.target.closest('.item-edit-icon') ||
|
e.target.closest('.item-edit-icon') ||
|
||||||
@@ -104,16 +135,24 @@ export function MediaItemAudio(props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editMediaComponent()}
|
{!isSelectMediaMode && editMediaComponent()}
|
||||||
{viewMediaComponent()}
|
{!isSelectMediaMode && viewMediaComponent()}
|
||||||
|
|
||||||
{thumbnailComponent()}
|
{thumbnailComponent()}
|
||||||
|
|
||||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
{isSelectMediaMode ? (
|
||||||
{titleComponent()}
|
<UnderThumbWrapper>
|
||||||
{metaComponents()}
|
{titleComponent()}
|
||||||
{descriptionComponent()}
|
{metaComponents()}
|
||||||
</UnderThumbWrapper>
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
) : (
|
||||||
|
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||||
|
{titleComponent()}
|
||||||
|
{metaComponents()}
|
||||||
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
{playlistOptionsComponent()}
|
{playlistOptionsComponent()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMediaItem } from '../../utils/hooks/';
|
import { useMediaItem } from '../../utils/hooks/';
|
||||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
import { PositiveIntegerOrZero, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
import { MediaDurationInfo } from '../../utils/classes/';
|
import { MediaDurationInfo } from '../../utils/classes/';
|
||||||
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx';
|
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx';
|
||||||
import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
||||||
@@ -9,10 +9,26 @@ import { MediaItem } from './MediaItem';
|
|||||||
|
|
||||||
export function MediaItemVideo(props) {
|
export function MediaItemVideo(props) {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] =
|
const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
|
||||||
useMediaItem({ ...props, type });
|
useMediaItem({ ...props, type });
|
||||||
|
|
||||||
|
// In embed mode, override components to remove links
|
||||||
|
const ItemTitle = ({ title }) => (
|
||||||
|
<h3>
|
||||||
|
<span>{title}</span>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
|
||||||
|
|
||||||
|
const titleComponent = isSelectMediaMode
|
||||||
|
? () => <ItemTitle title={props.title} />
|
||||||
|
: titleComponentOrig;
|
||||||
|
|
||||||
|
const UnderThumbWrapper = isSelectMediaMode ? ItemMain : UnderThumbWrapperOrig;
|
||||||
|
|
||||||
const _MediaDurationInfo = new MediaDurationInfo();
|
const _MediaDurationInfo = new MediaDurationInfo();
|
||||||
|
|
||||||
_MediaDurationInfo.update(props.duration);
|
_MediaDurationInfo.update(props.duration);
|
||||||
@@ -26,6 +42,24 @@ export function MediaItemVideo(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function thumbnailComponent() {
|
function thumbnailComponent() {
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
// In select media mode, render thumbnail without link
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="item-thumb"
|
||||||
|
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||||
|
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
|
||||||
|
>
|
||||||
|
{props.inPlaylistView ? null : (
|
||||||
|
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
|
||||||
|
)}
|
||||||
|
{props.inPlaylistView || props.inPlaylistPage ? null : (
|
||||||
|
<MediaItemVideoPreviewer url={props.preview_thumbnail} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const attr = {
|
const attr = {
|
||||||
key: 'item-thumb',
|
key: 'item-thumb',
|
||||||
href: props.link,
|
href: props.link,
|
||||||
@@ -75,11 +109,11 @@ export function MediaItemVideo(props) {
|
|||||||
const finalClassname = containerClassname +
|
const finalClassname = containerClassname +
|
||||||
(props.showSelection ? ' with-selection' : '') +
|
(props.showSelection ? ' with-selection' : '') +
|
||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection || isSelectMediaMode ? ' has-any-selection' : '');
|
||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
// If there's any selection active, clicking the item should toggle selection
|
// In select media mode or if there's any selection active, clicking the item should toggle selection
|
||||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
if ((isSelectMediaMode || props.hasAnySelection) && props.onCheckboxChange) {
|
||||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
if (e.target.closest('.item-selection-checkbox') ||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
e.target.closest('.item-edit-icon') ||
|
e.target.closest('.item-edit-icon') ||
|
||||||
@@ -111,19 +145,27 @@ export function MediaItemVideo(props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editMediaComponent()}
|
{!isSelectMediaMode && editMediaComponent()}
|
||||||
{viewMediaComponent()}
|
{!isSelectMediaMode && viewMediaComponent()}
|
||||||
|
|
||||||
{props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()}
|
{props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()}
|
||||||
|
|
||||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
{isSelectMediaMode ? (
|
||||||
{titleComponent()}
|
<UnderThumbWrapper>
|
||||||
{metaComponents()}
|
{titleComponent()}
|
||||||
{descriptionComponent()}
|
{metaComponents()}
|
||||||
</UnderThumbWrapper>
|
{descriptionComponent()}
|
||||||
</div>
|
</UnderThumbWrapper>
|
||||||
|
) : (
|
||||||
|
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||||
|
{titleComponent()}
|
||||||
|
{metaComponents()}
|
||||||
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
{playlistOptionsComponent()}
|
{playlistOptionsComponent()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,32 @@ import { LinksContext, SiteConsumer } from '../../utils/contexts/';
|
|||||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||||
import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/';
|
import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/';
|
||||||
import VideoViewer from '../media-viewer/VideoViewer';
|
|
||||||
|
const EMBED_OPTIONS_STORAGE_KEY = 'mediacms_embed_options';
|
||||||
|
|
||||||
|
function loadEmbedOptions() {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem(EMBED_OPTIONS_STORAGE_KEY);
|
||||||
|
if (saved) {
|
||||||
|
return JSON.parse(saved);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore localStorage errors
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEmbedOptions(options) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(EMBED_OPTIONS_STORAGE_KEY, JSON.stringify(options));
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore localStorage errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function MediaShareEmbed(props) {
|
export function MediaShareEmbed(props) {
|
||||||
const embedVideoDimensions = PageStore.get('config-options').embedded.video.dimensions;
|
const embedVideoDimensions = PageStore.get('config-options').embedded.video.dimensions;
|
||||||
|
const savedOptions = loadEmbedOptions();
|
||||||
|
|
||||||
const links = useContext(LinksContext);
|
const links = useContext(LinksContext);
|
||||||
|
|
||||||
@@ -18,12 +40,19 @@ export function MediaShareEmbed(props) {
|
|||||||
const onRightBottomRef = useRef(null);
|
const onRightBottomRef = useRef(null);
|
||||||
|
|
||||||
const [maxHeight, setMaxHeight] = useState(window.innerHeight - 144 + 56);
|
const [maxHeight, setMaxHeight] = useState(window.innerHeight - 144 + 56);
|
||||||
const [keepAspectRatio, setKeepAspectRatio] = useState(false);
|
const [keepAspectRatio, setKeepAspectRatio] = useState(savedOptions?.keepAspectRatio ?? true);
|
||||||
const [aspectRatio, setAspectRatio] = useState('16:9');
|
const [showTitle, setShowTitle] = useState(savedOptions?.showTitle ?? true);
|
||||||
const [embedWidthValue, setEmbedWidthValue] = useState(embedVideoDimensions.width);
|
const [showRelated, setShowRelated] = useState(savedOptions?.showRelated ?? true);
|
||||||
const [embedWidthUnit, setEmbedWidthUnit] = useState(embedVideoDimensions.widthUnit);
|
const [showUserAvatar, setShowUserAvatar] = useState(savedOptions?.showUserAvatar ?? true);
|
||||||
const [embedHeightValue, setEmbedHeightValue] = useState(embedVideoDimensions.height);
|
const [linkTitle, setLinkTitle] = useState(savedOptions?.linkTitle ?? true);
|
||||||
const [embedHeightUnit, setEmbedHeightUnit] = useState(embedVideoDimensions.heightUnit);
|
const [responsive, setResponsive] = useState(savedOptions?.responsive ?? false);
|
||||||
|
const [startAt, setStartAt] = useState(false);
|
||||||
|
const [startTime, setStartTime] = useState('0:00');
|
||||||
|
const [aspectRatio, setAspectRatio] = useState(savedOptions?.aspectRatio ?? '16:9');
|
||||||
|
const [embedWidthValue, setEmbedWidthValue] = useState(savedOptions?.embedWidthValue ?? embedVideoDimensions.width);
|
||||||
|
const [embedWidthUnit, setEmbedWidthUnit] = useState(savedOptions?.embedWidthUnit ?? embedVideoDimensions.widthUnit);
|
||||||
|
const [embedHeightValue, setEmbedHeightValue] = useState(savedOptions?.embedHeightValue ?? embedVideoDimensions.height);
|
||||||
|
const [embedHeightUnit, setEmbedHeightUnit] = useState(savedOptions?.embedHeightUnit ?? embedVideoDimensions.heightUnit);
|
||||||
const [rightMiddlePositionTop, setRightMiddlePositionTop] = useState(60);
|
const [rightMiddlePositionTop, setRightMiddlePositionTop] = useState(60);
|
||||||
const [rightMiddlePositionBottom, setRightMiddlePositionBottom] = useState(60);
|
const [rightMiddlePositionBottom, setRightMiddlePositionBottom] = useState(60);
|
||||||
const [unitOptions, setUnitOptions] = useState([
|
const [unitOptions, setUnitOptions] = useState([
|
||||||
@@ -71,36 +100,65 @@ export function MediaShareEmbed(props) {
|
|||||||
setEmbedHeightUnit(newVal);
|
setEmbedHeightUnit(newVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeepAspectRatioChange() {
|
function onShowTitleChange() {
|
||||||
const newVal = !keepAspectRatio;
|
setShowTitle(!showTitle);
|
||||||
|
}
|
||||||
|
|
||||||
const arr = aspectRatio.split(':');
|
function onShowRelatedChange() {
|
||||||
const x = arr[0];
|
setShowRelated(!showRelated);
|
||||||
const y = arr[1];
|
}
|
||||||
|
|
||||||
setKeepAspectRatio(newVal);
|
function onShowUserAvatarChange() {
|
||||||
setEmbedWidthUnit(newVal ? 'px' : embedWidthUnit);
|
setShowUserAvatar(!showUserAvatar);
|
||||||
setEmbedHeightUnit(newVal ? 'px' : embedHeightUnit);
|
}
|
||||||
setEmbedHeightValue(newVal ? parseInt((embedWidthValue * y) / x, 10) : embedHeightValue);
|
|
||||||
setUnitOptions(
|
function onLinkTitleChange() {
|
||||||
newVal
|
setLinkTitle(!linkTitle);
|
||||||
? [{ key: 'px', label: 'px' }]
|
}
|
||||||
: [
|
|
||||||
{ key: 'px', label: 'px' },
|
function onResponsiveChange() {
|
||||||
{ key: 'percent', label: '%' },
|
const nextResponsive = !responsive;
|
||||||
]
|
setResponsive(nextResponsive);
|
||||||
);
|
|
||||||
|
if (!nextResponsive) {
|
||||||
|
if (aspectRatio !== 'custom') {
|
||||||
|
const arr = aspectRatio.split(':');
|
||||||
|
const x = arr[0];
|
||||||
|
const y = arr[1];
|
||||||
|
|
||||||
|
setKeepAspectRatio(true);
|
||||||
|
setEmbedHeightValue(parseInt((embedWidthValue * y) / x, 10));
|
||||||
|
} else {
|
||||||
|
setKeepAspectRatio(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setKeepAspectRatio(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStartAtChange() {
|
||||||
|
setStartAt(!startAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStartTimeChange(e) {
|
||||||
|
setStartTime(e.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAspectRatioChange() {
|
function onAspectRatioChange() {
|
||||||
const newVal = aspectRatioValueRef.current.value;
|
const newVal = aspectRatioValueRef.current.value;
|
||||||
|
|
||||||
const arr = newVal.split(':');
|
if (newVal === 'custom') {
|
||||||
const x = arr[0];
|
setAspectRatio(newVal);
|
||||||
const y = arr[1];
|
setKeepAspectRatio(false);
|
||||||
|
} else {
|
||||||
|
const arr = newVal.split(':');
|
||||||
|
const x = arr[0];
|
||||||
|
const y = arr[1];
|
||||||
|
|
||||||
setAspectRatio(newVal);
|
setAspectRatio(newVal);
|
||||||
setEmbedHeightValue(keepAspectRatio ? parseInt((embedWidthValue * y) / x, 10) : embedHeightValue);
|
setKeepAspectRatio(true);
|
||||||
|
setEmbedHeightValue(parseInt((embedWidthValue * y) / x, 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWindowResize() {
|
function onWindowResize() {
|
||||||
@@ -130,13 +188,88 @@ export function MediaShareEmbed(props) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Save embed options to localStorage when they change (except startAt/startTime)
|
||||||
|
useEffect(() => {
|
||||||
|
saveEmbedOptions({
|
||||||
|
showTitle,
|
||||||
|
showRelated,
|
||||||
|
showUserAvatar,
|
||||||
|
linkTitle,
|
||||||
|
responsive,
|
||||||
|
aspectRatio,
|
||||||
|
embedWidthValue,
|
||||||
|
embedWidthUnit,
|
||||||
|
embedHeightValue,
|
||||||
|
embedHeightUnit,
|
||||||
|
keepAspectRatio,
|
||||||
|
});
|
||||||
|
}, [showTitle, showRelated, showUserAvatar, linkTitle, responsive, aspectRatio, embedWidthValue, embedWidthUnit, embedHeightValue, embedHeightUnit, keepAspectRatio]);
|
||||||
|
|
||||||
|
function getEmbedCode() {
|
||||||
|
const mediaId = MediaPageStore.get('media-id');
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (showTitle) params.set('showTitle', '1');
|
||||||
|
else params.set('showTitle', '0');
|
||||||
|
|
||||||
|
if (showRelated) params.set('showRelated', '1');
|
||||||
|
else params.set('showRelated', '0');
|
||||||
|
|
||||||
|
if (showUserAvatar) params.set('showUserAvatar', '1');
|
||||||
|
else params.set('showUserAvatar', '0');
|
||||||
|
|
||||||
|
if (linkTitle) params.set('linkTitle', '1');
|
||||||
|
else params.set('linkTitle', '0');
|
||||||
|
|
||||||
|
if (startAt && startTime) {
|
||||||
|
const parts = startTime.split(':').reverse();
|
||||||
|
let seconds = 0;
|
||||||
|
if (parts[0]) seconds += parseInt(parts[0], 10) || 0;
|
||||||
|
if (parts[1]) seconds += (parseInt(parts[1], 10) || 0) * 60;
|
||||||
|
if (parts[2]) seconds += (parseInt(parts[2], 10) || 0) * 3600;
|
||||||
|
if (seconds > 0) params.set('t', seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
const separator = links.embed.includes('?') ? '&' : '?';
|
||||||
|
const finalUrl = `${links.embed}${mediaId}${separator}${params.toString()}`;
|
||||||
|
|
||||||
|
if (responsive) {
|
||||||
|
if (aspectRatio === 'custom') {
|
||||||
|
// Use current width/height values to calculate aspect ratio for custom
|
||||||
|
const ratio = `${embedWidthValue} / ${embedHeightValue}`;
|
||||||
|
const maxWidth = `calc(100vh * ${embedWidthValue} / ${embedHeightValue})`;
|
||||||
|
return `<iframe src="${finalUrl}" style="width:100%;max-width:${maxWidth};aspect-ratio:${ratio};display:block;margin:auto;border:0;" allowFullScreen></iframe>`;
|
||||||
|
}
|
||||||
|
const arr = aspectRatio.split(':');
|
||||||
|
const ratio = `${arr[0]} / ${arr[1]}`;
|
||||||
|
const maxWidth = `calc(100vh * ${arr[0]} / ${arr[1]})`;
|
||||||
|
return `<iframe src="${finalUrl}" style="width:100%;max-width:${maxWidth};aspect-ratio:${ratio};display:block;margin:auto;border:0;" allowFullScreen></iframe>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = 'percent' === embedWidthUnit ? embedWidthValue + '%' : embedWidthValue;
|
||||||
|
const height = 'percent' === embedHeightUnit ? embedHeightValue + '%' : embedHeightValue;
|
||||||
|
return `<iframe width="${width}" height="${height}" src="${finalUrl}" frameBorder="0" allowFullScreen></iframe>`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="share-embed" style={{ maxHeight: maxHeight + 'px' }}>
|
<div className="share-embed" style={{ maxHeight: maxHeight + 'px' }}>
|
||||||
<div className="share-embed-inner">
|
<div className="share-embed-inner">
|
||||||
<div className="on-left">
|
<div className="on-left">
|
||||||
<div className="media-embed-wrap">
|
<div className="media-embed-wrap">
|
||||||
<SiteConsumer>
|
<SiteConsumer>
|
||||||
{(site) => <VideoViewer data={MediaPageStore.get('media-data')} siteUrl={site.url} inEmbed={true} />}
|
{(site) => {
|
||||||
|
const previewUrl = `${links.embed + MediaPageStore.get('media-id')}&showTitle=${showTitle ? '1' : '0'}&showRelated=${showRelated ? '1' : '0'}&showUserAvatar=${showUserAvatar ? '1' : '0'}&linkTitle=${linkTitle ? '1' : '0'}${startAt ? '&t=' + (startTime.split(':').reverse().reduce((acc, cur, i) => acc + (parseInt(cur, 10) || 0) * Math.pow(60, i), 0)) : ''}`;
|
||||||
|
|
||||||
|
const style = {};
|
||||||
|
style.width = '100%';
|
||||||
|
style.height = '480px';
|
||||||
|
style.overflow = 'hidden';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={style}>
|
||||||
|
<iframe width="100%" height="100%" src={previewUrl} frameBorder="0" allowFullScreen></iframe>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</SiteConsumer>
|
</SiteConsumer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,16 +291,7 @@ export function MediaShareEmbed(props) {
|
|||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
readOnly
|
readOnly
|
||||||
value={
|
value={getEmbedCode()}
|
||||||
'<iframe width="' +
|
|
||||||
('percent' === embedWidthUnit ? embedWidthValue + '%' : embedWidthValue) +
|
|
||||||
'" height="' +
|
|
||||||
('percent' === embedHeightUnit ? embedHeightValue + '%' : embedHeightValue) +
|
|
||||||
'" src="' +
|
|
||||||
links.embed +
|
|
||||||
MediaPageStore.get('media-id') +
|
|
||||||
'" frameborder="0" allowfullscreen></iframe>'
|
|
||||||
}
|
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
<div className="iframe-config">
|
<div className="iframe-config">
|
||||||
@@ -179,59 +303,106 @@ export function MediaShareEmbed(props) {
|
|||||||
</div>*/}
|
</div>*/}
|
||||||
|
|
||||||
<div className="option-content">
|
<div className="option-content">
|
||||||
<div className="ratio-options">
|
<div className="ratio-options" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 10px' }}>
|
||||||
<div className="options-group">
|
<div className="options-group">
|
||||||
<label style={{ minHeight: '36px' }}>
|
<label style={{ minHeight: '36px', whiteSpace: 'nowrap' }}>
|
||||||
<input type="checkbox" checked={keepAspectRatio} onChange={onKeepAspectRatioChange} />
|
<input type="checkbox" checked={showTitle} onChange={onShowTitleChange} />
|
||||||
Keep aspect ratio
|
Show title
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!keepAspectRatio ? null : (
|
<div className="options-group">
|
||||||
<div className="options-group">
|
<label style={{ minHeight: '36px', whiteSpace: 'nowrap', opacity: showTitle ? 1 : 0.5 }}>
|
||||||
<select ref={aspectRatioValueRef} onChange={onAspectRatioChange} value={aspectRatio}>
|
<input type="checkbox" checked={linkTitle} onChange={onLinkTitleChange} disabled={!showTitle} />
|
||||||
<optgroup label="Horizontal orientation">
|
Link title
|
||||||
<option value="16:9">16:9</option>
|
</label>
|
||||||
<option value="4:3">4:3</option>
|
</div>
|
||||||
<option value="3:2">3:2</option>
|
|
||||||
</optgroup>
|
<div className="options-group">
|
||||||
<optgroup label="Vertical orientation">
|
<label style={{ minHeight: '36px', whiteSpace: 'nowrap' }}>
|
||||||
<option value="9:16">9:16</option>
|
<input type="checkbox" checked={showRelated} onChange={onShowRelatedChange} />
|
||||||
<option value="3:4">3:4</option>
|
Show related
|
||||||
<option value="2:3">2:3</option>
|
</label>
|
||||||
</optgroup>
|
</div>
|
||||||
|
|
||||||
|
<div className="options-group">
|
||||||
|
<label style={{ minHeight: '36px', whiteSpace: 'nowrap', opacity: showTitle ? 1 : 0.5 }}>
|
||||||
|
<input type="checkbox" checked={showUserAvatar} onChange={onShowUserAvatarChange} disabled={!showTitle} />
|
||||||
|
Show user avatar
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="options-group" style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<label style={{ minHeight: '36px', whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', marginRight: '10px' }}>
|
||||||
|
<input type="checkbox" checked={responsive} onChange={onResponsiveChange} />
|
||||||
|
Responsive
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="options-group" style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<label style={{ minHeight: '36px', whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', marginRight: '10px' }}>
|
||||||
|
<input type="checkbox" checked={startAt} onChange={onStartAtChange} />
|
||||||
|
Start at
|
||||||
|
</label>
|
||||||
|
{startAt && (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={startTime}
|
||||||
|
onChange={onStartTimeChange}
|
||||||
|
style={{ width: '60px', height: '28px', fontSize: '12px', padding: '2px 5px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="options-group" style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||||
|
<div style={{ fontSize: '12px', marginBottom: '4px', color: 'rgba(0,0,0,0.6)' }}>Aspect Ratio</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<select
|
||||||
|
ref={aspectRatioValueRef}
|
||||||
|
onChange={onAspectRatioChange}
|
||||||
|
value={aspectRatio}
|
||||||
|
style={{ height: '28px', fontSize: '12px' }}
|
||||||
|
>
|
||||||
|
<option value="16:9">16:9</option>
|
||||||
|
<option value="4:3">4:3</option>
|
||||||
|
<option value="3:2">3:2</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div className="options-group">
|
{!responsive && (
|
||||||
<NumericInputWithUnit
|
<>
|
||||||
valueCallback={onEmbedWidthValueChange}
|
<div className="options-group">
|
||||||
unitCallback={onEmbedWidthUnitChange}
|
<NumericInputWithUnit
|
||||||
label={'Width'}
|
valueCallback={onEmbedWidthValueChange}
|
||||||
defaultValue={parseInt(embedWidthValue, 10)}
|
unitCallback={onEmbedWidthUnitChange}
|
||||||
defaultUnit={embedWidthUnit}
|
label={'Width'}
|
||||||
minValue={1}
|
defaultValue={parseInt(embedWidthValue, 10)}
|
||||||
maxValue={99999}
|
defaultUnit={embedWidthUnit}
|
||||||
units={unitOptions}
|
minValue={1}
|
||||||
/>
|
maxValue={99999}
|
||||||
</div>
|
units={unitOptions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="options-group">
|
<div className="options-group">
|
||||||
<NumericInputWithUnit
|
<NumericInputWithUnit
|
||||||
valueCallback={onEmbedHeightValueChange}
|
valueCallback={onEmbedHeightValueChange}
|
||||||
unitCallback={onEmbedHeightUnitChange}
|
unitCallback={onEmbedHeightUnitChange}
|
||||||
label={'Height'}
|
label={'Height'}
|
||||||
defaultValue={parseInt(embedHeightValue, 10)}
|
defaultValue={parseInt(embedHeightValue, 10)}
|
||||||
defaultUnit={embedHeightUnit}
|
defaultUnit={embedHeightUnit}
|
||||||
minValue={1}
|
minValue={1}
|
||||||
maxValue={99999}
|
maxValue={99999}
|
||||||
units={unitOptions}
|
units={unitOptions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,343 +3,293 @@ import { SiteContext } from '../../utils/contexts/';
|
|||||||
import { useUser, usePopup } from '../../utils/hooks/';
|
import { useUser, usePopup } from '../../utils/hooks/';
|
||||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||||
import { formatInnerLink, publishedOnDate } from '../../utils/helpers/';
|
import { formatInnerLink, inEmbeddedApp, publishedOnDate } from '../../utils/helpers/';
|
||||||
import { PopupMain, CircleIconButton, MaterialIcon, NavigationMenuList, NavigationContentApp } from '../_shared/';
|
import { PopupMain } from '../_shared/';
|
||||||
import CommentsList from '../comments/Comments';
|
import CommentsList from '../comments/Comments';
|
||||||
import { replaceString } from '../../utils/helpers/';
|
import { replaceString } from '../../utils/helpers/';
|
||||||
import { translateString } from '../../utils/helpers/';
|
import { translateString } from '../../utils/helpers/';
|
||||||
|
|
||||||
function metafield(arr) {
|
function metafield(arr) {
|
||||||
let i;
|
let i;
|
||||||
let sep;
|
let sep;
|
||||||
let ret = [];
|
let ret = [];
|
||||||
|
|
||||||
if (arr && arr.length) {
|
if (arr && arr.length) {
|
||||||
i = 0;
|
i = 0;
|
||||||
sep = 1 < arr.length ? ', ' : '';
|
sep = 1 < arr.length ? ', ' : '';
|
||||||
while (i < arr.length) {
|
while (i < arr.length) {
|
||||||
ret[i] = (
|
ret[i] = (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
<a href={arr[i].url} title={arr[i].title}>
|
<a href={arr[i].url} title={arr[i].title}>
|
||||||
{arr[i].title}
|
{arr[i].title}
|
||||||
</a>
|
</a>
|
||||||
{i < arr.length - 1 ? sep : ''}
|
{i < arr.length - 1 ? sep : ''}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
i += 1;
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function MediaAuthorBanner(props) {
|
function MediaAuthorBanner(props) {
|
||||||
return (
|
return (
|
||||||
<div className="media-author-banner">
|
<div className="media-author-banner">
|
||||||
<div>
|
<div>
|
||||||
<a className="author-banner-thumb" href={props.link || null} title={props.name}>
|
<a className="author-banner-thumb" href={props.link || null} title={props.name}>
|
||||||
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
|
<span style={{ backgroundImage: 'url(' + props.thumb + ')' }}>
|
||||||
<img src={props.thumb} loading="lazy" alt={props.name} title={props.name} />
|
<img src={props.thumb} loading="lazy" alt={props.name} title={props.name} />
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
<a href={props.link} className="author-banner-name" title={props.name}>
|
<a href={props.link} className="author-banner-name" title={props.name}>
|
||||||
<span>{props.name}</span>
|
<span>{props.name}</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{PageStore.get('config-media-item').displayPublishDate && props.published ? (
|
{PageStore.get('config-media-item').displayPublishDate && props.published ? (
|
||||||
<span className="author-banner-date">
|
<span className="author-banner-date">
|
||||||
{translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))}
|
{translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MediaMetaField(props) {
|
function MediaMetaField(props) {
|
||||||
return (
|
return (
|
||||||
<div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}>
|
<div className={props.id.trim() ? 'media-content-' + props.id.trim() : null}>
|
||||||
<div className="media-content-field">
|
<div className="media-content-field">
|
||||||
<div className="media-content-field-label">
|
<div className="media-content-field-label">
|
||||||
<h4>{props.title}</h4>
|
<h4>{props.title}</h4>
|
||||||
|
</div>
|
||||||
|
<div className="media-content-field-content">{props.value}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="media-content-field-content">{props.value}</div>
|
);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEditMenuItems() {
|
|
||||||
const items = [];
|
|
||||||
const friendlyToken = window.MediaCMS.mediaId;
|
|
||||||
const mediaData = MediaPageStore.get('media-data');
|
|
||||||
const mediaType = mediaData ? mediaData.media_type : null;
|
|
||||||
const isVideoOrAudio = mediaType === 'video' || mediaType === 'audio';
|
|
||||||
const allowMediaReplacement = window.MediaCMS.features.media.actions.allowMediaReplacement;
|
|
||||||
|
|
||||||
// Edit metadata - always available
|
|
||||||
items.push({
|
|
||||||
itemType: 'link',
|
|
||||||
link: `/edit?m=${friendlyToken}`,
|
|
||||||
text: translateString('Edit metadata'),
|
|
||||||
icon: 'edit',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Trim - only for video/audio
|
|
||||||
if (isVideoOrAudio) {
|
|
||||||
items.push({
|
|
||||||
itemType: 'link',
|
|
||||||
link: `/edit_video?m=${friendlyToken}`,
|
|
||||||
text: translateString('Trim'),
|
|
||||||
icon: 'content_cut',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Captions - only for video/audio
|
|
||||||
if (isVideoOrAudio) {
|
|
||||||
items.push({
|
|
||||||
itemType: 'link',
|
|
||||||
link: `/add_subtitle?m=${friendlyToken}`,
|
|
||||||
text: translateString('Captions'),
|
|
||||||
icon: 'closed_caption',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chapters - only for video/audio
|
|
||||||
if (isVideoOrAudio) {
|
|
||||||
items.push({
|
|
||||||
itemType: 'link',
|
|
||||||
link: `/edit_chapters?m=${friendlyToken}`,
|
|
||||||
text: 'Chapters',
|
|
||||||
icon: 'menu_book',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish - always available
|
|
||||||
items.push({
|
|
||||||
itemType: 'link',
|
|
||||||
link: `/publish?m=${friendlyToken}`,
|
|
||||||
text: translateString('Publish'),
|
|
||||||
icon: 'publish',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace - only if enabled
|
|
||||||
if (allowMediaReplacement) {
|
|
||||||
items.push({
|
|
||||||
itemType: 'link',
|
|
||||||
link: `/replace_media?m=${friendlyToken}`,
|
|
||||||
text: translateString('Replace'),
|
|
||||||
icon: 'swap_horiz',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditMediaButton(props) {
|
function EditMediaButton(props) {
|
||||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
let link = props.link;
|
||||||
|
|
||||||
const menuItems = getEditMenuItems();
|
if (window.MediaCMS.site.devEnv) {
|
||||||
|
link = '/edit-media.html';
|
||||||
|
}
|
||||||
|
|
||||||
const popupPages = {
|
if (link && inEmbeddedApp()) {
|
||||||
main: (
|
link += '&mode=lms_embed_mode';
|
||||||
<div className="edit-options">
|
}
|
||||||
<PopupMain>
|
|
||||||
<NavigationMenuList items={menuItems} />
|
|
||||||
</PopupMain>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="edit-media-dropdown">
|
<a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon">
|
||||||
<PopupTrigger contentRef={popupContentRef}>
|
<i className="material-icons">edit</i>
|
||||||
<button className="edit-media-icon" title={translateString('Edit media')}>
|
</a>
|
||||||
<i className="material-icons">edit</i>
|
);
|
||||||
</button>
|
|
||||||
</PopupTrigger>
|
|
||||||
<PopupContent contentRef={popupContentRef}>
|
|
||||||
<NavigationContentApp
|
|
||||||
initPage="main"
|
|
||||||
focusFirstItemOnPageChange={false}
|
|
||||||
pages={popupPages}
|
|
||||||
/>
|
|
||||||
</PopupContent>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ViewerInfoContent(props) {
|
export default function ViewerInfoContent(props) {
|
||||||
const { userCan } = useUser();
|
const { userCan } = useUser();
|
||||||
|
|
||||||
const description = props.description.trim();
|
const description = props.description.trim();
|
||||||
const tagsContent =
|
const tagsContent =
|
||||||
!PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled
|
!PageStore.get('config-enabled').taxonomies.tags || PageStore.get('config-enabled').taxonomies.tags.enabled
|
||||||
? metafield(MediaPageStore.get('media-tags'))
|
? metafield(MediaPageStore.get('media-tags'))
|
||||||
: [];
|
: [];
|
||||||
const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle
|
let mediaCategories = MediaPageStore.get('media-categories');
|
||||||
? []
|
|
||||||
: !PageStore.get('config-enabled').taxonomies.categories ||
|
|
||||||
PageStore.get('config-enabled').taxonomies.categories.enabled
|
|
||||||
? metafield(MediaPageStore.get('media-categories'))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
let summary = MediaPageStore.get('media-summary');
|
// Filter to show only LMS courses when in embed mode
|
||||||
|
if (inEmbeddedApp()) {
|
||||||
summary = summary ? summary.trim() : '';
|
mediaCategories = mediaCategories.filter(cat => cat.is_lms_course === true);
|
||||||
|
|
||||||
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
|
||||||
|
|
||||||
const [hasSummary, setHasSummary] = useState('' !== summary);
|
|
||||||
const [isContentVisible, setIsContentVisible] = useState('' == summary);
|
|
||||||
|
|
||||||
function proceedMediaRemoval() {
|
|
||||||
MediaPageActions.removeMedia();
|
|
||||||
popupContentRef.current.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelMediaRemoval() {
|
|
||||||
popupContentRef.current.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMediaDelete(mediaId) {
|
|
||||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
|
||||||
setTimeout(function () {
|
|
||||||
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.href =
|
|
||||||
SiteContext._currentValue.url + '/' + MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
|
|
||||||
}, 2000);
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
if (void 0 !== mediaId) {
|
|
||||||
console.info("Removed media '" + mediaId + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMediaDeleteFail(mediaId) {
|
|
||||||
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
|
||||||
setTimeout(function () {
|
|
||||||
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
if (void 0 !== mediaId) {
|
|
||||||
console.info('Media "' + mediaId + '"' + ' removal failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClickLoadMore() {
|
|
||||||
setIsContentVisible(!isContentVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
MediaPageStore.on('media_delete', onMediaDelete);
|
|
||||||
MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
|
|
||||||
return () => {
|
|
||||||
MediaPageStore.removeListener('media_delete', onMediaDelete);
|
|
||||||
MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
|
|
||||||
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
|
|
||||||
|
|
||||||
function setTimestampAnchors(text) {
|
|
||||||
function wrapTimestampWithAnchor(match, string) {
|
|
||||||
let split = match.split(':'),
|
|
||||||
s = 0,
|
|
||||||
m = 1;
|
|
||||||
|
|
||||||
while (split.length > 0) {
|
|
||||||
s += m * parseInt(split.pop(), 10);
|
|
||||||
m *= 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrapped = `<a href="#" data-timestamp="${s}" class="video-timestamp">${match}</a>`;
|
|
||||||
return wrapped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
|
const categoriesContent = PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||||
return text.replace(timeRegex, wrapTimestampWithAnchor);
|
? []
|
||||||
}
|
: !PageStore.get('config-enabled').taxonomies.categories ||
|
||||||
|
PageStore.get('config-enabled').taxonomies.categories.enabled
|
||||||
|
? metafield(mediaCategories)
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
let summary = MediaPageStore.get('media-summary');
|
||||||
<div className="media-info-content">
|
|
||||||
{void 0 === PageStore.get('config-media-item').displayAuthor ||
|
|
||||||
null === PageStore.get('config-media-item').displayAuthor ||
|
|
||||||
!!PageStore.get('config-media-item').displayAuthor ? (
|
|
||||||
<MediaAuthorBanner link={authorLink} thumb={authorThumb} name={props.author.name} published={props.published} />
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="media-content-banner">
|
summary = summary ? summary.trim() : '';
|
||||||
<div className="media-content-banner-inner">
|
|
||||||
{hasSummary ? <div className="media-content-summary">{summary}</div> : null}
|
|
||||||
{(!hasSummary || isContentVisible) && description ? (
|
|
||||||
<div
|
|
||||||
className="media-content-description"
|
|
||||||
dangerouslySetInnerHTML={{ __html: setTimestampAnchors(description) }}
|
|
||||||
></div>
|
|
||||||
) : null}
|
|
||||||
{hasSummary ? (
|
|
||||||
<button className="load-more" onClick={onClickLoadMore}>
|
|
||||||
{isContentVisible ? 'SHOW LESS' : 'SHOW MORE'}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{tagsContent.length ? (
|
|
||||||
<MediaMetaField
|
|
||||||
value={tagsContent}
|
|
||||||
title={1 < tagsContent.length ? translateString('Tags') : translateString('Tag')}
|
|
||||||
id="tags"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{categoriesContent.length ? (
|
|
||||||
<MediaMetaField
|
|
||||||
value={categoriesContent}
|
|
||||||
title={1 < categoriesContent.length ? translateString('Categories') : translateString('Category')}
|
|
||||||
id="categories"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{userCan.editMedia ? (
|
const [popupContentRef, PopupContent, PopupTrigger] = usePopup();
|
||||||
<div className="media-author-actions">
|
|
||||||
{userCan.editMedia ? <EditMediaButton /> : null}
|
|
||||||
|
|
||||||
{userCan.deleteMedia ? (
|
const [hasSummary, setHasSummary] = useState('' !== summary);
|
||||||
<PopupTrigger contentRef={popupContentRef}>
|
const [isContentVisible, setIsContentVisible] = useState('' == summary);
|
||||||
<button className="remove-media-icon" title={translateString('Delete media')}>
|
|
||||||
<i className="material-icons">delete</i>
|
|
||||||
</button>
|
|
||||||
</PopupTrigger>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{userCan.deleteMedia ? (
|
function proceedMediaRemoval() {
|
||||||
<PopupContent contentRef={popupContentRef}>
|
MediaPageActions.removeMedia();
|
||||||
<PopupMain>
|
popupContentRef.current.toggle();
|
||||||
<div className="popup-message">
|
}
|
||||||
<span className="popup-message-title">Media removal</span>
|
|
||||||
<span className="popup-message-main">You're willing to remove media permanently?</span>
|
function cancelMediaRemoval() {
|
||||||
</div>
|
popupContentRef.current.toggle();
|
||||||
<hr />
|
}
|
||||||
<span className="popup-message-bottom">
|
|
||||||
<button className="button-link cancel-comment-removal" onClick={cancelMediaRemoval}>
|
function onMediaDelete(mediaId) {
|
||||||
CANCEL
|
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||||
</button>
|
setTimeout(function () {
|
||||||
<button className="button-link proceed-comment-removal" onClick={proceedMediaRemoval}>
|
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
|
||||||
PROCEED
|
setTimeout(function () {
|
||||||
</button>
|
window.location.href =
|
||||||
</span>
|
SiteContext._currentValue.url +
|
||||||
</PopupMain>
|
'/' +
|
||||||
</PopupContent>
|
MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
|
||||||
) : null}
|
}, 2000);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
if (void 0 !== mediaId) {
|
||||||
|
console.info("Removed media '" + mediaId + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMediaDeleteFail(mediaId) {
|
||||||
|
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
|
||||||
|
setTimeout(function () {
|
||||||
|
PageActions.addNotification('Media removal failed', 'mediaDeleteFail');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
if (void 0 !== mediaId) {
|
||||||
|
console.info('Media "' + mediaId + '"' + ' removal failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickLoadMore() {
|
||||||
|
setIsContentVisible(!isContentVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
MediaPageStore.on('media_delete', onMediaDelete);
|
||||||
|
MediaPageStore.on('media_delete_fail', onMediaDeleteFail);
|
||||||
|
return () => {
|
||||||
|
MediaPageStore.removeListener('media_delete', onMediaDelete);
|
||||||
|
MediaPageStore.removeListener('media_delete_fail', onMediaDeleteFail);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
|
||||||
|
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
|
||||||
|
|
||||||
|
function setTimestampAnchors(text) {
|
||||||
|
function wrapTimestampWithAnchor(match, string) {
|
||||||
|
let split = match.split(':'),
|
||||||
|
s = 0,
|
||||||
|
m = 1;
|
||||||
|
|
||||||
|
while (split.length > 0) {
|
||||||
|
s += m * parseInt(split.pop(), 10);
|
||||||
|
m *= 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = `<a href="#" data-timestamp="${s}" class="video-timestamp">${match}</a>`;
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
|
||||||
|
return text.replace(timeRegex, wrapTimestampWithAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="media-info-content">
|
||||||
|
{void 0 === PageStore.get('config-media-item').displayAuthor ||
|
||||||
|
null === PageStore.get('config-media-item').displayAuthor ||
|
||||||
|
!!PageStore.get('config-media-item').displayAuthor ? (
|
||||||
|
<MediaAuthorBanner
|
||||||
|
link={authorLink}
|
||||||
|
thumb={authorThumb}
|
||||||
|
name={props.author.name}
|
||||||
|
published={props.published}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="media-content-banner">
|
||||||
|
<div className="media-content-banner-inner">
|
||||||
|
{hasSummary ? <div className="media-content-summary">{summary}</div> : null}
|
||||||
|
{(!hasSummary || isContentVisible) && description ? (
|
||||||
|
<div
|
||||||
|
className="media-content-description"
|
||||||
|
dangerouslySetInnerHTML={{ __html: setTimestampAnchors(description) }}
|
||||||
|
></div>
|
||||||
|
) : null}
|
||||||
|
{hasSummary ? (
|
||||||
|
<button className="load-more" onClick={onClickLoadMore}>
|
||||||
|
{isContentVisible ? 'SHOW LESS' : 'SHOW MORE'}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{tagsContent.length ? (
|
||||||
|
<MediaMetaField
|
||||||
|
value={tagsContent}
|
||||||
|
title={1 < tagsContent.length ? translateString('Tags') : translateString('Tag')}
|
||||||
|
id="tags"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{categoriesContent.length ? (
|
||||||
|
<MediaMetaField
|
||||||
|
value={categoriesContent}
|
||||||
|
title={
|
||||||
|
inEmbeddedApp()
|
||||||
|
? (1 < categoriesContent.length
|
||||||
|
? translateString('Courses')
|
||||||
|
: translateString('Course'))
|
||||||
|
: (1 < categoriesContent.length
|
||||||
|
? translateString('Categories')
|
||||||
|
: translateString('Category'))
|
||||||
|
}
|
||||||
|
id="categories"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{userCan.editMedia ? (
|
||||||
|
<div className="media-author-actions">
|
||||||
|
{userCan.editMedia ? (
|
||||||
|
<EditMediaButton link={MediaPageStore.get('media-data').edit_url} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{userCan.deleteMedia ? (
|
||||||
|
<PopupTrigger contentRef={popupContentRef}>
|
||||||
|
<button className="remove-media-icon" title={translateString('Delete media')}>
|
||||||
|
<i className="material-icons">delete</i>
|
||||||
|
</button>
|
||||||
|
</PopupTrigger>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{userCan.deleteMedia ? (
|
||||||
|
<PopupContent contentRef={popupContentRef}>
|
||||||
|
<PopupMain>
|
||||||
|
<div className="popup-message">
|
||||||
|
<span className="popup-message-title">Media removal</span>
|
||||||
|
<span className="popup-message-main">
|
||||||
|
You're willing to remove media permanently?
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<span className="popup-message-bottom">
|
||||||
|
<button
|
||||||
|
className="button-link cancel-comment-removal"
|
||||||
|
onClick={cancelMediaRemoval}
|
||||||
|
>
|
||||||
|
CANCEL
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="button-link proceed-comment-removal"
|
||||||
|
onClick={proceedMediaRemoval}
|
||||||
|
>
|
||||||
|
PROCEED
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</PopupMain>
|
||||||
|
</PopupContent>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CommentsList />
|
<CommentsList />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,8 +183,7 @@ export default class ViewerInfoTitleBanner extends React.PureComponent {
|
|||||||
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={false} /> : null}
|
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={false} /> : null}
|
||||||
|
|
||||||
{!MemberContext._currentValue.is.anonymous &&
|
{!MemberContext._currentValue.is.anonymous &&
|
||||||
MemberContext._currentValue.can.saveMedia &&
|
MemberContext._currentValue.can.saveMedia ? (
|
||||||
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
|
|
||||||
<MediaSaveButton />
|
<MediaSaveButton />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +1,117 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { formatViewsNumber } from '../../utils/helpers/';
|
import { formatViewsNumber, inEmbeddedApp } from '../../utils/helpers/';
|
||||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||||
import { MemberContext, PlaylistsContext } from '../../utils/contexts/';
|
import { MemberContext, PlaylistsContext } from '../../utils/contexts/';
|
||||||
import { MediaLikeIcon, MediaDislikeIcon, OtherMediaDownloadLink, VideoMediaDownloadLink, MediaSaveButton, MediaShareButton, MediaMoreOptionsIcon } from '../media-actions/';
|
import {
|
||||||
|
MediaLikeIcon,
|
||||||
|
MediaDislikeIcon,
|
||||||
|
OtherMediaDownloadLink,
|
||||||
|
VideoMediaDownloadLink,
|
||||||
|
MediaSaveButton,
|
||||||
|
MediaShareButton,
|
||||||
|
MediaMoreOptionsIcon,
|
||||||
|
} from '../media-actions/';
|
||||||
import ViewerInfoTitleBanner from './ViewerInfoTitleBanner';
|
import ViewerInfoTitleBanner from './ViewerInfoTitleBanner';
|
||||||
import { translateString } from '../../utils/helpers/';
|
import { translateString } from '../../utils/helpers/';
|
||||||
|
|
||||||
export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner {
|
export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner {
|
||||||
render() {
|
render() {
|
||||||
const displayViews = PageStore.get('config-options').pages.media.displayViews && void 0 !== this.props.views;
|
const displayViews = PageStore.get('config-options').pages.media.displayViews && void 0 !== this.props.views;
|
||||||
|
|
||||||
const mediaData = MediaPageStore.get('media-data');
|
const mediaData = MediaPageStore.get('media-data');
|
||||||
const mediaState = mediaData.state;
|
const mediaState = mediaData.state;
|
||||||
const isShared = mediaData.is_shared;
|
const isShared = mediaData.is_shared;
|
||||||
|
|
||||||
let stateTooltip = '';
|
let stateTooltip = '';
|
||||||
|
|
||||||
switch (mediaState) {
|
switch (mediaState) {
|
||||||
case 'private':
|
case 'private':
|
||||||
stateTooltip = 'The site admins have to make its access public';
|
stateTooltip = 'The site admins have to make its access public';
|
||||||
break;
|
break;
|
||||||
case 'unlisted':
|
case 'unlisted':
|
||||||
stateTooltip = 'The site admins have to make it appear on listings';
|
stateTooltip = 'The site admins have to make it appear on listings';
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedTooltip = 'This media is shared with specific users or categories';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="media-title-banner">
|
||||||
|
{displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||||
|
? this.mediaCategories(true)
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{void 0 !== this.props.title ? <h1>{this.props.title}</h1> : null}
|
||||||
|
|
||||||
|
{isShared || 'public' !== mediaState ? (
|
||||||
|
<div className="media-labels-area">
|
||||||
|
<div className="media-labels-area-inner">
|
||||||
|
{isShared ? (
|
||||||
|
<>
|
||||||
|
<span className="media-label-state">
|
||||||
|
<span>shared</span>
|
||||||
|
</span>
|
||||||
|
<span className="helper-icon" data-tooltip={sharedTooltip}>
|
||||||
|
<i className="material-icons">help_outline</i>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : 'public' !== mediaState ? (
|
||||||
|
<>
|
||||||
|
<span className="media-label-state">
|
||||||
|
<span>{mediaState}</span>
|
||||||
|
</span>
|
||||||
|
<span className="helper-icon" data-tooltip={stateTooltip}>
|
||||||
|
<i className="material-icons">help_outline</i>
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'media-views-actions' +
|
||||||
|
(this.state.likedMedia ? ' liked-media' : '') +
|
||||||
|
(this.state.dislikedMedia ? ' disliked-media' : '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
||||||
|
? this.mediaCategories()
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{displayViews ? (
|
||||||
|
<div className="media-views">
|
||||||
|
{formatViewsNumber(this.props.views, true)}{' '}
|
||||||
|
{1 >= this.props.views ? translateString('view') : translateString('views')}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="media-actions">
|
||||||
|
<div>
|
||||||
|
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
|
||||||
|
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
|
||||||
|
{!inEmbeddedApp() && MemberContext._currentValue.can.shareMedia ? (
|
||||||
|
<MediaShareButton isVideo={true} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!MemberContext._currentValue.is.anonymous &&
|
||||||
|
MemberContext._currentValue.can.saveMedia ? (
|
||||||
|
<MediaSaveButton />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{!this.props.allowDownload || !MemberContext._currentValue.can.downloadMedia ? null : !this
|
||||||
|
.downloadLink ? (
|
||||||
|
<VideoMediaDownloadLink />
|
||||||
|
) : (
|
||||||
|
<OtherMediaDownloadLink link={this.downloadLink} title={this.downloadFilename} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<MediaMoreOptionsIcon allowDownload={this.props.allowDownload} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharedTooltip = 'This media is shared with specific users or categories';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="media-title-banner">
|
|
||||||
{displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
|
||||||
? this.mediaCategories(true)
|
|
||||||
: null}
|
|
||||||
|
|
||||||
{void 0 !== this.props.title ? <h1>{this.props.title}</h1> : null}
|
|
||||||
|
|
||||||
{isShared || 'public' !== mediaState ? (
|
|
||||||
<div className="media-labels-area">
|
|
||||||
<div className="media-labels-area-inner">
|
|
||||||
{isShared ? (
|
|
||||||
<>
|
|
||||||
<span className="media-label-state">
|
|
||||||
<span>shared</span>
|
|
||||||
</span>
|
|
||||||
<span className="helper-icon" data-tooltip={sharedTooltip}>
|
|
||||||
<i className="material-icons">help_outline</i>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : 'public' !== mediaState ? (
|
|
||||||
<>
|
|
||||||
<span className="media-label-state">
|
|
||||||
<span>{mediaState}</span>
|
|
||||||
</span>
|
|
||||||
<span className="helper-icon" data-tooltip={stateTooltip}>
|
|
||||||
<i className="material-icons">help_outline</i>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
'media-views-actions' +
|
|
||||||
(this.state.likedMedia ? ' liked-media' : '') +
|
|
||||||
(this.state.dislikedMedia ? ' disliked-media' : '')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!displayViews && PageStore.get('config-options').pages.media.categoriesWithTitle
|
|
||||||
? this.mediaCategories()
|
|
||||||
: null}
|
|
||||||
|
|
||||||
{displayViews ? (
|
|
||||||
<div className="media-views">
|
|
||||||
{formatViewsNumber(this.props.views, true)} {1 >= this.props.views ? translateString('view') : translateString('views')}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="media-actions">
|
|
||||||
<div>
|
|
||||||
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
|
|
||||||
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
|
|
||||||
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={true} /> : null}
|
|
||||||
|
|
||||||
{!MemberContext._currentValue.is.anonymous &&
|
|
||||||
MemberContext._currentValue.can.saveMedia &&
|
|
||||||
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
|
|
||||||
<MediaSaveButton />
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{!this.props.allowDownload || !MemberContext._currentValue.can.downloadMedia ? null : !this
|
|
||||||
.downloadLink ? (
|
|
||||||
<VideoMediaDownloadLink />
|
|
||||||
) : (
|
|
||||||
<OtherMediaDownloadLink link={this.downloadLink} title={this.downloadFilename} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<MediaMoreOptionsIcon allowDownload={this.props.allowDownload} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MediaPageStore } from '../../utils/stores/';
|
|
||||||
import { AutoPlay } from './AutoPlay';
|
|
||||||
import { RelatedMedia } from './RelatedMedia';
|
|
||||||
import PlaylistView from './PlaylistView';
|
import PlaylistView from './PlaylistView';
|
||||||
|
|
||||||
export default class ViewerSidebar extends React.PureComponent {
|
export default class ViewerSidebar extends React.PureComponent {
|
||||||
@@ -12,8 +9,6 @@ export default class ViewerSidebar extends React.PureComponent {
|
|||||||
playlistData: props.playlistData,
|
playlistData: props.playlistData,
|
||||||
isPlaylistPage: !!props.playlistData,
|
isPlaylistPage: !!props.playlistData,
|
||||||
activeItem: 0,
|
activeItem: 0,
|
||||||
mediaType: MediaPageStore.get('media-type'),
|
|
||||||
chapters: MediaPageStore.get('media-data')?.chapters
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.playlistData) {
|
if (props.playlistData) {
|
||||||
@@ -28,18 +23,6 @@ export default class ViewerSidebar extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onMediaLoad = this.onMediaLoad.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMediaLoad() {
|
|
||||||
this.setState({
|
|
||||||
mediaType: MediaPageStore.get('media-type'),
|
|
||||||
chapters: MediaPageStore.get('media-data')?.chapter_data || []
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -47,10 +30,7 @@ export default class ViewerSidebar extends React.PureComponent {
|
|||||||
<div className="viewer-sidebar">
|
<div className="viewer-sidebar">
|
||||||
{this.state.isPlaylistPage ? (
|
{this.state.isPlaylistPage ? (
|
||||||
<PlaylistView activeItem={this.state.activeItem} playlistData={this.props.playlistData} />
|
<PlaylistView activeItem={this.state.activeItem} playlistData={this.props.playlistData} />
|
||||||
) : 'video' === this.state.mediaType || 'audio' === this.state.mediaType ? (
|
|
||||||
<AutoPlay />
|
|
||||||
) : null}
|
) : null}
|
||||||
<RelatedMedia hideFirst={!this.state.isPlaylistPage} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { VideoViewerActions } from '../../../utils/actions/';
|
import { VideoViewerActions } from '../../../utils/actions/';
|
||||||
import { SiteContext, SiteConsumer } from '../../../utils/contexts/';
|
import { SiteContext, SiteConsumer } from '../../../utils/contexts/';
|
||||||
import { PageStore, MediaPageStore, VideoViewerStore } from '../../../utils/stores/';
|
import { PageStore, MediaPageStore, VideoViewerStore } from '../../../utils/stores/';
|
||||||
import { addClassname, removeClassname, formatInnerLink } from '../../../utils/helpers/';
|
import { addClassname, removeClassname, formatInnerLink, inEmbeddedApp } from '../../../utils/helpers/';
|
||||||
import { BrowserCache, UpNextLoaderView, MediaDurationInfo } from '../../../utils/classes/';
|
import { BrowserCache, UpNextLoaderView, MediaDurationInfo } from '../../../utils/classes/';
|
||||||
import {
|
import {
|
||||||
orderedSupportedVideoFormats,
|
orderedSupportedVideoFormats,
|
||||||
@@ -176,11 +176,13 @@ export default class VideoViewer extends React.PureComponent {
|
|||||||
topLeftHtml = document.createElement('div');
|
topLeftHtml = document.createElement('div');
|
||||||
topLeftHtml.setAttribute('class', 'media-links-top-left');
|
topLeftHtml.setAttribute('class', 'media-links-top-left');
|
||||||
|
|
||||||
|
const linkTarget = inEmbeddedApp() || window.location.href.indexOf('lms_embed_mode') > -1 ? '_self' : '_blank';
|
||||||
|
|
||||||
if (titleLink) {
|
if (titleLink) {
|
||||||
titleLink.setAttribute('class', 'title-link');
|
titleLink.setAttribute('class', 'title-link');
|
||||||
titleLink.setAttribute('href', this.props.data.url);
|
titleLink.setAttribute('href', this.props.data.url);
|
||||||
titleLink.setAttribute('title', this.props.data.title);
|
titleLink.setAttribute('title', this.props.data.title);
|
||||||
titleLink.setAttribute('target', '_blank');
|
titleLink.setAttribute('target', linkTarget);
|
||||||
titleLink.innerHTML = this.props.data.title;
|
titleLink.innerHTML = this.props.data.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ export default class VideoViewer extends React.PureComponent {
|
|||||||
formatInnerLink(this.props.data.author_profile, this.props.siteUrl)
|
formatInnerLink(this.props.data.author_profile, this.props.siteUrl)
|
||||||
);
|
);
|
||||||
userThumbLink.setAttribute('title', this.props.data.author_name);
|
userThumbLink.setAttribute('title', this.props.data.author_name);
|
||||||
userThumbLink.setAttribute('target', '_blank');
|
userThumbLink.setAttribute('target', linkTarget);
|
||||||
userThumbLink.setAttribute(
|
userThumbLink.setAttribute(
|
||||||
'style',
|
'style',
|
||||||
'background-image:url(' +
|
'background-image:url(' +
|
||||||
@@ -410,8 +412,12 @@ export default class VideoViewer extends React.PureComponent {
|
|||||||
poster: this.videoPoster,
|
poster: this.videoPoster,
|
||||||
previewSprite: previewSprite,
|
previewSprite: previewSprite,
|
||||||
subtitlesInfo: this.props.data.subtitles_info,
|
subtitlesInfo: this.props.data.subtitles_info,
|
||||||
enableAutoplay: !this.props.inEmbed,
|
|
||||||
inEmbed: this.props.inEmbed,
|
inEmbed: this.props.inEmbed,
|
||||||
|
showTitle: this.props.showTitle,
|
||||||
|
showRelated: this.props.showRelated,
|
||||||
|
showUserAvatar: this.props.showUserAvatar,
|
||||||
|
linkTitle: this.props.linkTitle,
|
||||||
|
urlTimestamp: this.props.timestamp,
|
||||||
hasTheaterMode: !this.props.inEmbed,
|
hasTheaterMode: !this.props.inEmbed,
|
||||||
hasNextLink: !!nextLink,
|
hasNextLink: !!nextLink,
|
||||||
nextLink: nextLink,
|
nextLink: nextLink,
|
||||||
@@ -435,9 +441,19 @@ export default class VideoViewer extends React.PureComponent {
|
|||||||
|
|
||||||
VideoViewer.defaultProps = {
|
VideoViewer.defaultProps = {
|
||||||
inEmbed: !0,
|
inEmbed: !0,
|
||||||
|
showTitle: !0,
|
||||||
|
showRelated: !0,
|
||||||
|
showUserAvatar: !0,
|
||||||
|
linkTitle: !0,
|
||||||
|
timestamp: null,
|
||||||
siteUrl: PropTypes.string.isRequired,
|
siteUrl: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoViewer.propTypes = {
|
VideoViewer.propTypes = {
|
||||||
inEmbed: PropTypes.bool,
|
inEmbed: PropTypes.bool,
|
||||||
|
showTitle: PropTypes.bool,
|
||||||
|
showRelated: PropTypes.bool,
|
||||||
|
showUserAvatar: PropTypes.bool,
|
||||||
|
linkTitle: PropTypes.bool,
|
||||||
|
timestamp: PropTypes.number,
|
||||||
};
|
};
|
||||||
@@ -88,7 +88,7 @@ function UploadMediaButton({ user, links }) {
|
|||||||
{
|
{
|
||||||
link: '/record_screen',
|
link: '/record_screen',
|
||||||
icon: 'videocam',
|
icon: 'videocam',
|
||||||
text: translateString('Record Screen'),
|
text: translateString('Record'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
.page-main-wrap {
|
.page-main-wrap {
|
||||||
padding-top: var(--header-height);
|
padding-top: var(--header-height);
|
||||||
will-change: padding-left;
|
will-change: padding-left;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.visible-sidebar & {
|
||||||
|
padding-left: var(--sidebar-width);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible-sidebar #page-media & {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.visible-sidebar & {
|
.visible-sidebar & {
|
||||||
padding-left: var(--sidebar-width);
|
#page-media {
|
||||||
opacity: 1;
|
padding-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.visible-sidebar #page-media & {
|
body.sliding-sidebar & {
|
||||||
padding-left: 0;
|
transition-property: padding-left;
|
||||||
}
|
transition-duration: 0.2s;
|
||||||
|
|
||||||
.visible-sidebar & {
|
|
||||||
#page-media {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
body.sliding-sidebar & {
|
.embedded-app & {
|
||||||
transition-property: padding-left;
|
padding-top: 0;
|
||||||
transition-duration: 0.2s;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-profile-media,
|
#page-profile-media,
|
||||||
@@ -30,20 +35,20 @@
|
|||||||
#page-profile-about,
|
#page-profile-about,
|
||||||
#page-liked.profile-page-liked,
|
#page-liked.profile-page-liked,
|
||||||
#page-history.profile-page-history {
|
#page-history.profile-page-history {
|
||||||
.page-main {
|
.page-main {
|
||||||
min-height: calc(100vh - var(--header-height));
|
min-height: calc(100vh - var(--header-height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-main {
|
.page-main {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-main-inner {
|
.page-main-inner {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 1em 1em 0 1em;
|
margin: 1em 1em 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#page-profile-media,
|
#page-profile-media,
|
||||||
@@ -51,7 +56,7 @@
|
|||||||
#page-profile-about,
|
#page-profile-about,
|
||||||
#page-liked.profile-page-liked,
|
#page-liked.profile-page-liked,
|
||||||
#page-history.profile-page-history {
|
#page-history.profile-page-history {
|
||||||
.page-main-wrap {
|
.page-main-wrap {
|
||||||
background-color: var(--body-bg-color);
|
background-color: var(--body-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -656,30 +656,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.fixed-nav {
|
|
||||||
.profile-info-nav-wrap {
|
|
||||||
padding-bottom: $_authorPage-navHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-nav {
|
|
||||||
z-index: 3;
|
|
||||||
position: fixed;
|
|
||||||
top: var(--header-height);
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.visible-sidebar & {
|
|
||||||
padding-left: var(--sidebar-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sliding-sidebar & {
|
|
||||||
transition-property: padding-left;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-main {
|
.page-main {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,14 +3,14 @@ import { ApiUrlConsumer } from '../utils/contexts/';
|
|||||||
import { MediaListWrapper } from '../components/MediaListWrapper';
|
import { MediaListWrapper } from '../components/MediaListWrapper';
|
||||||
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
|
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { translateString } from '../utils/helpers/';
|
import { translateString, inEmbeddedApp } from '../utils/helpers/';
|
||||||
|
|
||||||
interface CategoriesPageProps {
|
interface CategoriesPageProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CategoriesPage: React.FC<CategoriesPageProps> = ({ id = 'categories', title = translateString('Categories') }) => (
|
export const CategoriesPage: React.FC<CategoriesPageProps> = ({ id = 'categories', title = inEmbeddedApp() ? translateString('Courses') : translateString('Categories') }) => (
|
||||||
<Page id={id}>
|
<Page id={id}>
|
||||||
<ApiUrlConsumer>
|
<ApiUrlConsumer>
|
||||||
{(apiUrl) => (
|
{(apiUrl) => (
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const EmbedPage: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="embed-wrap" style={wrapperStyles}>
|
<div className="embed-wrap media-embed-wrap" style={wrapperStyles}>
|
||||||
{failedMediaLoad && (
|
{failedMediaLoad && (
|
||||||
<div className="player-container player-container-error" style={containerStyles}>
|
<div className="player-container player-container-error" style={containerStyles}>
|
||||||
<div className="player-container-inner" style={containerStyles}>
|
<div className="player-container-inner" style={containerStyles}>
|
||||||
@@ -59,9 +59,32 @@ export const EmbedPage: React.FC = () => {
|
|||||||
|
|
||||||
{loadedVideo && (
|
{loadedVideo && (
|
||||||
<SiteConsumer>
|
<SiteConsumer>
|
||||||
{(site) => (
|
{(site) => {
|
||||||
<VideoViewer data={MediaPageStore.get('media-data')} siteUrl={site.url} containerStyles={containerStyles} />
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
)}
|
const urlShowTitle = urlParams.get('showTitle');
|
||||||
|
const showTitle = urlShowTitle !== '0';
|
||||||
|
const urlShowRelated = urlParams.get('showRelated');
|
||||||
|
const showRelated = urlShowRelated !== '0';
|
||||||
|
const urlShowUserAvatar = urlParams.get('showUserAvatar');
|
||||||
|
const showUserAvatar = urlShowUserAvatar !== '0';
|
||||||
|
const urlLinkTitle = urlParams.get('linkTitle');
|
||||||
|
const linkTitle = urlLinkTitle !== '0';
|
||||||
|
const urlTimestamp = urlParams.get('t');
|
||||||
|
const timestamp = urlTimestamp ? parseInt(urlTimestamp, 10) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoViewer
|
||||||
|
data={MediaPageStore.get('media-data')}
|
||||||
|
siteUrl={site.url}
|
||||||
|
containerStyles={containerStyles}
|
||||||
|
showTitle={showTitle}
|
||||||
|
showRelated={showRelated}
|
||||||
|
showUserAvatar={showUserAvatar}
|
||||||
|
linkTitle={linkTitle}
|
||||||
|
timestamp={timestamp}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</SiteConsumer>
|
</SiteConsumer>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import UrlParse from 'url-parse';
|
import UrlParse from 'url-parse';
|
||||||
import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/';
|
import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/';
|
||||||
import { formatInnerLink, csrfToken, postRequest } from '../utils/helpers/';
|
import { formatInnerLink, csrfToken, postRequest, inEmbeddedApp } from '../utils/helpers/';
|
||||||
import { PageActions } from '../utils/actions/';
|
import { PageActions } from '../utils/actions/';
|
||||||
import { PageStore, ProfilePageStore } from '../utils/stores/';
|
import { PageStore, ProfilePageStore } from '../utils/stores/';
|
||||||
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
||||||
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
||||||
import { MediaListRow } from '../components/MediaListRow';
|
import { MediaListRow } from '../components/MediaListRow';
|
||||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||||
|
|
||||||
class ChannelContactForm extends React.PureComponent {
|
class ChannelContactForm extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -149,7 +149,7 @@ class ChannelContactForm extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProfileAboutPage extends ProfileMediaPage {
|
export class ProfileAboutPage extends ProfileMediaPageBase {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props, 'author-about');
|
super(props, 'author-about');
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ export class ProfileAboutPage extends ProfileMediaPage {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" hideChannelBanner={inEmbeddedApp()} />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>
|
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ApiUrlConsumer } from '../utils/contexts/';
|
import { ApiUrlConsumer } from '../utils/contexts/';
|
||||||
import { PageStore } from '../utils/stores/';
|
import { PageStore } from '../utils/stores/';
|
||||||
|
import { inEmbeddedApp } from '../utils/helpers/';
|
||||||
import { MediaListWrapper } from '../components/MediaListWrapper';
|
import { MediaListWrapper } from '../components/MediaListWrapper';
|
||||||
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
||||||
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
||||||
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
|
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
|
||||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||||
|
|
||||||
export class ProfileHistoryPage extends ProfileMediaPage {
|
export class ProfileHistoryPage extends ProfileMediaPageBase {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props, 'author-history');
|
super(props, 'author-history');
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ export class ProfileHistoryPage extends ProfileMediaPage {
|
|||||||
pageContent() {
|
pageContent() {
|
||||||
return [
|
return [
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" hideChannelBanner={inEmbeddedApp()} />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ApiUrlConsumer } from '../utils/contexts/';
|
import { ApiUrlConsumer } from '../utils/contexts/';
|
||||||
import { PageStore } from '../utils/stores/';
|
import { PageStore } from '../utils/stores/';
|
||||||
|
import { inEmbeddedApp } from '../utils/helpers/';
|
||||||
import { MediaListWrapper } from '../components/MediaListWrapper';
|
import { MediaListWrapper } from '../components/MediaListWrapper';
|
||||||
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
||||||
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
||||||
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
|
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
|
||||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||||
|
|
||||||
export class ProfileLikedPage extends ProfileMediaPage {
|
export class ProfileLikedPage extends ProfileMediaPageBase {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props, 'author-liked');
|
super(props, 'author-liked');
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ export class ProfileLikedPage extends ProfileMediaPage {
|
|||||||
pageContent() {
|
pageContent() {
|
||||||
return [
|
return [
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" hideChannelBanner={inEmbeddedApp()} />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ApiUrlConsumer } from '../utils/contexts/';
|
import { ApiUrlConsumer } from '../utils/contexts/';
|
||||||
import { PageStore } from '../utils/stores/';
|
import { PageStore } from '../utils/stores/';
|
||||||
|
import { inEmbeddedApp } from '../utils/helpers/';
|
||||||
import { MediaListWrapper } from '../components/MediaListWrapper';
|
import { MediaListWrapper } from '../components/MediaListWrapper';
|
||||||
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
||||||
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
||||||
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
|
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
|
||||||
import { ProfileMediaPage } from './ProfileMediaPage';
|
import { ProfileMediaPageBase } from './ProfileMediaPage';
|
||||||
|
|
||||||
export class ProfilePlaylistsPage extends ProfileMediaPage {
|
export class ProfilePlaylistsPage extends ProfileMediaPageBase {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props, 'author-playlists');
|
super(props, 'author-playlists');
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ export class ProfilePlaylistsPage extends ProfileMediaPage {
|
|||||||
pageContent() {
|
pageContent() {
|
||||||
return [
|
return [
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" />
|
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" hideChannelBanner={inEmbeddedApp()} />
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFi
|
|||||||
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
|
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
|
||||||
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
|
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
|
||||||
import { BulkActionsModals } from '../components/BulkActionsModals';
|
import { BulkActionsModals } from '../components/BulkActionsModals';
|
||||||
import { translateString } from '../utils/helpers';
|
import { inEmbeddedApp, inSelectMediaEmbedMode } from '../utils/helpers';
|
||||||
import { withBulkActions } from '../utils/hoc/withBulkActions';
|
import { withBulkActions } from '../utils/hoc/withBulkActions';
|
||||||
|
|
||||||
import { Page } from './_Page';
|
import { Page } from './_Page';
|
||||||
@@ -19,400 +19,491 @@ import { Page } from './_Page';
|
|||||||
import '../components/profile-page/ProfilePage.scss';
|
import '../components/profile-page/ProfilePage.scss';
|
||||||
|
|
||||||
function EmptySharedByMe(props) {
|
function EmptySharedByMe(props) {
|
||||||
return (
|
return (
|
||||||
<LinksConsumer>
|
<LinksConsumer>
|
||||||
{(links) => (
|
{(links) => (
|
||||||
<div className="empty-media empty-channel-media">
|
<div className="empty-media empty-channel-media">
|
||||||
<div className="welcome-title">No shared media</div>
|
<div className="welcome-title">No shared media</div>
|
||||||
<div className="start-uploading">
|
<div className="start-uploading">Media that you have shared with others will show up here.</div>
|
||||||
Media that you have shared with others will show up here.
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</LinksConsumer>
|
||||||
)}
|
);
|
||||||
</LinksConsumer>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileSharedByMePage extends Page {
|
class ProfileSharedByMePage extends Page {
|
||||||
constructor(props, pageSlug) {
|
constructor(props, pageSlug) {
|
||||||
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me');
|
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me');
|
||||||
|
|
||||||
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me';
|
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-by-me';
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
channelMediaCount: -1,
|
channelMediaCount: -1,
|
||||||
author: ProfilePageStore.get('author-data'),
|
author: ProfilePageStore.get('author-data'),
|
||||||
uploadsPreviewItemsCount: 0,
|
uploadsPreviewItemsCount: 0,
|
||||||
title: this.props.title,
|
title: this.props.title,
|
||||||
query: ProfilePageStore.get('author-query'),
|
query: ProfilePageStore.get('author-query'),
|
||||||
requestUrl: null,
|
requestUrl: null,
|
||||||
hiddenFilters: true,
|
hiddenFilters: true,
|
||||||
hiddenTags: true,
|
hiddenTags: true,
|
||||||
hiddenSorting: true,
|
hiddenSorting: true,
|
||||||
filterArgs: '',
|
filterArgs: '',
|
||||||
availableTags: [],
|
availableTags: [],
|
||||||
selectedTag: 'all',
|
selectedTag: 'all',
|
||||||
selectedSort: 'date_added_desc',
|
selectedSort: 'date_added_desc',
|
||||||
};
|
selectedMedia: new Set(), // For select media mode
|
||||||
|
};
|
||||||
|
|
||||||
this.authorDataLoad = this.authorDataLoad.bind(this);
|
this.authorDataLoad = this.authorDataLoad.bind(this);
|
||||||
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
|
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
|
||||||
this.getCountFunc = this.getCountFunc.bind(this);
|
this.getCountFunc = this.getCountFunc.bind(this);
|
||||||
this.changeRequestQuery = this.changeRequestQuery.bind(this);
|
this.changeRequestQuery = this.changeRequestQuery.bind(this);
|
||||||
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
|
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
|
||||||
this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
|
this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
|
||||||
this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
|
this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
|
||||||
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
|
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
|
||||||
this.onTagSelect = this.onTagSelect.bind(this);
|
this.onTagSelect = this.onTagSelect.bind(this);
|
||||||
this.onSortSelect = this.onSortSelect.bind(this);
|
this.onSortSelect = this.onSortSelect.bind(this);
|
||||||
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
|
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
|
||||||
|
this.handleMediaSelection = this.handleMediaSelection.bind(this);
|
||||||
|
|
||||||
ProfilePageStore.on('load-author-data', this.authorDataLoad);
|
ProfilePageStore.on('load-author-data', this.authorDataLoad);
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
ProfilePageActions.load_author_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
authorDataLoad() {
|
|
||||||
const author = ProfilePageStore.get('author-data');
|
|
||||||
|
|
||||||
let requestUrl = this.state.requestUrl;
|
|
||||||
|
|
||||||
if (author) {
|
|
||||||
if (this.state.query) {
|
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
|
||||||
} else {
|
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me' + this.state.filterArgs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
componentDidMount() {
|
||||||
author: author,
|
ProfilePageActions.load_author_data();
|
||||||
requestUrl: requestUrl,
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
authorDataLoad() {
|
||||||
this.setState({
|
const author = ProfilePageStore.get('author-data');
|
||||||
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getCountFunc(count) {
|
let requestUrl = this.state.requestUrl;
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
channelMediaCount: count,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (this.state.query) {
|
|
||||||
let title = '';
|
|
||||||
|
|
||||||
if (!count) {
|
if (author) {
|
||||||
title = 'No results for "' + this.state.query + '"';
|
if (this.state.query) {
|
||||||
} else if (1 === count) {
|
requestUrl =
|
||||||
title = '1 result for "' + this.state.query + '"';
|
ApiUrlContext._currentValue.media +
|
||||||
} else {
|
'?author=' +
|
||||||
title = count + ' results for "' + this.state.query + '"';
|
author.id +
|
||||||
}
|
'&show=shared_by_me&q=' +
|
||||||
|
encodeURIComponent(this.state.query) +
|
||||||
this.setState({
|
this.state.filterArgs;
|
||||||
title: title,
|
} else {
|
||||||
});
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
author.id +
|
||||||
|
'&show=shared_by_me' +
|
||||||
|
this.state.filterArgs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeRequestQuery(newQuery) {
|
this.setState({
|
||||||
if (!this.state.author) {
|
author: author,
|
||||||
return;
|
requestUrl: requestUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestUrl;
|
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
||||||
|
this.setState({
|
||||||
if (newQuery) {
|
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs;
|
});
|
||||||
} else {
|
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = this.state.title;
|
getCountFunc(count) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
channelMediaCount: count,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (this.state.query) {
|
||||||
|
let title = '';
|
||||||
|
|
||||||
if ('' === newQuery) {
|
if (!count) {
|
||||||
title = this.props.title;
|
title = 'No results for "' + this.state.query + '"';
|
||||||
|
} else if (1 === count) {
|
||||||
|
title = '1 result for "' + this.state.query + '"';
|
||||||
|
} else {
|
||||||
|
title = count + ' results for "' + this.state.query + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
changeRequestQuery(newQuery) {
|
||||||
requestUrl: requestUrl,
|
|
||||||
query: newQuery,
|
|
||||||
title: title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleFiltersClick() {
|
|
||||||
this.setState({
|
|
||||||
hiddenFilters: !this.state.hiddenFilters,
|
|
||||||
hiddenTags: true,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleTagsClick() {
|
|
||||||
this.setState({
|
|
||||||
hiddenFilters: true,
|
|
||||||
hiddenTags: !this.state.hiddenTags,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleSortingClick() {
|
|
||||||
this.setState({
|
|
||||||
hiddenFilters: true,
|
|
||||||
hiddenTags: true,
|
|
||||||
hiddenSorting: !this.state.hiddenSorting,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onTagSelect(tag) {
|
|
||||||
this.setState({ selectedTag: tag }, () => {
|
|
||||||
this.onFiltersUpdate({
|
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
|
||||||
sort_by: this.state.selectedSort,
|
|
||||||
tag: tag,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSortSelect(sortBy) {
|
|
||||||
this.setState({ selectedSort: sortBy }, () => {
|
|
||||||
this.onFiltersUpdate({
|
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
|
||||||
sort_by: sortBy,
|
|
||||||
tag: this.state.selectedTag,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onFiltersUpdate(updatedArgs) {
|
|
||||||
const args = {
|
|
||||||
media_type: null,
|
|
||||||
upload_date: null,
|
|
||||||
duration: null,
|
|
||||||
publish_state: null,
|
|
||||||
sort_by: null,
|
|
||||||
ordering: null,
|
|
||||||
t: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (updatedArgs.media_type) {
|
|
||||||
case 'video':
|
|
||||||
case 'audio':
|
|
||||||
case 'image':
|
|
||||||
case 'pdf':
|
|
||||||
args.media_type = updatedArgs.media_type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (updatedArgs.upload_date) {
|
|
||||||
case 'today':
|
|
||||||
case 'this_week':
|
|
||||||
case 'this_month':
|
|
||||||
case 'this_year':
|
|
||||||
args.upload_date = updatedArgs.upload_date;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle duration filter
|
|
||||||
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
|
|
||||||
args.duration = updatedArgs.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle publish state filter
|
|
||||||
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
|
|
||||||
args.publish_state = updatedArgs.publish_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (updatedArgs.sort_by) {
|
|
||||||
case 'date_added_desc':
|
|
||||||
// Default sorting, no need to add parameters
|
|
||||||
break;
|
|
||||||
case 'date_added_asc':
|
|
||||||
args.ordering = 'asc';
|
|
||||||
break;
|
|
||||||
case 'alphabetically_asc':
|
|
||||||
args.sort_by = 'title_asc';
|
|
||||||
break;
|
|
||||||
case 'alphabetically_desc':
|
|
||||||
args.sort_by = 'title_desc';
|
|
||||||
break;
|
|
||||||
case 'plays_least':
|
|
||||||
args.sort_by = 'views_asc';
|
|
||||||
break;
|
|
||||||
case 'plays_most':
|
|
||||||
args.sort_by = 'views_desc';
|
|
||||||
break;
|
|
||||||
case 'likes_least':
|
|
||||||
args.sort_by = 'likes_asc';
|
|
||||||
break;
|
|
||||||
case 'likes_most':
|
|
||||||
args.sort_by = 'likes_desc';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
|
|
||||||
args.t = updatedArgs.tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newArgs = [];
|
|
||||||
|
|
||||||
for (let arg in args) {
|
|
||||||
if (null !== args[arg]) {
|
|
||||||
newArgs.push(arg + '=' + args[arg]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
if (!this.state.author) {
|
if (!this.state.author) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestUrl;
|
let requestUrl;
|
||||||
|
|
||||||
if (this.state.query) {
|
if (newQuery) {
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_by_me&q=' +
|
||||||
|
encodeURIComponent(newQuery) +
|
||||||
|
this.state.filterArgs;
|
||||||
} else {
|
} else {
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs;
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_by_me' +
|
||||||
|
this.state.filterArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = this.state.title;
|
||||||
|
|
||||||
|
if ('' === newQuery) {
|
||||||
|
title = this.props.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
requestUrl: requestUrl,
|
requestUrl: requestUrl,
|
||||||
|
query: newQuery,
|
||||||
|
title: title,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onResponseDataLoaded(responseData) {
|
|
||||||
if (responseData && responseData.tags) {
|
|
||||||
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag);
|
|
||||||
this.setState({ availableTags: tags });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pageContent() {
|
onToggleFiltersClick() {
|
||||||
const authorData = ProfilePageStore.get('author-data');
|
this.setState({
|
||||||
|
hiddenFilters: !this.state.hiddenFilters,
|
||||||
|
hiddenTags: true,
|
||||||
|
hiddenSorting: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
onToggleTagsClick() {
|
||||||
|
this.setState({
|
||||||
|
hiddenFilters: true,
|
||||||
|
hiddenTags: !this.state.hiddenTags,
|
||||||
|
hiddenSorting: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any filters are active
|
onToggleSortingClick() {
|
||||||
const hasActiveFilters = this.state.filterArgs && (
|
this.setState({
|
||||||
this.state.filterArgs.includes('media_type=') ||
|
hiddenFilters: true,
|
||||||
this.state.filterArgs.includes('upload_date=') ||
|
hiddenTags: true,
|
||||||
this.state.filterArgs.includes('duration=') ||
|
hiddenSorting: !this.state.hiddenSorting,
|
||||||
this.state.filterArgs.includes('publish_state=')
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
return [
|
onTagSelect(tag) {
|
||||||
this.state.author ? (
|
this.setState({ selectedTag: tag }, () => {
|
||||||
<ProfilePagesHeader
|
this.onFiltersUpdate({
|
||||||
key="ProfilePagesHeader"
|
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
||||||
author={this.state.author}
|
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
||||||
type="shared_by_me"
|
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
||||||
onQueryChange={this.changeRequestQuery}
|
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
||||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
sort_by: this.state.selectedSort,
|
||||||
onToggleTagsClick={this.onToggleTagsClick}
|
tag: tag,
|
||||||
onToggleSortingClick={this.onToggleSortingClick}
|
});
|
||||||
hasActiveFilters={hasActiveFilters}
|
});
|
||||||
hasActiveTags={this.state.selectedTag !== 'all'}
|
}
|
||||||
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
|
||||||
/>
|
onSortSelect(sortBy) {
|
||||||
) : null,
|
this.setState({ selectedSort: sortBy }, () => {
|
||||||
this.state.author ? (
|
this.onFiltersUpdate({
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
||||||
<MediaListWrapper
|
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
||||||
title={this.state.title}
|
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
||||||
className="items-list-ver"
|
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
||||||
showBulkActions={isMediaAuthor}
|
sort_by: sortBy,
|
||||||
selectedCount={this.props.bulkActions.selectedMedia.size}
|
tag: this.state.selectedTag,
|
||||||
totalCount={this.props.bulkActions.availableMediaIds.length}
|
});
|
||||||
onBulkAction={this.props.bulkActions.handleBulkAction}
|
});
|
||||||
onSelectAll={this.props.bulkActions.handleSelectAll}
|
}
|
||||||
onDeselectAll={this.props.bulkActions.handleDeselectAll}
|
|
||||||
>
|
onFiltersUpdate(updatedArgs) {
|
||||||
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} />
|
const args = {
|
||||||
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} />
|
media_type: null,
|
||||||
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
upload_date: null,
|
||||||
<LazyLoadItemListAsync
|
duration: null,
|
||||||
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
|
publish_state: null,
|
||||||
requestUrl={this.state.requestUrl}
|
sort_by: null,
|
||||||
hideAuthor={true}
|
ordering: null,
|
||||||
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
t: null,
|
||||||
hideViews={!PageStore.get('config-media-item').displayViews}
|
};
|
||||||
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
|
||||||
canEdit={isMediaAuthor}
|
switch (updatedArgs.media_type) {
|
||||||
onResponseDataLoaded={this.onResponseDataLoaded}
|
case 'video':
|
||||||
showSelection={isMediaAuthor}
|
case 'audio':
|
||||||
hasAnySelection={this.props.bulkActions.selectedMedia.size > 0}
|
case 'image':
|
||||||
selectedMedia={this.props.bulkActions.selectedMedia}
|
case 'pdf':
|
||||||
onMediaSelection={this.props.bulkActions.handleMediaSelection}
|
args.media_type = updatedArgs.media_type;
|
||||||
onItemsUpdate={this.props.bulkActions.handleItemsUpdate}
|
break;
|
||||||
/>
|
}
|
||||||
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
|
||||||
<EmptySharedByMe name={this.state.author.name} />
|
switch (updatedArgs.upload_date) {
|
||||||
) : null}
|
case 'today':
|
||||||
</MediaListWrapper>
|
case 'this_week':
|
||||||
</ProfilePagesContent>
|
case 'this_month':
|
||||||
) : null,
|
case 'this_year':
|
||||||
this.state.author && isMediaAuthor ? (
|
args.upload_date = updatedArgs.upload_date;
|
||||||
<BulkActionsModals
|
break;
|
||||||
key="BulkActionsModals"
|
}
|
||||||
{...this.props.bulkActions}
|
|
||||||
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)}
|
// Handle duration filter
|
||||||
csrfToken={this.props.bulkActions.getCsrfToken()}
|
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
|
||||||
username={this.state.author.username}
|
args.duration = updatedArgs.duration;
|
||||||
onConfirmCancel={this.props.bulkActions.handleConfirmCancel}
|
}
|
||||||
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
|
|
||||||
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel}
|
// Handle publish state filter
|
||||||
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess}
|
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
|
||||||
onPermissionModalError={this.props.bulkActions.handlePermissionModalError}
|
args.publish_state = updatedArgs.publish_state;
|
||||||
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel}
|
}
|
||||||
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
|
|
||||||
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError}
|
switch (updatedArgs.sort_by) {
|
||||||
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel}
|
case 'date_added_desc':
|
||||||
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess}
|
// Default sorting, no need to add parameters
|
||||||
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError}
|
break;
|
||||||
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel}
|
case 'date_added_asc':
|
||||||
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess}
|
args.ordering = 'asc';
|
||||||
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError}
|
break;
|
||||||
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel}
|
case 'alphabetically_asc':
|
||||||
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess}
|
args.sort_by = 'title_asc';
|
||||||
onCategoryModalError={this.props.bulkActions.handleCategoryModalError}
|
break;
|
||||||
onTagModalCancel={this.props.bulkActions.handleTagModalCancel}
|
case 'alphabetically_desc':
|
||||||
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess}
|
args.sort_by = 'title_desc';
|
||||||
onTagModalError={this.props.bulkActions.handleTagModalError}
|
break;
|
||||||
/>
|
case 'plays_least':
|
||||||
) : null,
|
args.sort_by = 'views_asc';
|
||||||
];
|
break;
|
||||||
}
|
case 'plays_most':
|
||||||
|
args.sort_by = 'views_desc';
|
||||||
|
break;
|
||||||
|
case 'likes_least':
|
||||||
|
args.sort_by = 'likes_asc';
|
||||||
|
break;
|
||||||
|
case 'likes_most':
|
||||||
|
args.sort_by = 'likes_desc';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
|
||||||
|
args.t = updatedArgs.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newArgs = [];
|
||||||
|
|
||||||
|
for (let arg in args) {
|
||||||
|
if (null !== args[arg]) {
|
||||||
|
newArgs.push(arg + '=' + args[arg]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
if (!this.state.author) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let requestUrl;
|
||||||
|
|
||||||
|
if (this.state.query) {
|
||||||
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_by_me&q=' +
|
||||||
|
encodeURIComponent(this.state.query) +
|
||||||
|
this.state.filterArgs;
|
||||||
|
} else {
|
||||||
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_by_me' +
|
||||||
|
this.state.filterArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
requestUrl: requestUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponseDataLoaded(responseData) {
|
||||||
|
if (responseData && responseData.tags) {
|
||||||
|
const tags = responseData.tags
|
||||||
|
.split(',')
|
||||||
|
.map((tag) => tag.trim())
|
||||||
|
.filter((tag) => tag);
|
||||||
|
this.setState({ availableTags: tags });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMediaSelection(mediaId, isSelected) {
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
|
this.setState((prevState) => {
|
||||||
|
const newSelectedMedia = new Set();
|
||||||
|
|
||||||
|
// In select media mode, only allow single selection
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
if (isSelected) {
|
||||||
|
newSelectedMedia.add(mediaId);
|
||||||
|
console.log('Selected media item:', mediaId);
|
||||||
|
|
||||||
|
// Send postMessage to parent window (Moodle TinyMCE plugin)
|
||||||
|
if (window.parent !== window) {
|
||||||
|
// Construct the embed URL
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
|
||||||
|
|
||||||
|
// Send message in the format expected by the Moodle plugin
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'videoSelected',
|
||||||
|
embedUrl: embedUrl,
|
||||||
|
videoId: mediaId
|
||||||
|
}, '*');
|
||||||
|
|
||||||
|
console.log('Sent postMessage to parent:', { embedUrl, videoId: mediaId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal mode: should not reach here as bulk actions handle this
|
||||||
|
newSelectedMedia.clear();
|
||||||
|
prevState.selectedMedia.forEach((id) => newSelectedMedia.add(id));
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
newSelectedMedia.add(mediaId);
|
||||||
|
} else {
|
||||||
|
newSelectedMedia.delete(mediaId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { selectedMedia: newSelectedMedia };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pageContent() {
|
||||||
|
const authorData = ProfilePageStore.get('author-data');
|
||||||
|
|
||||||
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
|
// Check if any filters are active
|
||||||
|
const hasActiveFilters =
|
||||||
|
this.state.filterArgs &&
|
||||||
|
(this.state.filterArgs.includes('media_type=') ||
|
||||||
|
this.state.filterArgs.includes('upload_date=') ||
|
||||||
|
this.state.filterArgs.includes('duration=') ||
|
||||||
|
this.state.filterArgs.includes('publish_state='));
|
||||||
|
|
||||||
|
return [
|
||||||
|
this.state.author ? (
|
||||||
|
<ProfilePagesHeader
|
||||||
|
key="ProfilePagesHeader"
|
||||||
|
author={this.state.author}
|
||||||
|
type="shared_by_me"
|
||||||
|
onQueryChange={this.changeRequestQuery}
|
||||||
|
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||||
|
onToggleTagsClick={this.onToggleTagsClick}
|
||||||
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
|
hasActiveFilters={hasActiveFilters}
|
||||||
|
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||||
|
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||||
|
hideChannelBanner={inEmbeddedApp()}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
this.state.author ? (
|
||||||
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
<MediaListWrapper
|
||||||
|
title={inEmbeddedApp() ? undefined : this.state.title}
|
||||||
|
className="items-list-ver"
|
||||||
|
style={inEmbeddedApp() ? { marginTop: '24px' } : undefined}
|
||||||
|
showBulkActions={!isSelectMediaMode && isMediaAuthor}
|
||||||
|
selectedCount={isSelectMediaMode ? this.state.selectedMedia.size : this.props.bulkActions.selectedMedia.size}
|
||||||
|
totalCount={isSelectMediaMode ? 0 : this.props.bulkActions.availableMediaIds.length}
|
||||||
|
onBulkAction={this.props.bulkActions.handleBulkAction}
|
||||||
|
onSelectAll={this.props.bulkActions.handleSelectAll}
|
||||||
|
onDeselectAll={this.props.bulkActions.handleDeselectAll}
|
||||||
|
>
|
||||||
|
<ProfileMediaFilters
|
||||||
|
hidden={this.state.hiddenFilters}
|
||||||
|
tags={this.state.availableTags}
|
||||||
|
onFiltersUpdate={this.onFiltersUpdate}
|
||||||
|
/>
|
||||||
|
<ProfileMediaTags
|
||||||
|
hidden={this.state.hiddenTags}
|
||||||
|
tags={this.state.availableTags}
|
||||||
|
onTagSelect={this.onTagSelect}
|
||||||
|
/>
|
||||||
|
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
||||||
|
<LazyLoadItemListAsync
|
||||||
|
key={isSelectMediaMode ? this.state.requestUrl : `${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
|
||||||
|
requestUrl={this.state.requestUrl}
|
||||||
|
hideAuthor={true}
|
||||||
|
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
||||||
|
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||||
|
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||||
|
canEdit={!isSelectMediaMode && isMediaAuthor}
|
||||||
|
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||||
|
showSelection={isMediaAuthor || isSelectMediaMode}
|
||||||
|
hasAnySelection={isSelectMediaMode ? this.state.selectedMedia.size > 0 : this.props.bulkActions.selectedMedia.size > 0}
|
||||||
|
selectedMedia={isSelectMediaMode ? this.state.selectedMedia : this.props.bulkActions.selectedMedia}
|
||||||
|
onMediaSelection={isSelectMediaMode ? this.handleMediaSelection : this.props.bulkActions.handleMediaSelection}
|
||||||
|
onItemsUpdate={!isSelectMediaMode ? this.props.bulkActions.handleItemsUpdate : undefined}
|
||||||
|
/>
|
||||||
|
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
||||||
|
<EmptySharedByMe name={this.state.author.name} />
|
||||||
|
) : null}
|
||||||
|
</MediaListWrapper>
|
||||||
|
</ProfilePagesContent>
|
||||||
|
) : null,
|
||||||
|
this.state.author && isMediaAuthor && !isSelectMediaMode ? (
|
||||||
|
<BulkActionsModals
|
||||||
|
key="BulkActionsModals"
|
||||||
|
{...this.props.bulkActions}
|
||||||
|
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)}
|
||||||
|
csrfToken={this.props.bulkActions.getCsrfToken()}
|
||||||
|
username={this.state.author.username}
|
||||||
|
onConfirmCancel={this.props.bulkActions.handleConfirmCancel}
|
||||||
|
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
|
||||||
|
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel}
|
||||||
|
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess}
|
||||||
|
onPermissionModalError={this.props.bulkActions.handlePermissionModalError}
|
||||||
|
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel}
|
||||||
|
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
|
||||||
|
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError}
|
||||||
|
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel}
|
||||||
|
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess}
|
||||||
|
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError}
|
||||||
|
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel}
|
||||||
|
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess}
|
||||||
|
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError}
|
||||||
|
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel}
|
||||||
|
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess}
|
||||||
|
onCategoryModalError={this.props.bulkActions.handleCategoryModalError}
|
||||||
|
onTagModalCancel={this.props.bulkActions.handleTagModalCancel}
|
||||||
|
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess}
|
||||||
|
onTagModalError={this.props.bulkActions.handleTagModalError}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileSharedByMePage.propTypes = {
|
ProfileSharedByMePage.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
bulkActions: PropTypes.object.isRequired,
|
bulkActions: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfileSharedByMePage.defaultProps = {
|
ProfileSharedByMePage.defaultProps = {
|
||||||
title: 'Shared by me',
|
title: 'Shared by me',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrap with HOC and export as named export for compatibility
|
// Wrap with HOC and export as named export for compatibility
|
||||||
|
|||||||
@@ -10,364 +10,459 @@ import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListA
|
|||||||
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
|
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
|
||||||
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
|
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
|
||||||
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
|
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
|
||||||
import { translateString } from '../utils/helpers';
|
import { inEmbeddedApp, inSelectMediaEmbedMode } from '../utils/helpers';
|
||||||
|
|
||||||
import { Page } from './_Page';
|
import { Page } from './_Page';
|
||||||
|
|
||||||
import '../components/profile-page/ProfilePage.scss';
|
import '../components/profile-page/ProfilePage.scss';
|
||||||
|
|
||||||
function EmptySharedWithMe(props) {
|
function EmptySharedWithMe(props) {
|
||||||
return (
|
return (
|
||||||
<LinksConsumer>
|
<LinksConsumer>
|
||||||
{(links) => (
|
{(links) => (
|
||||||
<div className="empty-media empty-channel-media">
|
<div className="empty-media empty-channel-media">
|
||||||
<div className="welcome-title">No shared media</div>
|
<div className="welcome-title">No shared media</div>
|
||||||
<div className="start-uploading">
|
<div className="start-uploading">Media that others have shared with you will show up here.</div>
|
||||||
Media that others have shared with you will show up here.
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</LinksConsumer>
|
||||||
)}
|
);
|
||||||
</LinksConsumer>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProfileSharedWithMePage extends Page {
|
export class ProfileSharedWithMePage extends Page {
|
||||||
constructor(props, pageSlug) {
|
constructor(props, pageSlug) {
|
||||||
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me');
|
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me');
|
||||||
|
|
||||||
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me';
|
this.profilePageSlug = 'string' === typeof pageSlug ? pageSlug : 'author-shared-with-me';
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
channelMediaCount: -1,
|
channelMediaCount: -1,
|
||||||
author: ProfilePageStore.get('author-data'),
|
author: ProfilePageStore.get('author-data'),
|
||||||
uploadsPreviewItemsCount: 0,
|
uploadsPreviewItemsCount: 0,
|
||||||
title: this.props.title,
|
title: this.props.title,
|
||||||
query: ProfilePageStore.get('author-query'),
|
query: ProfilePageStore.get('author-query'),
|
||||||
requestUrl: null,
|
requestUrl: null,
|
||||||
hiddenFilters: true,
|
hiddenFilters: true,
|
||||||
hiddenTags: true,
|
hiddenTags: true,
|
||||||
hiddenSorting: true,
|
hiddenSorting: true,
|
||||||
filterArgs: '',
|
filterArgs: '',
|
||||||
availableTags: [],
|
availableTags: [],
|
||||||
selectedTag: 'all',
|
selectedTag: 'all',
|
||||||
selectedSort: 'date_added_desc',
|
selectedSort: 'date_added_desc',
|
||||||
};
|
selectedMedia: new Set(), // For select media mode
|
||||||
|
};
|
||||||
|
|
||||||
this.authorDataLoad = this.authorDataLoad.bind(this);
|
this.authorDataLoad = this.authorDataLoad.bind(this);
|
||||||
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
|
this.onAuthorPreviewItemsCountCallback = this.onAuthorPreviewItemsCountCallback.bind(this);
|
||||||
this.getCountFunc = this.getCountFunc.bind(this);
|
this.getCountFunc = this.getCountFunc.bind(this);
|
||||||
this.changeRequestQuery = this.changeRequestQuery.bind(this);
|
this.changeRequestQuery = this.changeRequestQuery.bind(this);
|
||||||
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
|
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
|
||||||
this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
|
this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
|
||||||
this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
|
this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
|
||||||
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
|
this.onFiltersUpdate = this.onFiltersUpdate.bind(this);
|
||||||
this.onTagSelect = this.onTagSelect.bind(this);
|
this.onTagSelect = this.onTagSelect.bind(this);
|
||||||
this.onSortSelect = this.onSortSelect.bind(this);
|
this.onSortSelect = this.onSortSelect.bind(this);
|
||||||
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
|
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
|
||||||
|
this.handleMediaSelection = this.handleMediaSelection.bind(this);
|
||||||
|
|
||||||
ProfilePageStore.on('load-author-data', this.authorDataLoad);
|
ProfilePageStore.on('load-author-data', this.authorDataLoad);
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
ProfilePageActions.load_author_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
authorDataLoad() {
|
|
||||||
const author = ProfilePageStore.get('author-data');
|
|
||||||
|
|
||||||
let requestUrl = this.state.requestUrl;
|
|
||||||
|
|
||||||
if (author) {
|
|
||||||
if (this.state.query) {
|
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
|
||||||
} else {
|
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me' + this.state.filterArgs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
componentDidMount() {
|
||||||
author: author,
|
ProfilePageActions.load_author_data();
|
||||||
requestUrl: requestUrl,
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
authorDataLoad() {
|
||||||
this.setState({
|
const author = ProfilePageStore.get('author-data');
|
||||||
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getCountFunc(count) {
|
let requestUrl = this.state.requestUrl;
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
channelMediaCount: count,
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if (this.state.query) {
|
|
||||||
let title = '';
|
|
||||||
|
|
||||||
if (!count) {
|
if (author) {
|
||||||
title = 'No results for "' + this.state.query + '"';
|
if (this.state.query) {
|
||||||
} else if (1 === count) {
|
requestUrl =
|
||||||
title = '1 result for "' + this.state.query + '"';
|
ApiUrlContext._currentValue.media +
|
||||||
} else {
|
'?author=' +
|
||||||
title = count + ' results for "' + this.state.query + '"';
|
author.id +
|
||||||
}
|
'&show=shared_with_me&q=' +
|
||||||
|
encodeURIComponent(this.state.query) +
|
||||||
this.setState({
|
this.state.filterArgs;
|
||||||
title: title,
|
} else {
|
||||||
});
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
author.id +
|
||||||
|
'&show=shared_with_me' +
|
||||||
|
this.state.filterArgs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
changeRequestQuery(newQuery) {
|
this.setState({
|
||||||
if (!this.state.author) {
|
author: author,
|
||||||
return;
|
requestUrl: requestUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestUrl;
|
onAuthorPreviewItemsCountCallback(totalAuthorPreviewItems) {
|
||||||
|
this.setState({
|
||||||
if (newQuery) {
|
uploadsPreviewItemsCount: totalAuthorPreviewItems,
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs;
|
});
|
||||||
} else {
|
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = this.state.title;
|
getCountFunc(count) {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
channelMediaCount: count,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (this.state.query) {
|
||||||
|
let title = '';
|
||||||
|
|
||||||
if ('' === newQuery) {
|
if (!count) {
|
||||||
title = this.props.title;
|
title = 'No results for "' + this.state.query + '"';
|
||||||
|
} else if (1 === count) {
|
||||||
|
title = '1 result for "' + this.state.query + '"';
|
||||||
|
} else {
|
||||||
|
title = count + ' results for "' + this.state.query + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
changeRequestQuery(newQuery) {
|
||||||
requestUrl: requestUrl,
|
|
||||||
query: newQuery,
|
|
||||||
title: title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleFiltersClick() {
|
|
||||||
this.setState({
|
|
||||||
hiddenFilters: !this.state.hiddenFilters,
|
|
||||||
hiddenTags: true,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleTagsClick() {
|
|
||||||
this.setState({
|
|
||||||
hiddenFilters: true,
|
|
||||||
hiddenTags: !this.state.hiddenTags,
|
|
||||||
hiddenSorting: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggleSortingClick() {
|
|
||||||
this.setState({
|
|
||||||
hiddenFilters: true,
|
|
||||||
hiddenTags: true,
|
|
||||||
hiddenSorting: !this.state.hiddenSorting,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onTagSelect(tag) {
|
|
||||||
this.setState({ selectedTag: tag }, () => {
|
|
||||||
this.onFiltersUpdate({
|
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
|
||||||
sort_by: this.state.selectedSort,
|
|
||||||
tag: tag,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSortSelect(sortBy) {
|
|
||||||
this.setState({ selectedSort: sortBy }, () => {
|
|
||||||
this.onFiltersUpdate({
|
|
||||||
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
|
||||||
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
|
||||||
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
|
||||||
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
|
||||||
sort_by: sortBy,
|
|
||||||
tag: this.state.selectedTag,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onFiltersUpdate(updatedArgs) {
|
|
||||||
const args = {
|
|
||||||
media_type: null,
|
|
||||||
upload_date: null,
|
|
||||||
duration: null,
|
|
||||||
publish_state: null,
|
|
||||||
sort_by: null,
|
|
||||||
ordering: null,
|
|
||||||
t: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (updatedArgs.media_type) {
|
|
||||||
case 'video':
|
|
||||||
case 'audio':
|
|
||||||
case 'image':
|
|
||||||
case 'pdf':
|
|
||||||
args.media_type = updatedArgs.media_type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (updatedArgs.upload_date) {
|
|
||||||
case 'today':
|
|
||||||
case 'this_week':
|
|
||||||
case 'this_month':
|
|
||||||
case 'this_year':
|
|
||||||
args.upload_date = updatedArgs.upload_date;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle duration filter
|
|
||||||
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
|
|
||||||
args.duration = updatedArgs.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle publish state filter
|
|
||||||
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
|
|
||||||
args.publish_state = updatedArgs.publish_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (updatedArgs.sort_by) {
|
|
||||||
case 'date_added_desc':
|
|
||||||
// Default sorting, no need to add parameters
|
|
||||||
break;
|
|
||||||
case 'date_added_asc':
|
|
||||||
args.ordering = 'asc';
|
|
||||||
break;
|
|
||||||
case 'alphabetically_asc':
|
|
||||||
args.sort_by = 'title_asc';
|
|
||||||
break;
|
|
||||||
case 'alphabetically_desc':
|
|
||||||
args.sort_by = 'title_desc';
|
|
||||||
break;
|
|
||||||
case 'plays_least':
|
|
||||||
args.sort_by = 'views_asc';
|
|
||||||
break;
|
|
||||||
case 'plays_most':
|
|
||||||
args.sort_by = 'views_desc';
|
|
||||||
break;
|
|
||||||
case 'likes_least':
|
|
||||||
args.sort_by = 'likes_asc';
|
|
||||||
break;
|
|
||||||
case 'likes_most':
|
|
||||||
args.sort_by = 'likes_desc';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
|
|
||||||
args.t = updatedArgs.tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newArgs = [];
|
|
||||||
|
|
||||||
for (let arg in args) {
|
|
||||||
if (null !== args[arg]) {
|
|
||||||
newArgs.push(arg + '=' + args[arg]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
if (!this.state.author) {
|
if (!this.state.author) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestUrl;
|
let requestUrl;
|
||||||
|
|
||||||
if (this.state.query) {
|
if (newQuery) {
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs;
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_with_me&q=' +
|
||||||
|
encodeURIComponent(newQuery) +
|
||||||
|
this.state.filterArgs;
|
||||||
} else {
|
} else {
|
||||||
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs;
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_with_me' +
|
||||||
|
this.state.filterArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = this.state.title;
|
||||||
|
|
||||||
|
if ('' === newQuery) {
|
||||||
|
title = this.props.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
requestUrl: requestUrl,
|
requestUrl: requestUrl,
|
||||||
|
query: newQuery,
|
||||||
|
title: title,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onResponseDataLoaded(responseData) {
|
|
||||||
if (responseData && responseData.tags) {
|
|
||||||
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag);
|
|
||||||
this.setState({ availableTags: tags });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pageContent() {
|
onToggleFiltersClick() {
|
||||||
const authorData = ProfilePageStore.get('author-data');
|
this.setState({
|
||||||
|
hiddenFilters: !this.state.hiddenFilters,
|
||||||
|
hiddenTags: true,
|
||||||
|
hiddenSorting: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
onToggleTagsClick() {
|
||||||
|
this.setState({
|
||||||
|
hiddenFilters: true,
|
||||||
|
hiddenTags: !this.state.hiddenTags,
|
||||||
|
hiddenSorting: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check if any filters are active
|
onToggleSortingClick() {
|
||||||
const hasActiveFilters = this.state.filterArgs && (
|
this.setState({
|
||||||
this.state.filterArgs.includes('media_type=') ||
|
hiddenFilters: true,
|
||||||
this.state.filterArgs.includes('upload_date=') ||
|
hiddenTags: true,
|
||||||
this.state.filterArgs.includes('duration=') ||
|
hiddenSorting: !this.state.hiddenSorting,
|
||||||
this.state.filterArgs.includes('publish_state=')
|
});
|
||||||
);
|
}
|
||||||
|
|
||||||
return [
|
onTagSelect(tag) {
|
||||||
this.state.author ? (
|
this.setState({ selectedTag: tag }, () => {
|
||||||
<ProfilePagesHeader
|
this.onFiltersUpdate({
|
||||||
key="ProfilePagesHeader"
|
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
||||||
author={this.state.author}
|
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
||||||
type="shared_with_me"
|
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
||||||
onQueryChange={this.changeRequestQuery}
|
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
||||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
sort_by: this.state.selectedSort,
|
||||||
onToggleTagsClick={this.onToggleTagsClick}
|
tag: tag,
|
||||||
onToggleSortingClick={this.onToggleSortingClick}
|
});
|
||||||
hasActiveFilters={hasActiveFilters}
|
});
|
||||||
hasActiveTags={this.state.selectedTag !== 'all'}
|
}
|
||||||
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
|
||||||
/>
|
onSortSelect(sortBy) {
|
||||||
) : null,
|
this.setState({ selectedSort: sortBy }, () => {
|
||||||
this.state.author ? (
|
this.onFiltersUpdate({
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
media_type: this.state.filterArgs.match(/media_type=([^&]+)/)?.[1],
|
||||||
<MediaListWrapper
|
upload_date: this.state.filterArgs.match(/upload_date=([^&]+)/)?.[1],
|
||||||
title={this.state.title}
|
duration: this.state.filterArgs.match(/duration=([^&]+)/)?.[1],
|
||||||
className="items-list-ver"
|
publish_state: this.state.filterArgs.match(/publish_state=([^&]+)/)?.[1],
|
||||||
>
|
sort_by: sortBy,
|
||||||
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} />
|
tag: this.state.selectedTag,
|
||||||
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} />
|
});
|
||||||
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
});
|
||||||
<LazyLoadItemListAsync
|
}
|
||||||
key={this.state.requestUrl}
|
|
||||||
requestUrl={this.state.requestUrl}
|
onFiltersUpdate(updatedArgs) {
|
||||||
hideAuthor={true}
|
const args = {
|
||||||
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
media_type: null,
|
||||||
hideViews={!PageStore.get('config-media-item').displayViews}
|
upload_date: null,
|
||||||
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
duration: null,
|
||||||
canEdit={false}
|
publish_state: null,
|
||||||
onResponseDataLoaded={this.onResponseDataLoaded}
|
sort_by: null,
|
||||||
/>
|
ordering: null,
|
||||||
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
t: null,
|
||||||
<EmptySharedWithMe name={this.state.author.name} />
|
};
|
||||||
) : null}
|
|
||||||
</MediaListWrapper>
|
switch (updatedArgs.media_type) {
|
||||||
</ProfilePagesContent>
|
case 'video':
|
||||||
) : null,
|
case 'audio':
|
||||||
];
|
case 'image':
|
||||||
}
|
case 'pdf':
|
||||||
|
args.media_type = updatedArgs.media_type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (updatedArgs.upload_date) {
|
||||||
|
case 'today':
|
||||||
|
case 'this_week':
|
||||||
|
case 'this_month':
|
||||||
|
case 'this_year':
|
||||||
|
args.upload_date = updatedArgs.upload_date;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle duration filter
|
||||||
|
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
|
||||||
|
args.duration = updatedArgs.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle publish state filter
|
||||||
|
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
|
||||||
|
args.publish_state = updatedArgs.publish_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (updatedArgs.sort_by) {
|
||||||
|
case 'date_added_desc':
|
||||||
|
// Default sorting, no need to add parameters
|
||||||
|
break;
|
||||||
|
case 'date_added_asc':
|
||||||
|
args.ordering = 'asc';
|
||||||
|
break;
|
||||||
|
case 'alphabetically_asc':
|
||||||
|
args.sort_by = 'title_asc';
|
||||||
|
break;
|
||||||
|
case 'alphabetically_desc':
|
||||||
|
args.sort_by = 'title_desc';
|
||||||
|
break;
|
||||||
|
case 'plays_least':
|
||||||
|
args.sort_by = 'views_asc';
|
||||||
|
break;
|
||||||
|
case 'plays_most':
|
||||||
|
args.sort_by = 'views_desc';
|
||||||
|
break;
|
||||||
|
case 'likes_least':
|
||||||
|
args.sort_by = 'likes_asc';
|
||||||
|
break;
|
||||||
|
case 'likes_most':
|
||||||
|
args.sort_by = 'likes_desc';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
|
||||||
|
args.t = updatedArgs.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newArgs = [];
|
||||||
|
|
||||||
|
for (let arg in args) {
|
||||||
|
if (null !== args[arg]) {
|
||||||
|
newArgs.push(arg + '=' + args[arg]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
if (!this.state.author) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let requestUrl;
|
||||||
|
|
||||||
|
if (this.state.query) {
|
||||||
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_with_me&q=' +
|
||||||
|
encodeURIComponent(this.state.query) +
|
||||||
|
this.state.filterArgs;
|
||||||
|
} else {
|
||||||
|
requestUrl =
|
||||||
|
ApiUrlContext._currentValue.media +
|
||||||
|
'?author=' +
|
||||||
|
this.state.author.id +
|
||||||
|
'&show=shared_with_me' +
|
||||||
|
this.state.filterArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
requestUrl: requestUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponseDataLoaded(responseData) {
|
||||||
|
if (responseData && responseData.tags) {
|
||||||
|
const tags = responseData.tags
|
||||||
|
.split(',')
|
||||||
|
.map((tag) => tag.trim())
|
||||||
|
.filter((tag) => tag);
|
||||||
|
this.setState({ availableTags: tags });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMediaSelection(mediaId, isSelected) {
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
|
this.setState((prevState) => {
|
||||||
|
const newSelectedMedia = new Set();
|
||||||
|
|
||||||
|
// In select media mode, only allow single selection
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
if (isSelected) {
|
||||||
|
newSelectedMedia.add(mediaId);
|
||||||
|
console.log('Selected media item:', mediaId);
|
||||||
|
|
||||||
|
// Send postMessage to parent window (Moodle TinyMCE plugin)
|
||||||
|
if (window.parent !== window) {
|
||||||
|
// Construct the embed URL
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
|
||||||
|
|
||||||
|
// Send message in the format expected by the Moodle plugin
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'videoSelected',
|
||||||
|
embedUrl: embedUrl,
|
||||||
|
videoId: mediaId
|
||||||
|
}, '*');
|
||||||
|
|
||||||
|
console.log('Sent postMessage to parent:', { embedUrl, videoId: mediaId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal mode: no selection UI in this page normally
|
||||||
|
newSelectedMedia.clear();
|
||||||
|
prevState.selectedMedia.forEach((id) => newSelectedMedia.add(id));
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
newSelectedMedia.add(mediaId);
|
||||||
|
} else {
|
||||||
|
newSelectedMedia.delete(mediaId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { selectedMedia: newSelectedMedia };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pageContent() {
|
||||||
|
const authorData = ProfilePageStore.get('author-data');
|
||||||
|
|
||||||
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
|
// Check if any filters are active
|
||||||
|
const hasActiveFilters =
|
||||||
|
this.state.filterArgs &&
|
||||||
|
(this.state.filterArgs.includes('media_type=') ||
|
||||||
|
this.state.filterArgs.includes('upload_date=') ||
|
||||||
|
this.state.filterArgs.includes('duration=') ||
|
||||||
|
this.state.filterArgs.includes('publish_state='));
|
||||||
|
|
||||||
|
return [
|
||||||
|
this.state.author ? (
|
||||||
|
<ProfilePagesHeader
|
||||||
|
key="ProfilePagesHeader"
|
||||||
|
author={this.state.author}
|
||||||
|
type="shared_with_me"
|
||||||
|
onQueryChange={this.changeRequestQuery}
|
||||||
|
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||||
|
onToggleTagsClick={this.onToggleTagsClick}
|
||||||
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
|
hasActiveFilters={hasActiveFilters}
|
||||||
|
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||||
|
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||||
|
hideChannelBanner={inEmbeddedApp()}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
this.state.author ? (
|
||||||
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
|
<MediaListWrapper
|
||||||
|
title={inEmbeddedApp() ? undefined : this.state.title}
|
||||||
|
className="items-list-ver"
|
||||||
|
style={inEmbeddedApp() ? { marginTop: '24px' } : undefined}
|
||||||
|
>
|
||||||
|
<ProfileMediaFilters
|
||||||
|
hidden={this.state.hiddenFilters}
|
||||||
|
tags={this.state.availableTags}
|
||||||
|
onFiltersUpdate={this.onFiltersUpdate}
|
||||||
|
/>
|
||||||
|
<ProfileMediaTags
|
||||||
|
hidden={this.state.hiddenTags}
|
||||||
|
tags={this.state.availableTags}
|
||||||
|
onTagSelect={this.onTagSelect}
|
||||||
|
/>
|
||||||
|
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
||||||
|
<LazyLoadItemListAsync
|
||||||
|
key={this.state.requestUrl}
|
||||||
|
requestUrl={this.state.requestUrl}
|
||||||
|
hideAuthor={true}
|
||||||
|
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
|
||||||
|
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||||
|
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||||
|
canEdit={false}
|
||||||
|
onResponseDataLoaded={this.onResponseDataLoaded}
|
||||||
|
showSelection={isSelectMediaMode}
|
||||||
|
hasAnySelection={this.state.selectedMedia.size > 0}
|
||||||
|
selectedMedia={this.state.selectedMedia}
|
||||||
|
onMediaSelection={this.handleMediaSelection}
|
||||||
|
/>
|
||||||
|
{isMediaAuthor && 0 === this.state.channelMediaCount && !this.state.query ? (
|
||||||
|
<EmptySharedWithMe name={this.state.author.name} />
|
||||||
|
) : null}
|
||||||
|
</MediaListWrapper>
|
||||||
|
</ProfilePagesContent>
|
||||||
|
) : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProfileSharedWithMePage.propTypes = {
|
ProfileSharedWithMePage.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfileSharedWithMePage.defaultProps = {
|
ProfileSharedWithMePage.defaultProps = {
|
||||||
title: 'Shared with me',
|
title: 'Shared with me',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListA
|
|||||||
import { SearchMediaFiltersRow } from '../components/search-filters/SearchMediaFiltersRow';
|
import { SearchMediaFiltersRow } from '../components/search-filters/SearchMediaFiltersRow';
|
||||||
import { SearchResultsFilters } from '../components/search-filters/SearchResultsFilters';
|
import { SearchResultsFilters } from '../components/search-filters/SearchResultsFilters';
|
||||||
import { Page } from './_Page';
|
import { Page } from './_Page';
|
||||||
import { translateString } from '../utils/helpers/';
|
import { translateString, inEmbeddedApp } from '../utils/helpers/';
|
||||||
|
|
||||||
export class SearchPage extends Page {
|
export class SearchPage extends Page {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -115,7 +115,7 @@ export class SearchPage extends Page {
|
|||||||
} else {
|
} else {
|
||||||
if (this.state.searchCategories) {
|
if (this.state.searchCategories) {
|
||||||
title = null === this.state.resultsCount || 0 === this.state.resultsCount ? 'No' : this.state.resultsCount;
|
title = null === this.state.resultsCount || 0 === this.state.resultsCount ? 'No' : this.state.resultsCount;
|
||||||
title += ' ' + translateString('media in category') + ' "' + this.state.searchCategories + '"';
|
title += ' ' + translateString(inEmbeddedApp() ? 'media in course' : 'media in category') + ' "' + this.state.searchCategories + '"';
|
||||||
} else if (this.state.searchTags) {
|
} else if (this.state.searchTags) {
|
||||||
title = null === this.state.resultsCount || 0 === this.state.resultsCount ? 'No' : this.state.resultsCount;
|
title = null === this.state.resultsCount || 0 === this.state.resultsCount ? 'No' : this.state.resultsCount;
|
||||||
title += ' ' + translateString('media in tag') + ' "' + this.state.searchTags + '"';
|
title += ' ' + translateString('media in tag') + ' "' + this.state.searchTags + '"';
|
||||||
|
|||||||
@@ -10,102 +10,98 @@ import '../components/media-page/MediaPage.scss';
|
|||||||
const wideLayoutBreakpoint = 1216;
|
const wideLayoutBreakpoint = 1216;
|
||||||
|
|
||||||
export class _MediaPage extends Page {
|
export class _MediaPage extends Page {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props, 'media');
|
super(props, 'media');
|
||||||
|
|
||||||
const isWideLayout = wideLayoutBreakpoint <= window.innerWidth;
|
const isWideLayout = wideLayoutBreakpoint <= window.innerWidth;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
mediaLoaded: false,
|
mediaLoaded: false,
|
||||||
mediaLoadFailed: false,
|
mediaLoadFailed: false,
|
||||||
wideLayout: isWideLayout,
|
wideLayout: isWideLayout,
|
||||||
infoAndSidebarViewType: !isWideLayout ? 0 : 1,
|
infoAndSidebarViewType: !isWideLayout ? 0 : 1,
|
||||||
viewerClassname: 'cf viewer-section viewer-wide',
|
viewerClassname: 'cf viewer-section viewer-wide',
|
||||||
viewerNestedClassname: 'viewer-section-nested',
|
viewerNestedClassname: 'viewer-section-nested',
|
||||||
pagePlaylistLoaded: false,
|
pagePlaylistLoaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onWindowResize = this.onWindowResize.bind(this);
|
this.onWindowResize = this.onWindowResize.bind(this);
|
||||||
this.onMediaLoad = this.onMediaLoad.bind(this);
|
this.onMediaLoad = this.onMediaLoad.bind(this);
|
||||||
this.onMediaLoadError = this.onMediaLoadError.bind(this);
|
this.onMediaLoadError = this.onMediaLoadError.bind(this);
|
||||||
this.onPagePlaylistLoad = this.onPagePlaylistLoad.bind(this);
|
this.onPagePlaylistLoad = this.onPagePlaylistLoad.bind(this);
|
||||||
|
|
||||||
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
|
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
|
||||||
MediaPageStore.on('loaded_media_error', this.onMediaLoadError);
|
MediaPageStore.on('loaded_media_error', this.onMediaLoadError);
|
||||||
MediaPageStore.on('loaded_page_playlist_data', this.onPagePlaylistLoad);
|
MediaPageStore.on('loaded_page_playlist_data', this.onPagePlaylistLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
MediaPageActions.loadMediaData();
|
MediaPageActions.loadMediaData();
|
||||||
// FIXME: Is not neccessary to check on every window dimension for changes...
|
// FIXME: Is not neccessary to check on every window dimension for changes...
|
||||||
PageStore.on('window_resize', this.onWindowResize);
|
PageStore.on('window_resize', this.onWindowResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPagePlaylistLoad() {
|
onPagePlaylistLoad() {
|
||||||
this.setState({
|
this.setState({
|
||||||
pagePlaylistLoaded: true,
|
pagePlaylistLoaded: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onWindowResize() {
|
onWindowResize() {
|
||||||
const isWideLayout = wideLayoutBreakpoint <= window.innerWidth;
|
const isWideLayout = wideLayoutBreakpoint <= window.innerWidth;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
wideLayout: isWideLayout,
|
wideLayout: isWideLayout,
|
||||||
infoAndSidebarViewType: !isWideLayout || (MediaPageStore.isVideo() && this.state.theaterMode) ? 0 : 1,
|
infoAndSidebarViewType: !isWideLayout || (MediaPageStore.isVideo() && this.state.theaterMode) ? 0 : 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMediaLoad() {
|
onMediaLoad() {
|
||||||
this.setState({ mediaLoaded: true });
|
this.setState({ mediaLoaded: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
onMediaLoadError() {
|
onMediaLoadError() {
|
||||||
this.setState({ mediaLoadFailed: true });
|
this.setState({ mediaLoadFailed: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
viewerContainerContent() {
|
viewerContainerContent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaType() {
|
mediaType() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pageContent() {
|
pageContent() {
|
||||||
return this.state.mediaLoadFailed ? (
|
return this.state.mediaLoadFailed ? (
|
||||||
<div className={this.state.viewerClassname}>
|
<div className={this.state.viewerClassname}>
|
||||||
<ViewerError />
|
<ViewerError />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={this.state.viewerClassname}>
|
<div className={this.state.viewerClassname}>
|
||||||
<div className="viewer-container" key="viewer-container">
|
<div className="viewer-container" key="viewer-container">
|
||||||
{this.state.mediaLoaded ? this.viewerContainerContent() : null}
|
{this.state.mediaLoaded ? this.viewerContainerContent() : null}
|
||||||
</div>
|
</div>
|
||||||
<div key="viewer-section-nested" className={this.state.viewerNestedClassname}>
|
<div key="viewer-section-nested" className={this.state.viewerNestedClassname}>
|
||||||
{!this.state.infoAndSidebarViewType
|
{!this.state.infoAndSidebarViewType
|
||||||
? [
|
? [
|
||||||
<ViewerInfo key="viewer-info" />,
|
<ViewerInfo key="viewer-info" />,
|
||||||
this.state.pagePlaylistLoaded ? (
|
<ViewerSidebar
|
||||||
<ViewerSidebar
|
key="viewer-sidebar"
|
||||||
key="viewer-sidebar"
|
mediaId={MediaPageStore.get('media-id')}
|
||||||
mediaId={MediaPageStore.get('media-id')}
|
playlistData={MediaPageStore.get('playlist-data')}
|
||||||
playlistData={MediaPageStore.get('playlist-data')}
|
/>,
|
||||||
/>
|
]
|
||||||
) : null,
|
: [
|
||||||
]
|
<ViewerSidebar
|
||||||
: [
|
key="viewer-sidebar"
|
||||||
this.state.pagePlaylistLoaded ? (
|
mediaId={MediaPageStore.get('media-id')}
|
||||||
<ViewerSidebar
|
playlistData={MediaPageStore.get('playlist-data')}
|
||||||
key="viewer-sidebar"
|
/>,
|
||||||
mediaId={MediaPageStore.get('media-id')}
|
<ViewerInfo key="viewer-info" />,
|
||||||
playlistData={MediaPageStore.get('playlist-data')}
|
]}
|
||||||
/>
|
</div>
|
||||||
) : null,
|
</div>
|
||||||
<ViewerInfo key="viewer-info" />,
|
);
|
||||||
]}
|
}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,118 +11,119 @@ import _MediaPage from './_MediaPage';
|
|||||||
const wideLayoutBreakpoint = 1216;
|
const wideLayoutBreakpoint = 1216;
|
||||||
|
|
||||||
export class _VideoMediaPage extends Page {
|
export class _VideoMediaPage extends Page {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props, 'media');
|
super(props, 'media');
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
|
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
|
||||||
mediaLoaded: false,
|
mediaLoaded: false,
|
||||||
mediaLoadFailed: false,
|
mediaLoadFailed: false,
|
||||||
isVideoMedia: false,
|
isVideoMedia: false,
|
||||||
theaterMode: false, // FIXME: Used only in case of video media, but is included in every media page code.
|
theaterMode: false, // FIXME: Used only in case of video media, but is included in every media page code.
|
||||||
pagePlaylistLoaded: false,
|
pagePlaylistLoaded: false,
|
||||||
pagePlaylistData: MediaPageStore.get('playlist-data'),
|
pagePlaylistData: MediaPageStore.get('playlist-data'),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onWindowResize = this.onWindowResize.bind(this);
|
this.onWindowResize = this.onWindowResize.bind(this);
|
||||||
this.onMediaLoad = this.onMediaLoad.bind(this);
|
this.onMediaLoad = this.onMediaLoad.bind(this);
|
||||||
this.onMediaLoadError = this.onMediaLoadError.bind(this);
|
this.onMediaLoadError = this.onMediaLoadError.bind(this);
|
||||||
this.onPagePlaylistLoad = this.onPagePlaylistLoad.bind(this);
|
this.onPagePlaylistLoad = this.onPagePlaylistLoad.bind(this);
|
||||||
|
|
||||||
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
|
MediaPageStore.on('loaded_media_data', this.onMediaLoad);
|
||||||
MediaPageStore.on('loaded_media_error', this.onMediaLoadError);
|
MediaPageStore.on('loaded_media_error', this.onMediaLoadError);
|
||||||
MediaPageStore.on('loaded_page_playlist_data', this.onPagePlaylistLoad);
|
MediaPageStore.on('loaded_page_playlist_data', this.onPagePlaylistLoad);
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
MediaPageActions.loadMediaData();
|
|
||||||
// FIXME: Is not neccessary to check on every window dimension for changes...
|
|
||||||
PageStore.on('window_resize', this.onWindowResize);
|
|
||||||
}
|
|
||||||
|
|
||||||
onWindowResize() {
|
|
||||||
this.setState({
|
|
||||||
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onPagePlaylistLoad() {
|
|
||||||
this.setState({
|
|
||||||
pagePlaylistLoaded: true,
|
|
||||||
pagePlaylistData: MediaPageStore.get('playlist-data'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMediaLoad() {
|
|
||||||
const isVideoMedia = 'video' === MediaPageStore.get('media-type') || 'audio' === MediaPageStore.get('media-type');
|
|
||||||
|
|
||||||
if (isVideoMedia) {
|
|
||||||
this.onViewerModeChange = this.onViewerModeChange.bind(this);
|
|
||||||
|
|
||||||
VideoViewerStore.on('changed_viewer_mode', this.onViewerModeChange);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
mediaLoaded: true,
|
|
||||||
isVideoMedia: isVideoMedia,
|
|
||||||
theaterMode: VideoViewerStore.get('in-theater-mode'),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
mediaLoaded: true,
|
|
||||||
isVideoMedia: isVideoMedia,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onViewerModeChange() {
|
componentDidMount() {
|
||||||
this.setState({ theaterMode: VideoViewerStore.get('in-theater-mode') });
|
MediaPageActions.loadMediaData();
|
||||||
}
|
// FIXME: Is not neccessary to check on every window dimension for changes...
|
||||||
|
PageStore.on('window_resize', this.onWindowResize);
|
||||||
|
}
|
||||||
|
|
||||||
onMediaLoadError(a) {
|
onWindowResize() {
|
||||||
this.setState({ mediaLoadFailed: true });
|
this.setState({
|
||||||
}
|
wideLayout: wideLayoutBreakpoint <= window.innerWidth,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pageContent() {
|
onPagePlaylistLoad() {
|
||||||
const viewerClassname = 'cf viewer-section' + (this.state.theaterMode ? ' theater-mode' : ' viewer-wide');
|
this.setState({
|
||||||
const viewerNestedClassname = 'viewer-section-nested' + (this.state.theaterMode ? ' viewer-section' : '');
|
pagePlaylistLoaded: true,
|
||||||
|
pagePlaylistData: MediaPageStore.get('playlist-data'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this.state.mediaLoadFailed ? (
|
onMediaLoad() {
|
||||||
<div className={viewerClassname}>
|
const isVideoMedia =
|
||||||
<ViewerError />
|
'video' === MediaPageStore.get('media-type') || 'audio' === MediaPageStore.get('media-type');
|
||||||
</div>
|
|
||||||
) : (
|
if (isVideoMedia) {
|
||||||
<div className={viewerClassname}>
|
this.onViewerModeChange = this.onViewerModeChange.bind(this);
|
||||||
{[
|
|
||||||
<div className="viewer-container" key="viewer-container">
|
VideoViewerStore.on('changed_viewer_mode', this.onViewerModeChange);
|
||||||
{this.state.mediaLoaded && this.state.pagePlaylistLoaded
|
|
||||||
? this.viewerContainerContent(MediaPageStore.get('media-data'))
|
this.setState({
|
||||||
: null}
|
mediaLoaded: true,
|
||||||
</div>,
|
isVideoMedia: isVideoMedia,
|
||||||
<div key="viewer-section-nested" className={viewerNestedClassname}>
|
theaterMode: VideoViewerStore.get('in-theater-mode'),
|
||||||
{!this.state.wideLayout || (this.state.isVideoMedia && this.state.theaterMode)
|
});
|
||||||
? [
|
} else {
|
||||||
<ViewerInfoVideo key="viewer-info" />,
|
this.setState({
|
||||||
this.state.pagePlaylistLoaded ? (
|
mediaLoaded: true,
|
||||||
<ViewerSidebar
|
isVideoMedia: isVideoMedia,
|
||||||
key="viewer-sidebar"
|
});
|
||||||
mediaId={MediaPageStore.get('media-id')}
|
}
|
||||||
playlistData={MediaPageStore.get('playlist-data')}
|
}
|
||||||
/>
|
|
||||||
) : null,
|
onViewerModeChange() {
|
||||||
]
|
this.setState({ theaterMode: VideoViewerStore.get('in-theater-mode') });
|
||||||
: [
|
}
|
||||||
this.state.pagePlaylistLoaded ? (
|
|
||||||
<ViewerSidebar
|
onMediaLoadError(a) {
|
||||||
key="viewer-sidebar"
|
this.setState({ mediaLoadFailed: true });
|
||||||
mediaId={MediaPageStore.get('media-id')}
|
}
|
||||||
playlistData={MediaPageStore.get('playlist-data')}
|
|
||||||
/>
|
pageContent() {
|
||||||
) : null,
|
const viewerClassname = 'cf viewer-section' + (this.state.theaterMode ? ' theater-mode' : ' viewer-wide');
|
||||||
<ViewerInfoVideo key="viewer-info" />,
|
const viewerNestedClassname = 'viewer-section-nested' + (this.state.theaterMode ? ' viewer-section' : '');
|
||||||
|
|
||||||
|
return this.state.mediaLoadFailed ? (
|
||||||
|
<div className={viewerClassname}>
|
||||||
|
<ViewerError />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={viewerClassname}>
|
||||||
|
{[
|
||||||
|
<div className="viewer-container" key="viewer-container">
|
||||||
|
{this.state.mediaLoaded && this.state.pagePlaylistLoaded
|
||||||
|
? this.viewerContainerContent(MediaPageStore.get('media-data'))
|
||||||
|
: null}
|
||||||
|
</div>,
|
||||||
|
<div key="viewer-section-nested" className={viewerNestedClassname}>
|
||||||
|
{!this.state.wideLayout || (this.state.isVideoMedia && this.state.theaterMode)
|
||||||
|
? [
|
||||||
|
<ViewerInfoVideo key="viewer-info" />,
|
||||||
|
this.state.pagePlaylistLoaded ? (
|
||||||
|
<ViewerSidebar
|
||||||
|
key="viewer-sidebar"
|
||||||
|
mediaId={MediaPageStore.get('media-id')}
|
||||||
|
playlistData={MediaPageStore.get('playlist-data')}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
this.state.pagePlaylistLoaded ? (
|
||||||
|
<ViewerSidebar
|
||||||
|
key="viewer-sidebar"
|
||||||
|
mediaId={MediaPageStore.get('media-id')}
|
||||||
|
playlistData={MediaPageStore.get('playlist-data')}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
<ViewerInfoVideo key="viewer-info" />,
|
||||||
|
]}
|
||||||
|
</div>,
|
||||||
]}
|
]}
|
||||||
</div>,
|
</div>
|
||||||
]}
|
);
|
||||||
</div>
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,105 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { BrowserCache } from '../classes/';
|
import { BrowserCache } from '../classes/';
|
||||||
import { PageStore } from '../stores/';
|
import { PageStore } from '../stores/';
|
||||||
import { addClassname, removeClassname } from '../helpers/';
|
import { addClassname, removeClassname, inEmbeddedApp } from '../helpers/';
|
||||||
import SiteContext from './SiteContext';
|
import SiteContext from './SiteContext';
|
||||||
|
|
||||||
let slidingSidebarTimeout;
|
let slidingSidebarTimeout;
|
||||||
|
|
||||||
function onSidebarVisibilityChange(visibleSidebar) {
|
function onSidebarVisibilityChange(visibleSidebar) {
|
||||||
clearTimeout(slidingSidebarTimeout);
|
clearTimeout(slidingSidebarTimeout);
|
||||||
|
|
||||||
addClassname(document.body, 'sliding-sidebar');
|
addClassname(document.body, 'sliding-sidebar');
|
||||||
|
|
||||||
slidingSidebarTimeout = setTimeout(function () {
|
|
||||||
if ('media' === PageStore.get('current-page')) {
|
|
||||||
if (visibleSidebar) {
|
|
||||||
addClassname(document.body, 'overflow-hidden');
|
|
||||||
} else {
|
|
||||||
removeClassname(document.body, 'overflow-hidden');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!visibleSidebar || 767 < window.innerWidth) {
|
|
||||||
removeClassname(document.body, 'overflow-hidden');
|
|
||||||
} else {
|
|
||||||
addClassname(document.body, 'overflow-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visibleSidebar) {
|
|
||||||
addClassname(document.body, 'visible-sidebar');
|
|
||||||
} else {
|
|
||||||
removeClassname(document.body, 'visible-sidebar');
|
|
||||||
}
|
|
||||||
|
|
||||||
slidingSidebarTimeout = setTimeout(function () {
|
slidingSidebarTimeout = setTimeout(function () {
|
||||||
slidingSidebarTimeout = null;
|
if ('media' === PageStore.get('current-page')) {
|
||||||
removeClassname(document.body, 'sliding-sidebar');
|
if (visibleSidebar) {
|
||||||
}, 220);
|
addClassname(document.body, 'overflow-hidden');
|
||||||
}, 20);
|
} else {
|
||||||
|
removeClassname(document.body, 'overflow-hidden');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!visibleSidebar || 767 < window.innerWidth) {
|
||||||
|
removeClassname(document.body, 'overflow-hidden');
|
||||||
|
} else {
|
||||||
|
addClassname(document.body, 'overflow-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleSidebar) {
|
||||||
|
addClassname(document.body, 'visible-sidebar');
|
||||||
|
} else {
|
||||||
|
removeClassname(document.body, 'visible-sidebar');
|
||||||
|
}
|
||||||
|
|
||||||
|
slidingSidebarTimeout = setTimeout(function () {
|
||||||
|
slidingSidebarTimeout = null;
|
||||||
|
removeClassname(document.body, 'sliding-sidebar');
|
||||||
|
}, 220);
|
||||||
|
}, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LayoutContext = createContext();
|
export const LayoutContext = createContext();
|
||||||
|
|
||||||
export const LayoutProvider = ({ children }) => {
|
export const LayoutProvider = ({ children }) => {
|
||||||
const site = useContext(SiteContext);
|
const site = useContext(SiteContext);
|
||||||
const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
|
const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
|
||||||
|
|
||||||
const enabledSidebar = !!(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar'));
|
const isMediaPage = useMemo(() => PageStore.get('current-page') === 'media' || window.MediaCMS?.mediaId !== undefined, []);
|
||||||
|
const isEmbeddedApp = useMemo(() => inEmbeddedApp(), []);
|
||||||
|
|
||||||
const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar'));
|
const enabledSidebar = Boolean(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar'));
|
||||||
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
|
|
||||||
|
|
||||||
const toggleMobileSearch = () => {
|
const [visibleSidebar, setVisibleSidebar] = useState(
|
||||||
setVisibleMobileSearch(!visibleMobileSearch);
|
isMediaPage || isEmbeddedApp ? false : cache.get('visible-sidebar')
|
||||||
};
|
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
|
||||||
const newval = !visibleSidebar;
|
|
||||||
onSidebarVisibilityChange(newval);
|
|
||||||
setVisibleSidebar(newval);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visibleSidebar) {
|
|
||||||
addClassname(document.body, 'visible-sidebar');
|
|
||||||
} else {
|
|
||||||
removeClassname(document.body, 'visible-sidebar');
|
|
||||||
}
|
|
||||||
if ('media' !== PageStore.get('current-page') && 1023 < window.innerWidth) {
|
|
||||||
cache.set('visible-sidebar', visibleSidebar);
|
|
||||||
}
|
|
||||||
}, [visibleSidebar]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
PageStore.once('page_init', () => {
|
|
||||||
if ('media' === PageStore.get('current-page')) {
|
|
||||||
setVisibleSidebar(false);
|
|
||||||
removeClassname(document.body, 'visible-sidebar');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setVisibleSidebar(
|
|
||||||
'media' !== PageStore.get('current-page') &&
|
|
||||||
1023 < window.innerWidth &&
|
|
||||||
(null === visibleSidebar || visibleSidebar)
|
|
||||||
);
|
);
|
||||||
}, []);
|
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
|
||||||
|
|
||||||
const value = {
|
const toggleMobileSearch = () => {
|
||||||
enabledSidebar,
|
setVisibleMobileSearch(!visibleMobileSearch);
|
||||||
visibleSidebar,
|
};
|
||||||
setVisibleSidebar,
|
|
||||||
visibleMobileSearch,
|
|
||||||
toggleMobileSearch,
|
|
||||||
toggleSidebar,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>;
|
const toggleSidebar = () => {
|
||||||
|
const newval = !visibleSidebar;
|
||||||
|
onSidebarVisibilityChange(newval);
|
||||||
|
setVisibleSidebar(newval);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEmbeddedApp && visibleSidebar) {
|
||||||
|
addClassname(document.body, 'visible-sidebar');
|
||||||
|
} else {
|
||||||
|
removeClassname(document.body, 'visible-sidebar');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth) {
|
||||||
|
cache.set('visible-sidebar', visibleSidebar);
|
||||||
|
}
|
||||||
|
}, [isEmbeddedApp, isMediaPage, visibleSidebar]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
PageStore.once('page_init', () => {
|
||||||
|
if (isEmbeddedApp || isMediaPage) {
|
||||||
|
setVisibleSidebar(false);
|
||||||
|
removeClassname(document.body, 'visible-sidebar');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setVisibleSidebar(
|
||||||
|
!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth && (null === visibleSidebar || visibleSidebar)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
enabledSidebar,
|
||||||
|
visibleSidebar,
|
||||||
|
setVisibleSidebar,
|
||||||
|
visibleMobileSearch,
|
||||||
|
toggleMobileSearch,
|
||||||
|
toggleSidebar,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayoutConsumer = LayoutContext.Consumer;
|
export const LayoutConsumer = LayoutContext.Consumer;
|
||||||
|
|||||||
51
frontend/src/static/js/utils/helpers/embeddedApp.ts
Normal file
51
frontend/src/static/js/utils/helpers/embeddedApp.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
export function inEmbeddedApp() {
|
||||||
|
try {
|
||||||
|
const params = new URL(globalThis.location.href).searchParams;
|
||||||
|
const mode = params.get('mode');
|
||||||
|
|
||||||
|
if (mode === 'lms_embed_mode') {
|
||||||
|
sessionStorage.setItem('lms_embed_mode', 'true');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'standard') {
|
||||||
|
sessionStorage.removeItem('lms_embed_mode');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionStorage.getItem('lms_embed_mode') === 'true';
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSelectMediaMode() {
|
||||||
|
try {
|
||||||
|
const params = new URL(globalThis.location.href).searchParams;
|
||||||
|
const action = params.get('action');
|
||||||
|
|
||||||
|
return action === 'select_media';
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inSelectMediaEmbedMode() {
|
||||||
|
return inEmbeddedApp() && isSelectMediaMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLtiContextId(): string | null {
|
||||||
|
try {
|
||||||
|
const params = new URL(globalThis.location.href).searchParams;
|
||||||
|
const contextId = params.get('lti_context_id');
|
||||||
|
|
||||||
|
if (contextId) {
|
||||||
|
sessionStorage.setItem('lti_context_id', contextId);
|
||||||
|
return contextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionStorage.getItem('lti_context_id');
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,3 +14,4 @@ export * from './quickSort';
|
|||||||
export * from './requests';
|
export * from './requests';
|
||||||
export { translateString } from './translate';
|
export { translateString } from './translate';
|
||||||
export { replaceString } from './replacementStrings';
|
export { replaceString } from './replacementStrings';
|
||||||
|
export { inEmbeddedApp, inSelectMediaEmbedMode, isSelectMediaMode } from './embeddedApp';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// check templates/config/installation/translations.html for more
|
// check templates/config/installation/translations.html for more
|
||||||
|
|
||||||
export function translateString(str) {
|
export function translateString(str) {
|
||||||
return window.TRANSLATION?.[str] ?? str;
|
return window.TRANSLATION?.[str] || str;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ export function useBulkActions() {
|
|||||||
setShowConfirmModal(true);
|
setShowConfirmModal(true);
|
||||||
setPendingAction(action);
|
setPendingAction(action);
|
||||||
setConfirmMessage(translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
setConfirmMessage(translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||||
|
} else if (action === 'delete-comments') {
|
||||||
|
setShowConfirmModal(true);
|
||||||
|
setPendingAction(action);
|
||||||
|
setConfirmMessage(translateString('You are going to delete all comments from') + ` ${selectedCount} ` + translateString('media, are you sure?'));
|
||||||
} else if (action === 'enable-download') {
|
} else if (action === 'enable-download') {
|
||||||
setShowConfirmModal(true);
|
setShowConfirmModal(true);
|
||||||
setPendingAction(action);
|
setPendingAction(action);
|
||||||
@@ -165,6 +169,8 @@ export function useBulkActions() {
|
|||||||
executeEnableComments();
|
executeEnableComments();
|
||||||
} else if (action === 'disable-comments') {
|
} else if (action === 'disable-comments') {
|
||||||
executeDisableComments();
|
executeDisableComments();
|
||||||
|
} else if (action === 'delete-comments') {
|
||||||
|
executeDeleteComments();
|
||||||
} else if (action === 'enable-download') {
|
} else if (action === 'enable-download') {
|
||||||
executeEnableDownload();
|
executeEnableDownload();
|
||||||
} else if (action === 'disable-download') {
|
} else if (action === 'disable-download') {
|
||||||
@@ -271,6 +277,37 @@ export function useBulkActions() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Execute delete comments
|
||||||
|
const executeDeleteComments = () => {
|
||||||
|
const selectedIds = Array.from(selectedMedia);
|
||||||
|
|
||||||
|
fetch('/api/v1/media/user/bulk_actions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': getCsrfToken(),
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'delete_comments',
|
||||||
|
media_ids: selectedIds,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to delete comments');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
showNotificationMessage(translateString('Successfully deleted comments'));
|
||||||
|
clearSelection();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showNotificationMessage(translateString('Failed to delete comments.'), 'error');
|
||||||
|
clearSelection();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Execute enable download
|
// Execute enable download
|
||||||
const executeEnableDownload = () => {
|
const executeEnableDownload = () => {
|
||||||
const selectedIds = Array.from(selectedMedia);
|
const selectedIds = Array.from(selectedMedia);
|
||||||
|
|||||||
@@ -3,64 +3,83 @@ import ReactDOM from 'react-dom';
|
|||||||
import { ThemeProvider } from './contexts/ThemeContext';
|
import { ThemeProvider } from './contexts/ThemeContext';
|
||||||
import { LayoutProvider } from './contexts/LayoutContext';
|
import { LayoutProvider } from './contexts/LayoutContext';
|
||||||
import { UserProvider } from './contexts/UserContext';
|
import { UserProvider } from './contexts/UserContext';
|
||||||
|
import { inEmbeddedApp } from './helpers';
|
||||||
|
|
||||||
const AppProviders = ({ children }) => (
|
const AppProviders = ({ children }) => (
|
||||||
<LayoutProvider>
|
<LayoutProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<UserProvider>{children}</UserProvider>
|
<UserProvider>{children}</UserProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</LayoutProvider>
|
</LayoutProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
import { PageHeader, PageSidebar } from '../components/page-layout';
|
import { PageHeader, PageSidebar } from '../components/page-layout';
|
||||||
|
|
||||||
export function renderPage(idSelector, PageComponent) {
|
export function renderPage(idSelector, PageComponent) {
|
||||||
const appHeader = document.getElementById('app-header');
|
if (inEmbeddedApp()) {
|
||||||
const appSidebar = document.getElementById('app-sidebar');
|
globalThis.document.body.classList.add('embedded-app');
|
||||||
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
globalThis.document.body.classList.remove('visible-sidebar');
|
||||||
|
|
||||||
if (appContent && PageComponent) {
|
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
||||||
ReactDOM.render(
|
|
||||||
<AppProviders>
|
if (appContent && PageComponent) {
|
||||||
{appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
|
ReactDOM.render(
|
||||||
{appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null}
|
<AppProviders>
|
||||||
<PageComponent />
|
<PageComponent />
|
||||||
</AppProviders>,
|
</AppProviders>,
|
||||||
appContent
|
appContent
|
||||||
);
|
);
|
||||||
} else if (appHeader && appSidebar) {
|
}
|
||||||
ReactDOM.render(
|
|
||||||
<AppProviders>
|
return;
|
||||||
{ReactDOM.createPortal(<PageHeader />, appHeader)}
|
}
|
||||||
<PageSidebar />
|
|
||||||
</AppProviders>,
|
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
||||||
appSidebar
|
const appHeader = document.getElementById('app-header');
|
||||||
);
|
const appSidebar = document.getElementById('app-sidebar');
|
||||||
} else if (appHeader) {
|
|
||||||
ReactDOM.render(
|
if (appContent && PageComponent) {
|
||||||
<LayoutProvider>
|
ReactDOM.render(
|
||||||
<ThemeProvider>
|
<AppProviders>
|
||||||
<UserProvider>
|
{appHeader ? ReactDOM.createPortal(<PageHeader />, appHeader) : null}
|
||||||
<PageHeader />
|
{appSidebar ? ReactDOM.createPortal(<PageSidebar />, appSidebar) : null}
|
||||||
</UserProvider>
|
<PageComponent />
|
||||||
</ThemeProvider>
|
</AppProviders>,
|
||||||
</LayoutProvider>,
|
appContent
|
||||||
appSidebar
|
);
|
||||||
);
|
} else if (appHeader && appSidebar) {
|
||||||
} else if (appSidebar) {
|
ReactDOM.render(
|
||||||
ReactDOM.render(
|
<AppProviders>
|
||||||
<AppProviders>
|
{ReactDOM.createPortal(<PageHeader />, appHeader)}
|
||||||
<PageSidebar />
|
<PageSidebar />
|
||||||
</AppProviders>,
|
</AppProviders>,
|
||||||
appSidebar
|
appSidebar
|
||||||
);
|
);
|
||||||
}
|
} else if (appHeader) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<LayoutProvider>
|
||||||
|
<ThemeProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<PageHeader />
|
||||||
|
</UserProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</LayoutProvider>,
|
||||||
|
appSidebar
|
||||||
|
);
|
||||||
|
} else if (appSidebar) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppProviders>
|
||||||
|
<PageSidebar />
|
||||||
|
</AppProviders>,
|
||||||
|
appSidebar
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderEmbedPage(idSelector, PageComponent) {
|
export function renderEmbedPage(idSelector, PageComponent) {
|
||||||
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
|
||||||
|
|
||||||
if (appContent && PageComponent) {
|
if (appContent && PageComponent) {
|
||||||
ReactDOM.render(<PageComponent />, appContent);
|
ReactDOM.render(<PageComponent />, appContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,13 +195,18 @@ class MediaPageStore extends EventEmitter {
|
|||||||
this.emit('loaded_media_data');
|
this.emit('loaded_media_data');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadPlaylists();
|
// Skip loading playlists and comments when in embed mode (to reduce API calls)
|
||||||
if (MediaCMS.features.media.actions.comment_mention === true) {
|
const isEmbedMode = window.location.pathname.startsWith('/embed');
|
||||||
this.loadUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mediacms_config.member.can.readComment) {
|
if (!isEmbedMode) {
|
||||||
this.loadComments();
|
this.loadPlaylists();
|
||||||
|
if (MediaCMS.features.media.actions.comment_mention === true) {
|
||||||
|
this.loadUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mediacms_config.member.can.readComment) {
|
||||||
|
this.loadComments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1745
frontend/yarn.lock
1745
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
49
lms-plugins/mediacms-moodle/Gruntfile.js
Normal file
49
lms-plugins/mediacms-moodle/Gruntfile.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
module.exports = function(grunt) {
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
grunt.initConfig({
|
||||||
|
babel: {
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
presets: ['@babel/preset-env'],
|
||||||
|
plugins: ['@babel/plugin-transform-modules-amd'],
|
||||||
|
moduleIds: true,
|
||||||
|
getModuleId: function(moduleName) {
|
||||||
|
// moduleName is the absolute or relative path to the file.
|
||||||
|
// We need to convert 'tiny/mediacms/amd/src/filename' to 'tiny_mediacms/filename'
|
||||||
|
var filename = path.basename(moduleName);
|
||||||
|
return 'tiny_mediacms/' + filename;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dist: {
|
||||||
|
files: [{
|
||||||
|
expand: true,
|
||||||
|
cwd: 'tiny/mediacms/amd/src',
|
||||||
|
src: ['*.js'],
|
||||||
|
dest: 'tiny/mediacms/amd/build',
|
||||||
|
ext: '.js'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uglify: {
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
output: { comments: false }
|
||||||
|
},
|
||||||
|
dist: {
|
||||||
|
files: [{
|
||||||
|
expand: true,
|
||||||
|
cwd: 'tiny/mediacms/amd/build',
|
||||||
|
src: ['*.js', '!*.min.js'],
|
||||||
|
dest: 'tiny/mediacms/amd/build',
|
||||||
|
ext: '.min.js'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
|
|
||||||
|
grunt.registerTask('default', ['babel', 'uglify']);
|
||||||
|
};
|
||||||
72
lms-plugins/mediacms-moodle/INSTALL.txt
Normal file
72
lms-plugins/mediacms-moodle/INSTALL.txt
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
================================================================================
|
||||||
|
MediaCMS Moodle Plugin Suite v1.0.0
|
||||||
|
Installation Guide
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
- Moodle 4.5 or later
|
||||||
|
- MediaCMS instance
|
||||||
|
- MediaCMS set as External Tool
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
1. Extract zip file to Moodle root public directory:
|
||||||
|
|
||||||
|
cd /var/www/moodle/public
|
||||||
|
unzip mediacms-moodle-v1.0.0.zip
|
||||||
|
|
||||||
|
This will place files in:
|
||||||
|
- filter/mediacms/
|
||||||
|
- lib/editor/tiny/plugins/mediacms/
|
||||||
|
|
||||||
|
2. Set permissions
|
||||||
|
chown -R www-data:www-data filter/mediacms
|
||||||
|
chown -R www-data:www-data lib/editor/tiny/plugins/mediacms
|
||||||
|
|
||||||
|
3. Install through Moodle
|
||||||
|
- Log in as Administrator
|
||||||
|
- Go to: Site Administration → Notifications
|
||||||
|
- Click "Upgrade Moodle database now"
|
||||||
|
- Both plugins will be installed automatically
|
||||||
|
- Set the MediaCMS tool under the LTI Tool
|
||||||
|
|
||||||
|
4. Make sure Filter is enabled
|
||||||
|
- As Administrator, visit Plugins, 'Manage Filters', find MediaCMS filter and enable it.
|
||||||
|
Then place it at the top of the filter. This is important, otherwise embeds won't load.
|
||||||
|
|
||||||
|
5. Enter 'My Media' on top navigation.
|
||||||
|
- Log in as Administrator
|
||||||
|
- Go to: Site Administration → Appearance → Advanced Theme settings -> Custom menu items:
|
||||||
|
add: My Media|/filter/mediacms/my_media.php
|
||||||
|
Notes: User needs to be enrolled on one course at least, otherwise visiting My Media won't show anything.
|
||||||
|
The plugin needs to use the ID of an existing course to the 'My Media' page and thus it is going to use it
|
||||||
|
and create a 'my media' page on the course, if it doesn't already exist. This is happening automatically the
|
||||||
|
first time a user visits the global 'my media' link, if a 'my media' page activity does not exist in the course, the
|
||||||
|
plugin will create it. If you don't want the 'my media' page to show up on the course, you can place it to a hidden part
|
||||||
|
Create a part (eg 'Hidden Links'), select the Edit options (three vertical dots) and Hide it.
|
||||||
|
Then place the 'My Media' activity there, select the three dots for the activity, Availability, and set "Make available but don't show on course page: Available to students if you provide a link."
|
||||||
|
This option won't appear if the part is not hidden, so first you want to create the part, hide it, and then edit the setting for 'my media'
|
||||||
|
|
||||||
|
To summarize, for the 'my media' global link on top, a 'my media' is also required in each course, and you can either
|
||||||
|
create it (as MediaCMS activity with any name) or let the first user that visits the global 'my media' have it created automatically.
|
||||||
|
If you don't want this activity to be visible in the course, place it under a hidden part and set it's availability as "Make available but don't show on course page", which is only possible for items inside hidden parts.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
What to expect
|
||||||
|
-------
|
||||||
|
|
||||||
|
1. Create a test course
|
||||||
|
2. Add a page or label
|
||||||
|
3. Click MediaCMS button in TinyMCE editor
|
||||||
|
4. Try inserting from video library or pasting a URL
|
||||||
|
|
||||||
|
SUPPORT
|
||||||
|
-------
|
||||||
|
Issues: https://github.com/mediacms-io/mediacms/issues
|
||||||
|
Docs: https://docs.mediacms.io
|
||||||
|
|
||||||
|
================================================================================
|
||||||
114
lms-plugins/mediacms-moodle/README.md
Normal file
114
lms-plugins/mediacms-moodle/README.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# MediaCMS for Moodle
|
||||||
|
|
||||||
|
**Version:** 1.0.0 | **Release Date:** 2026-02-12 | **Moodle:** 4.5+
|
||||||
|
|
||||||
|
This package provides complete MediaCMS integration for Moodle, consisting of two plugins that work together with **unified settings**:
|
||||||
|
|
||||||
|
1. **Filter Plugin (filter_mediacms):**
|
||||||
|
* Handles LTI 1.3 authentication and secure video launches
|
||||||
|
* Auto-converts MediaCMS URLs to embedded players
|
||||||
|
* **Provides core settings** (MediaCMS URL, LTI Tool ID) used by both plugins
|
||||||
|
* **Location:** `filter/mediacms`
|
||||||
|
|
||||||
|
2. **Editor Plugin (tiny_mediacms):**
|
||||||
|
* Adds MediaCMS button to TinyMCE editor
|
||||||
|
* Browse authenticated video library via LTI Deep Linking
|
||||||
|
* Configure embed options (dimensions, display, start time)
|
||||||
|
* **Reads core settings** from filter plugin
|
||||||
|
* **Location:** `lib/editor/tiny/plugins/mediacms`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This package is distributed as a single repository but contains two distinct Moodle plugins that must be installed in their respective directories.
|
||||||
|
|
||||||
|
### 1. Copy Files
|
||||||
|
|
||||||
|
Copy the directories into your Moodle installation as follows (example assuming Moodle is at `/var/www/moodle/public`):
|
||||||
|
|
||||||
|
* Copy `filter/mediacms` to `/var/www/moodle/public/filter/mediacms`.
|
||||||
|
* Copy `tiny/mediacms` to `/var/www/moodle/public/lib/editor/tiny/plugins/mediacms`.
|
||||||
|
|
||||||
|
### 2. Set Permissions
|
||||||
|
|
||||||
|
Ensure the web server user (typically `www-data`) has ownership of the new directories:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example for Ubuntu/Debian systems
|
||||||
|
chown -R www-data:www-data /var/www/moodle/public/filter/mediacms
|
||||||
|
chown -R www-data:www-data /var/www/moodle/public/lib/editor/tiny/plugins/mediacms
|
||||||
|
chmod -R 755 /var/www/moodle/public/filter/mediacms
|
||||||
|
chmod -R 755 /var/www/moodle/public/lib/editor/tiny/plugins/mediacms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install Plugins
|
||||||
|
|
||||||
|
1. Log in to Moodle as an Administrator.
|
||||||
|
2. Go to **Site administration > Notifications**.
|
||||||
|
3. Follow the prompts to upgrade the database and install the new plugins.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Step 1: Core Settings (Required) - Configure Once
|
||||||
|
|
||||||
|
Go to **Site administration > Plugins > Filters > MediaCMS** (Settings)
|
||||||
|
|
||||||
|
* **MediaCMS URL:** Enter your MediaCMS instance URL (e.g., `https://lti.mediacms.io`)
|
||||||
|
* **LTI Tool:** Select the External Tool configuration for MediaCMS
|
||||||
|
* *First create an LTI 1.3 tool at: Site administration > Plugins > Activity modules > External tool > Manage tools*
|
||||||
|
|
||||||
|
> **✨ Note:** These core settings are automatically used by **both** the filter and TinyMCE editor plugin.
|
||||||
|
|
||||||
|
### Step 2: Enable Filter
|
||||||
|
|
||||||
|
1. Go to **Site administration > Plugins > Filters > Manage filters**
|
||||||
|
2. Set **MediaCMS** to "On"
|
||||||
|
|
||||||
|
### Step 3: Configure Auto-convert Defaults (Optional)
|
||||||
|
|
||||||
|
Go to **Site administration > Plugins > Text editors > TinyMCE editor > MediaCMS settings**
|
||||||
|
|
||||||
|
Configure default display options for auto-converted URLs:
|
||||||
|
* Show video title
|
||||||
|
* Link video title
|
||||||
|
* Show related videos
|
||||||
|
* Show user avatar
|
||||||
|
|
||||||
|
> **Note:** The core settings (URL, LTI Tool) are managed in the filter plugin settings.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### For Teachers (Editor)
|
||||||
|
|
||||||
|
1. In any text editor (TinyMCE), click the **MediaCMS** icon (or "Insert MediaCMS Media" from the Insert menu).
|
||||||
|
2. You can:
|
||||||
|
* **Paste a URL:** Paste a View or Embed URL.
|
||||||
|
* **Video Library:** Click the "Video Library" tab to browse and select videos (requires LTI Deep Linking configuration).
|
||||||
|
3. The video will appear as a placeholder or iframe in the editor.
|
||||||
|
|
||||||
|
### For Students (Display)
|
||||||
|
|
||||||
|
When content is viewed, the Filter will ensure the video is loaded securely via LTI 1.3, authenticating the user with MediaCMS automatically.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Build instructions / Developing with the plugin
|
||||||
|
|
||||||
|
two types of changes: php (no build), js (build with npx grunt amd)
|
||||||
|
|
||||||
|
needs moodle/
|
||||||
|
npx version, dependencies etc
|
||||||
|
|
||||||
|
1. make changes here in lms-plugins/mediacms-moodle
|
||||||
|
2. copy to moodle
|
||||||
|
3. run `npx grunt amd` in moodle to build the JS files
|
||||||
|
4. from moodle copy back
|
||||||
|
sudo cp -r ~/mediacms/lms-plugins/mediacms-moodle/tiny/mediacms/ -r ~/mediacms/moodle/public/lib/editor/tiny/plugins/
|
||||||
|
|
||||||
|
5. cd ~/mediacms/moodle/public/lib/editor/tiny/plugins/mediacms/
|
||||||
|
|
||||||
|
npx grunt amd
|
||||||
|
6.
|
||||||
|
cp files back...
|
||||||
|
sudo cp -r /home/user/mediacms/moodle/public/lib/editor/tiny/plugins/mediacms /home/user/mediacms/lms-plugins/mediacms-moodle/tiny/
|
||||||
|
|
||||||
|
php admin/cli/purge_caches.php after
|
||||||
89
lms-plugins/mediacms-moodle/build.sh
Executable file
89
lms-plugins/mediacms-moodle/build.sh
Executable file
@@ -0,0 +1,89 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# MediaCMS Moodle Plugin Suite - Build Script
|
||||||
|
# Creates distributable ZIP package
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${GREEN}======================================${NC}"
|
||||||
|
echo -e "${GREEN}MediaCMS Moodle Plugin Suite Builder${NC}"
|
||||||
|
echo -e "${GREEN}======================================${NC}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
VERSION="1.0.0"
|
||||||
|
BUILD_DATE=$(date +%Y%m%d)
|
||||||
|
PACKAGE_NAME="mediacms-moodle-v${VERSION}"
|
||||||
|
DIST_DIR="dist"
|
||||||
|
BUILD_DIR="${DIST_DIR}/${PACKAGE_NAME}"
|
||||||
|
|
||||||
|
# Create clean dist directory
|
||||||
|
echo -e "${YELLOW}→${NC} Cleaning dist directory..."
|
||||||
|
rm -rf "${DIST_DIR}"
|
||||||
|
mkdir -p "${BUILD_DIR}"
|
||||||
|
|
||||||
|
# Copy filter plugin
|
||||||
|
echo -e "${YELLOW}→${NC} Copying filter plugin..."
|
||||||
|
mkdir -p "${BUILD_DIR}/filter"
|
||||||
|
cp -r filter/mediacms "${BUILD_DIR}/filter/"
|
||||||
|
|
||||||
|
# Copy TinyMCE plugin
|
||||||
|
echo -e "${YELLOW}→${NC} Copying TinyMCE plugin..."
|
||||||
|
mkdir -p "${BUILD_DIR}/lib/editor/tiny/plugins"
|
||||||
|
cp -r tiny/mediacms "${BUILD_DIR}/lib/editor/tiny/plugins/"
|
||||||
|
|
||||||
|
# Copy documentation
|
||||||
|
echo -e "${YELLOW}→${NC} Copying documentation..."
|
||||||
|
cp README.md "${BUILD_DIR}/filter/mediacms/"
|
||||||
|
cp INSTALL.txt "${BUILD_DIR}/filter/mediacms/"
|
||||||
|
|
||||||
|
# Clean up development files
|
||||||
|
echo -e "${YELLOW}→${NC} Removing development files..."
|
||||||
|
find "${BUILD_DIR}" -type d -name "node_modules" -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
find "${BUILD_DIR}" -type f -name ".DS_Store" -delete 2>/dev/null || true
|
||||||
|
find "${BUILD_DIR}" -type f -name "*.log" -delete 2>/dev/null || true
|
||||||
|
find "${BUILD_DIR}" -type d -name ".git" -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
find "${BUILD_DIR}" -type f -name ".gitignore" -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
# Remove AMD source files (keep only built versions)
|
||||||
|
echo -e "${YELLOW}→${NC} Cleaning AMD source files..."
|
||||||
|
find "${BUILD_DIR}/lib/editor/tiny/plugins/mediacms/amd" -type f -name "*.js" ! -name "*-lazy.js" ! -path "*/build/*" -delete 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create ZIP archive
|
||||||
|
echo -e "${YELLOW}→${NC} Creating ZIP archive..."
|
||||||
|
cd "${BUILD_DIR}"
|
||||||
|
zip -r "../${PACKAGE_NAME}.zip" . -q
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
# Create checksum
|
||||||
|
echo -e "${YELLOW}→${NC} Generating checksum..."
|
||||||
|
cd "${DIST_DIR}"
|
||||||
|
sha256sum "${PACKAGE_NAME}.zip" > "${PACKAGE_NAME}.zip.sha256"
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Display results
|
||||||
|
ZIP_SIZE=$(du -h "${DIST_DIR}/${PACKAGE_NAME}.zip" | cut -f1)
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}✓ Build complete!${NC}"
|
||||||
|
echo
|
||||||
|
echo "Package: ${DIST_DIR}/${PACKAGE_NAME}.zip"
|
||||||
|
echo "Size: ${ZIP_SIZE}"
|
||||||
|
echo "Checksum: ${DIST_DIR}/${PACKAGE_NAME}.zip.sha256"
|
||||||
|
echo
|
||||||
|
echo -e "${YELLOW}Contents:${NC}"
|
||||||
|
echo " - filter/mediacms/ (includes docs)"
|
||||||
|
echo " - lib/editor/tiny/plugins/mediacms/"
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}Ready for distribution!${NC}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Show checksum
|
||||||
|
echo -e "${YELLOW}SHA256 Checksum:${NC}"
|
||||||
|
cat "${DIST_DIR}/${PACKAGE_NAME}.zip.sha256"
|
||||||
|
echo
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace filter_mediacms\privacy;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
class provider implements \core_privacy\local\metadata\null_provider {
|
||||||
|
public static function get_reason(): string {
|
||||||
|
return 'privacy:metadata';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace filter_mediacms;
|
||||||
|
|
||||||
|
use moodle_url;
|
||||||
|
use html_writer;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MediaCMS text filter.
|
||||||
|
*
|
||||||
|
* @package filter_mediacms
|
||||||
|
* @copyright 2026 MediaCMS
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class text_filter extends \core_filters\text_filter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter method.
|
||||||
|
*
|
||||||
|
* @param string $text The text to filter.
|
||||||
|
* @param array $options Filter options.
|
||||||
|
* @return string The filtered text.
|
||||||
|
*/
|
||||||
|
public function filter($text, array $options = array()) {
|
||||||
|
if (!is_string($text) or empty($text)) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediacmsurl = get_config('filter_mediacms', 'mediacmsurl');
|
||||||
|
if (empty($mediacmsurl)) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newtext = $text;
|
||||||
|
|
||||||
|
// 1. Handle [mediacms:TOKEN] tag
|
||||||
|
$pattern_tag = '/\[mediacms:([a-zA-Z0-9]+)\]/';
|
||||||
|
$newtext = preg_replace_callback($pattern_tag, [$this, 'callback_tag'], $newtext);
|
||||||
|
|
||||||
|
// 2. Auto-convert MediaCMS URLs to embedded players
|
||||||
|
// First, protect text-only links from being converted
|
||||||
|
// by temporarily replacing them with placeholders
|
||||||
|
$textlink_placeholders = [];
|
||||||
|
$textlink_pattern = '/<a\s+[^>]*data-mediacms-textlink=["\']true["\'][^>]*>.*?<\/a>/is';
|
||||||
|
|
||||||
|
$newtext = preg_replace_callback($textlink_pattern, function($matches) use (&$textlink_placeholders) {
|
||||||
|
$placeholder = '###MEDIACMS_TEXTLINK_' . count($textlink_placeholders) . '###';
|
||||||
|
$textlink_placeholders[$placeholder] = $matches[0];
|
||||||
|
return $placeholder;
|
||||||
|
}, $newtext);
|
||||||
|
|
||||||
|
// Regex for MediaCMS view URLs: https://domain/view?m=TOKEN
|
||||||
|
// We need to be careful to match the configured domain
|
||||||
|
$parsed_url = parse_url($mediacmsurl);
|
||||||
|
$host = preg_quote($parsed_url['host'] ?? '', '/');
|
||||||
|
$scheme = preg_quote($parsed_url['scheme'] ?? 'https', '/');
|
||||||
|
|
||||||
|
// Allow http or https, and optional path prefix
|
||||||
|
$path_prefix = preg_quote(rtrim($parsed_url['path'] ?? '', '/'), '/');
|
||||||
|
|
||||||
|
// Pattern: https://HOST/PREFIX/view?m=TOKEN
|
||||||
|
// Also handle /embed?m=TOKEN
|
||||||
|
$pattern_url = '/(' . $scheme . ':\/\/' . $host . $path_prefix . '\/(view|embed)\?m=([a-zA-Z0-9]+)(?:&[^\s<]*)?)/';
|
||||||
|
|
||||||
|
$newtext = preg_replace_callback($pattern_url, [$this, 'callback_url'], $newtext);
|
||||||
|
|
||||||
|
// Restore protected text-only links as modal launchers
|
||||||
|
foreach ($textlink_placeholders as $placeholder => $original) {
|
||||||
|
$newtext = str_replace($placeholder, $this->transform_textlink($original), $newtext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for [mediacms:TOKEN]
|
||||||
|
*/
|
||||||
|
public function callback_tag($matches) {
|
||||||
|
return $this->generate_iframe($matches[1], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for URLs
|
||||||
|
*/
|
||||||
|
public function callback_url($matches) {
|
||||||
|
// matches[0] is the full matched string
|
||||||
|
// matches[1] is full URL, matches[3] is token
|
||||||
|
|
||||||
|
// Check if this URL is inside a text-only link
|
||||||
|
// by looking at the context around the match
|
||||||
|
$fullmatch = $matches[0];
|
||||||
|
|
||||||
|
// If this is already inside an <a> tag with data-mediacms-textlink="true",
|
||||||
|
// return the original URL unchanged
|
||||||
|
// We'll check this in the main filter method instead
|
||||||
|
|
||||||
|
$token = $matches[3];
|
||||||
|
|
||||||
|
// Extract additional embed parameters from the URL
|
||||||
|
$embed_params = [];
|
||||||
|
$full_url = $matches[1];
|
||||||
|
|
||||||
|
// Decode HTML entities (& -> &) before parsing
|
||||||
|
$full_url = html_entity_decode($full_url, ENT_QUOTES | ENT_HTML5);
|
||||||
|
|
||||||
|
$parsed_url = parse_url($full_url);
|
||||||
|
|
||||||
|
if (isset($parsed_url['query'])) {
|
||||||
|
parse_str($parsed_url['query'], $query_params);
|
||||||
|
|
||||||
|
// Extract embed-related parameters
|
||||||
|
$supported_params = ['showTitle', 'showRelated', 'showUserAvatar', 'linkTitle', 't', 'width', 'height'];
|
||||||
|
foreach ($supported_params as $param) {
|
||||||
|
if (isset($query_params[$param])) {
|
||||||
|
$embed_params[$param] = $query_params[$param];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->generate_iframe($token, $embed_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the Iframe pointing to launch.php
|
||||||
|
*/
|
||||||
|
private function generate_iframe($token, $embed_params = []) {
|
||||||
|
global $CFG, $COURSE;
|
||||||
|
|
||||||
|
// Use width/height from embed params if provided, no defaults
|
||||||
|
$width = isset($embed_params['width']) ? $embed_params['width'] : null;
|
||||||
|
$height = isset($embed_params['height']) ? $embed_params['height'] : null;
|
||||||
|
$courseid = $COURSE->id ?? 0;
|
||||||
|
|
||||||
|
// Build launch URL parameters
|
||||||
|
$launch_params = [
|
||||||
|
'token' => $token,
|
||||||
|
'courseid' => $courseid
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add width/height only if provided
|
||||||
|
if ($width !== null) {
|
||||||
|
$launch_params['width'] = $width;
|
||||||
|
}
|
||||||
|
if ($height !== null) {
|
||||||
|
$launch_params['height'] = $height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other embed parameters if provided (excluding width/height as they're already handled)
|
||||||
|
foreach ($embed_params as $key => $value) {
|
||||||
|
if ($key !== 'width' && $key !== 'height') {
|
||||||
|
$launch_params[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$launchurl = new moodle_url('/filter/mediacms/launch.php', $launch_params);
|
||||||
|
|
||||||
|
// Build iframe attributes
|
||||||
|
$iframe_attrs = [
|
||||||
|
'src' => $launchurl->out(false),
|
||||||
|
'frameborder' => 0,
|
||||||
|
'allowfullscreen' => 'allowfullscreen',
|
||||||
|
'class' => 'mediacms-embed',
|
||||||
|
'title' => 'MediaCMS Video'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add width/height attributes only if provided
|
||||||
|
if ($width !== null) {
|
||||||
|
$iframe_attrs['width'] = $width;
|
||||||
|
}
|
||||||
|
if ($height !== null) {
|
||||||
|
$iframe_attrs['height'] = $height;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iframe = html_writer::tag('iframe', '', $iframe_attrs);
|
||||||
|
|
||||||
|
return $iframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a text-only link into a link that replaces itself with an inline iframe on click.
|
||||||
|
*
|
||||||
|
* @param string $anchor_html Original <a ...>...</a> HTML
|
||||||
|
* @return string Transformed HTML (or original if token cannot be extracted)
|
||||||
|
*/
|
||||||
|
private function transform_textlink($anchor_html) {
|
||||||
|
global $COURSE;
|
||||||
|
|
||||||
|
// Extract href.
|
||||||
|
if (!preg_match('/href=["\']([^"\']+)["\']/', $anchor_html, $href_matches)) {
|
||||||
|
return $anchor_html;
|
||||||
|
}
|
||||||
|
$href = html_entity_decode($href_matches[1], ENT_QUOTES | ENT_HTML5);
|
||||||
|
|
||||||
|
// Extract ?m=TOKEN.
|
||||||
|
parse_str(parse_url($href, PHP_URL_QUERY) ?? '', $query_params);
|
||||||
|
$token = $query_params['m'] ?? null;
|
||||||
|
if (!$token || !preg_match('/^[a-zA-Z0-9]+$/', $token)) {
|
||||||
|
return $anchor_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract inner link text.
|
||||||
|
if (!preg_match('/<a[^>]*>(.*?)<\/a>/is', $anchor_html, $text_matches)) {
|
||||||
|
return $anchor_html;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view_url = new moodle_url('/filter/mediacms/my_media.php', [
|
||||||
|
'token' => $token,
|
||||||
|
'courseid' => isset($COURSE->id) ? (int)$COURSE->id : 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return html_writer::tag('a', $text_matches[1], [
|
||||||
|
'href' => $view_url->out(false),
|
||||||
|
'target' => '_blank',
|
||||||
|
'rel' => 'noopener noreferrer',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
lms-plugins/mediacms-moodle/filter/mediacms/db/hooks.php
Normal file
13
lms-plugins/mediacms-moodle/filter/mediacms/db/hooks.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Hook registrations for filter_mediacms.
|
||||||
|
* Primary navigation is handled via extend_navigation() in lib.php instead.
|
||||||
|
*
|
||||||
|
* @package filter_mediacms
|
||||||
|
* @copyright 2026 MediaCMS
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
$callbacks = [];
|
||||||
45
lms-plugins/mediacms-moodle/filter/mediacms/db/install.php
Normal file
45
lms-plugins/mediacms-moodle/filter/mediacms/db/install.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-installation hook.
|
||||||
|
*/
|
||||||
|
function xmldb_filter_mediacms_install() {
|
||||||
|
global $CFG, $DB;
|
||||||
|
require_once($CFG->libdir . '/filterlib.php');
|
||||||
|
|
||||||
|
// Enable the filter globally.
|
||||||
|
filter_set_global_state('filter_mediacms', TEXTFILTER_ON);
|
||||||
|
|
||||||
|
// Move to top priority (lowest sortorder).
|
||||||
|
$syscontextid = context_system::instance()->id;
|
||||||
|
$filters = $DB->get_records('filter_active', ['contextid' => $syscontextid], 'sortorder ASC');
|
||||||
|
|
||||||
|
if (empty($filters)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate mediacms from other filters by inspecting the record property,
|
||||||
|
// not the array key (get_records indexes by id, not by filter name).
|
||||||
|
$mediacmsrecord = null;
|
||||||
|
$otherrecords = [];
|
||||||
|
foreach ($filters as $record) {
|
||||||
|
if ($record->filter === 'filter_mediacms') {
|
||||||
|
$mediacmsrecord = $record;
|
||||||
|
} else {
|
||||||
|
$otherrecords[] = $record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reassign sortorders: mediacms first, then everyone else.
|
||||||
|
$sortorder = 1;
|
||||||
|
if ($mediacmsrecord) {
|
||||||
|
$mediacmsrecord->sortorder = $sortorder++;
|
||||||
|
$DB->update_record('filter_active', $mediacmsrecord);
|
||||||
|
}
|
||||||
|
foreach ($otherrecords as $record) {
|
||||||
|
$record->sortorder = $sortorder++;
|
||||||
|
$DB->update_record('filter_active', $record);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
$string['filtername'] = 'MediaCMS';
|
||||||
|
$string['pluginname'] = 'MediaCMS';
|
||||||
|
$string['coresettings'] = 'Core MediaCMS Settings';
|
||||||
|
$string['coresettings_desc'] = 'These settings are shared with the TinyMCE MediaCMS editor plugin.';
|
||||||
|
$string['mediacmsurl'] = 'MediaCMS URL';
|
||||||
|
$string['mediacmsurl_desc'] = 'The base URL of your MediaCMS instance (e.g., https://lti.mediacms.io). This setting is used by both the filter and the TinyMCE editor plugin.';
|
||||||
|
$string['ltitoolid'] = 'LTI Tool';
|
||||||
|
$string['ltitoolid_desc'] = 'Select the External Tool configuration for MediaCMS. This enables the video library in the TinyMCE editor and LTI authentication. To set up an LTI tool, go to Site Administration > Plugins > Activity modules > External tool > Manage tools.';
|
||||||
|
$string['noltitoolsfound'] = 'No LTI tools found';
|
||||||
|
$string['iframewidth'] = 'Default Width';
|
||||||
|
$string['iframewidth_desc'] = 'Default width for embedded videos (pixels).';
|
||||||
|
$string['iframeheight'] = 'Default Height';
|
||||||
|
$string['iframeheight_desc'] = 'Default height for embedded videos (pixels).';
|
||||||
|
$string['enableautoconvert'] = 'Auto-convert URLs';
|
||||||
|
$string['enableautoconvert_desc'] = 'Automatically convert MediaCMS URLs (e.g., /view?m=xyz) in text to embedded players.';
|
||||||
|
$string['privacy:metadata'] = 'The MediaCMS filter does not store any personal data.';
|
||||||
|
|
||||||
|
$string['mymedia'] = 'My Media';
|
||||||
|
$string['notconfigured'] = 'MediaCMS is not fully configured. Please set the MediaCMS URL and LTI Tool in Site Administration → Plugins → Filters → MediaCMS.';
|
||||||
|
$string['ltitoolnotfound'] = 'The configured LTI tool could not be found. Please check the MediaCMS filter settings.';
|
||||||
|
$string['cannotcreatedummyactivity'] = 'Could not create the MediaCMS launcher activity. Please check course permissions.';
|
||||||
168
lms-plugins/mediacms-moodle/filter/mediacms/launch.php
Normal file
168
lms-plugins/mediacms-moodle/filter/mediacms/launch.php
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* LTI Launch for MediaCMS Filter
|
||||||
|
*
|
||||||
|
* @package filter_mediacms
|
||||||
|
* @copyright 2026 MediaCMS
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../../config.php');
|
||||||
|
require_once($CFG->dirroot . '/mod/lti/lib.php');
|
||||||
|
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||||
|
|
||||||
|
global $SITE, $DB, $PAGE, $OUTPUT, $CFG, $SESSION;
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$mediatoken = required_param('token', PARAM_ALPHANUMEXT);
|
||||||
|
$courseid = optional_param('courseid', 0, PARAM_INT);
|
||||||
|
$height = optional_param('height', 0, PARAM_INT);
|
||||||
|
$width = optional_param('width', 0, PARAM_INT);
|
||||||
|
|
||||||
|
// Extract embed parameters
|
||||||
|
$showTitle = optional_param('showTitle', '', PARAM_TEXT);
|
||||||
|
$showRelated = optional_param('showRelated', '', PARAM_TEXT);
|
||||||
|
$showUserAvatar = optional_param('showUserAvatar', '', PARAM_TEXT);
|
||||||
|
$linkTitle = optional_param('linkTitle', '', PARAM_TEXT);
|
||||||
|
$startTime = optional_param('t', '', PARAM_TEXT);
|
||||||
|
$show_media_page = optional_param('show_media_page', '', PARAM_TEXT);
|
||||||
|
|
||||||
|
// Get configuration
|
||||||
|
$mediacmsurl = get_config('filter_mediacms', 'mediacmsurl');
|
||||||
|
$ltitoolid = get_config('filter_mediacms', 'ltitoolid');
|
||||||
|
|
||||||
|
if (empty($mediacmsurl)) {
|
||||||
|
die('MediaCMS URL not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = false;
|
||||||
|
if (!empty($ltitoolid)) {
|
||||||
|
$type = $DB->get_record('lti_types', ['id' => $ltitoolid]);
|
||||||
|
}
|
||||||
|
if (!$type) {
|
||||||
|
die('LTI tool not found or not configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up context
|
||||||
|
if ($courseid && $courseid != SITEID) {
|
||||||
|
$context = context_course::instance($courseid);
|
||||||
|
$course = get_course($courseid);
|
||||||
|
} else {
|
||||||
|
$context = context_system::instance();
|
||||||
|
$course = $SITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build custom params for this video embed.
|
||||||
|
$custom_params = ["media_friendly_token=" . $mediatoken];
|
||||||
|
|
||||||
|
if ($showTitle !== '') {
|
||||||
|
$custom_params[] = "embed_show_title=" . $showTitle;
|
||||||
|
}
|
||||||
|
if ($showRelated !== '') {
|
||||||
|
$custom_params[] = "embed_show_related=" . $showRelated;
|
||||||
|
}
|
||||||
|
if ($showUserAvatar !== '') {
|
||||||
|
$custom_params[] = "embed_show_user_avatar=" . $showUserAvatar;
|
||||||
|
}
|
||||||
|
if ($linkTitle !== '') {
|
||||||
|
$custom_params[] = "embed_link_title=" . $linkTitle;
|
||||||
|
}
|
||||||
|
if ($startTime !== '') {
|
||||||
|
$custom_params[] = "embed_start_time=" . $startTime;
|
||||||
|
}
|
||||||
|
if ($show_media_page === 'true') {
|
||||||
|
$custom_params[] = "show_media_page=true";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up page
|
||||||
|
$page_params = [
|
||||||
|
'token' => $mediatoken,
|
||||||
|
'courseid' => $courseid,
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($showTitle !== '') {
|
||||||
|
$page_params['showTitle'] = $showTitle;
|
||||||
|
}
|
||||||
|
if ($showRelated !== '') {
|
||||||
|
$page_params['showRelated'] = $showRelated;
|
||||||
|
}
|
||||||
|
if ($showUserAvatar !== '') {
|
||||||
|
$page_params['showUserAvatar'] = $showUserAvatar;
|
||||||
|
}
|
||||||
|
if ($linkTitle !== '') {
|
||||||
|
$page_params['linkTitle'] = $linkTitle;
|
||||||
|
}
|
||||||
|
if ($startTime !== '') {
|
||||||
|
$page_params['t'] = $startTime;
|
||||||
|
}
|
||||||
|
if ($show_media_page === 'true') {
|
||||||
|
$page_params['show_media_page'] = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
$PAGE->set_url(new moodle_url('/filter/mediacms/launch.php', $page_params));
|
||||||
|
$PAGE->set_context($context);
|
||||||
|
$PAGE->set_pagelayout('embedded');
|
||||||
|
$PAGE->set_title('MediaCMS');
|
||||||
|
|
||||||
|
$typeconfig = lti_get_type_type_config($type->id);
|
||||||
|
|
||||||
|
// Build the OIDC login request params directly so we can capture the launchid.
|
||||||
|
// This avoids a shared SESSION key, which would cause a race condition when
|
||||||
|
// multiple videos are embedded on the same page and load simultaneously.
|
||||||
|
$oidc_params = lti_build_login_request($course->id, 0, null, $typeconfig, null, 0, 'MediaCMS Video');
|
||||||
|
|
||||||
|
// Key the custom params by launchid — lti_auth.php retrieves them the same way.
|
||||||
|
$hint = json_decode($oidc_params['lti_message_hint']);
|
||||||
|
$SESSION->{'mediacms_cp_' . $hint->launchid} = implode("\n", $custom_params);
|
||||||
|
|
||||||
|
// Build the fallback hidden fields (MediaCMS encodes them in state as a secondary mechanism).
|
||||||
|
$hidden_fields = '<input type="hidden" name="media_token" value="' . htmlspecialchars($mediatoken, ENT_QUOTES) . '" />';
|
||||||
|
|
||||||
|
if ($showTitle !== '') {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_show_title" value="' . htmlspecialchars($showTitle, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
if ($showRelated !== '') {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_show_related" value="' . htmlspecialchars($showRelated, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
if ($showUserAvatar !== '') {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_show_user_avatar" value="' . htmlspecialchars($showUserAvatar, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
if ($linkTitle !== '') {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_link_title" value="' . htmlspecialchars($linkTitle, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
if ($startTime !== '') {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_start_time" value="' . htmlspecialchars($startTime, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
if ($show_media_page === 'true') {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="show_media_page" value="true" />';
|
||||||
|
}
|
||||||
|
if ($width) {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_width" value="' . htmlspecialchars($width, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
if ($height) {
|
||||||
|
$hidden_fields .= '<input type="hidden" name="embed_height" value="' . htmlspecialchars($height, ENT_QUOTES) . '" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce the OIDC login form (mirrors lti_initiate_login output).
|
||||||
|
$content = '<form action="' . htmlspecialchars($typeconfig->lti_initiatelogin, ENT_COMPAT)
|
||||||
|
. '" name="ltiInitiateLoginForm" id="ltiInitiateLoginForm"'
|
||||||
|
. ' method="post" encType="application/x-www-form-urlencoded">' . "\n";
|
||||||
|
foreach ($oidc_params as $key => $value) {
|
||||||
|
$key = htmlspecialchars($key, ENT_COMPAT);
|
||||||
|
$value = htmlspecialchars($value, ENT_COMPAT);
|
||||||
|
$content .= " <input type=\"hidden\" name=\"{$key}\" value=\"{$value}\"/>\n";
|
||||||
|
}
|
||||||
|
$content .= $hidden_fields . "\n";
|
||||||
|
$content .= "</form>\n";
|
||||||
|
$content .= "<script type=\"text/javascript\">\n"
|
||||||
|
. "//<![CDATA[\n"
|
||||||
|
. "document.ltiInitiateLoginForm.submit();\n"
|
||||||
|
. "//]]>\n"
|
||||||
|
. "</script>\n";
|
||||||
|
|
||||||
|
echo $OUTPUT->header();
|
||||||
|
echo $content;
|
||||||
|
echo $OUTPUT->footer();
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user