mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-09 14:37:22 -04:00
plugin
This commit is contained in:
@@ -2,186 +2,16 @@
|
||||
|
||||
A TinyMCE editor plugin for Moodle that provides media embedding capabilities with MediaCMS/LTI integration.
|
||||
|
||||
## Plugin Information
|
||||
## Build Information
|
||||
|
||||
- **Component:** `tiny_mediacms`
|
||||
- **Version:** See `version.php`
|
||||
- **Requires:** Moodle 4.5+ (2024100100)
|
||||
|
||||
## Directory Structure
|
||||
1. Get and extract Moodle 5.1
|
||||
2. cp -r lms-plugins/mediacms-moodle/tiny/mediacms/ moodle/public/lib/editor/tiny/plugins/
|
||||
3. nvm use 22 && cd moodle/public && npm install
|
||||
4. npx grunt amd --root=lib/editor/tiny/plugins/mediacms
|
||||
# i've noticed that this fails, so this should work: npx grunt amd
|
||||
|
||||
```
|
||||
mediacms/
|
||||
├── amd/
|
||||
│ ├── src/ # JavaScript source files (ES6 modules)
|
||||
│ │ ├── plugin.js # Main plugin entry point
|
||||
│ │ ├── commands.js # Editor commands
|
||||
│ │ ├── configuration.js # Plugin configuration
|
||||
│ │ ├── iframeembed.js # Iframe embedding logic
|
||||
│ │ ├── iframemodal.js # Iframe modal UI
|
||||
│ │ ├── autoconvert.js # URL auto-conversion
|
||||
│ │ ├── embed.js # Media embedding
|
||||
│ │ ├── embedmodal.js # Embed modal UI
|
||||
│ │ ├── image.js # Image handling
|
||||
│ │ ├── imagemodal.js # Image modal UI
|
||||
│ │ ├── imageinsert.js # Image insertion
|
||||
│ │ ├── imagedetails.js # Image details panel
|
||||
│ │ ├── imagehelpers.js # Image utility functions
|
||||
│ │ ├── manager.js # File manager
|
||||
│ │ ├── options.js # Plugin options
|
||||
│ │ ├── selectors.js # DOM selectors
|
||||
│ │ ├── common.js # Shared utilities
|
||||
│ │ └── usedfiles.js # Track used files
|
||||
│ └── build/ # Compiled/minified files (generated)
|
||||
├── classes/ # PHP classes
|
||||
├── lang/ # Language strings
|
||||
│ └── en/
|
||||
│ └── tiny_mediacms.php
|
||||
├── templates/ # Mustache templates
|
||||
├── styles.css # Plugin styles
|
||||
├── settings.php # Admin settings
|
||||
└── version.php # Plugin version
|
||||
```
|
||||
5. To test the output:
|
||||
cp lib/editor/tiny/plugins/mediacms/* ../../lms-plugins/mediacms-moodle/tiny/mediacms/ -r
|
||||
6. Then copy to Moodle server and purge caches
|
||||
|
||||
## Building JavaScript (AMD Modules)
|
||||
|
||||
When you modify JavaScript files in `amd/src/`, you must rebuild the minified files in `amd/build/`.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Make sure you have Node.js installed and have run `npm install` in the Moodle root directory:
|
||||
|
||||
```bash
|
||||
cd /path/to/moodle/public
|
||||
npm install
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
|
||||
#### Build all AMD modules (entire Moodle):
|
||||
|
||||
```bash
|
||||
cd /path/to/moodle/public
|
||||
npx grunt amd
|
||||
```
|
||||
|
||||
#### Build only this plugin's AMD modules:
|
||||
|
||||
```bash
|
||||
cd /path/to/moodle/public
|
||||
npx grunt amd --root=lib/editor/tiny/plugins/mediacms
|
||||
```
|
||||
|
||||
#### Watch for changes (auto-rebuild):
|
||||
|
||||
```bash
|
||||
cd /path/to/moodle/public
|
||||
npx grunt watch --root=lib/editor/tiny/plugins/mediacms
|
||||
```
|
||||
|
||||
#### Force build (ignore warnings):
|
||||
|
||||
```bash
|
||||
cd /path/to/moodle/public
|
||||
npx grunt amd --force --root=lib/editor/tiny/plugins/mediacms
|
||||
```
|
||||
|
||||
### Build Output
|
||||
|
||||
After running grunt, the following files are generated in `amd/build/`:
|
||||
|
||||
- `*.min.js` - Minified JavaScript files
|
||||
- `*.min.js.map` - Source maps for debugging
|
||||
|
||||
## Development Mode (Skip Building)
|
||||
|
||||
For faster development, you can skip building by enabling developer mode in Moodle's `config.php`:
|
||||
|
||||
```php
|
||||
// Add these lines to config.php
|
||||
$CFG->debugdeveloper = true;
|
||||
$CFG->cachejs = false;
|
||||
```
|
||||
|
||||
This tells Moodle to load the unminified source files directly from `amd/src/` instead of `amd/build/`.
|
||||
|
||||
**Note:** Always build before committing or deploying to production!
|
||||
|
||||
## Purging Caches
|
||||
|
||||
After making changes, you may need to purge Moodle caches:
|
||||
|
||||
### Via CLI (Docker):
|
||||
|
||||
```bash
|
||||
docker compose exec moodle php /var/www/html/public/admin/cli/purge_caches.php
|
||||
```
|
||||
|
||||
### Via CLI (Local):
|
||||
|
||||
```bash
|
||||
php admin/cli/purge_caches.php
|
||||
```
|
||||
|
||||
### Via Web:
|
||||
|
||||
Visit: `http://your-moodle-site/admin/purgecaches.php`
|
||||
|
||||
## What Needs Cache Purging?
|
||||
|
||||
| File Type | Cache Purge Needed? |
|
||||
|-----------|---------------------|
|
||||
| `amd/src/*.js` | No (if `$CFG->cachejs = false`) |
|
||||
| `amd/build/*.min.js` | Yes |
|
||||
| `lang/en/*.php` | Yes |
|
||||
| `templates/*.mustache` | Yes |
|
||||
| `styles.css` | Yes |
|
||||
| `classes/*.php` | Usually no |
|
||||
| `settings.php` | Yes |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Changes not appearing?
|
||||
|
||||
1. **JavaScript changes:**
|
||||
- Rebuild AMD modules: `npx grunt amd --root=lib/editor/tiny/plugins/mediacms`
|
||||
- Hard refresh browser: `Cmd+Shift+R` (Mac) / `Ctrl+Shift+R` (Windows/Linux)
|
||||
- Check browser console for errors
|
||||
|
||||
2. **Language strings:**
|
||||
- Purge Moodle caches
|
||||
|
||||
3. **Templates:**
|
||||
- Purge Moodle caches
|
||||
|
||||
4. **Styles:**
|
||||
- Purge Moodle caches
|
||||
- Hard refresh browser
|
||||
|
||||
### Grunt errors?
|
||||
|
||||
```bash
|
||||
# Make sure dependencies are installed
|
||||
cd /path/to/moodle/public
|
||||
npm install
|
||||
|
||||
# Try with force flag
|
||||
npx grunt amd --force --root=lib/editor/tiny/plugins/mediacms
|
||||
```
|
||||
|
||||
### ESLint errors?
|
||||
|
||||
Fix linting issues or use:
|
||||
|
||||
```bash
|
||||
npx grunt amd --force --root=lib/editor/tiny/plugins/mediacms
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [AUTOCONVERT.md](./AUTOCONVERT.md) - URL auto-conversion feature documentation
|
||||
- [LTI_INTEGRATION.md](./LTI_INTEGRATION.md) - LTI integration documentation
|
||||
|
||||
## License
|
||||
|
||||
GNU GPL v3 or later - http://www.gnu.org/copyleft/gpl.html
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -694,6 +694,9 @@ export default class IframeEmbed {
|
||||
|
||||
// Iframe library event listeners
|
||||
this.registerIframeLibraryEventListeners(root);
|
||||
|
||||
// Since My Media tab is now default/active, load it immediately on modal open
|
||||
setTimeout(() => this.handleIframeLibraryTabClick(root), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -796,24 +799,6 @@ export default class IframeEmbed {
|
||||
* @param {HTMLElement} root - Modal root element
|
||||
*/
|
||||
handleIframeLibraryTabClick(root) {
|
||||
const form = root.querySelector(Selectors.IFRAME.elements.form);
|
||||
const pane = form.querySelector(
|
||||
Selectors.IFRAME.elements.paneIframeLibrary,
|
||||
);
|
||||
const iframeEl = pane
|
||||
? pane.querySelector(Selectors.IFRAME.elements.iframeLibraryFrame)
|
||||
: null;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'handleIframeLibraryTabClick called, iframeLibraryLoaded:',
|
||||
this.iframeLibraryLoaded,
|
||||
'iframe src:',
|
||||
iframeEl ? iframeEl.src : 'no iframe',
|
||||
'pane:',
|
||||
pane,
|
||||
);
|
||||
|
||||
// Always refetch content when tab is clicked (no caching)
|
||||
// Reset the loaded state to ensure fresh content is fetched
|
||||
this.iframeLibraryLoaded = false;
|
||||
@@ -827,13 +812,9 @@ export default class IframeEmbed {
|
||||
*/
|
||||
loadIframeLibrary(root) {
|
||||
const ltiConfig = getLti(this.editor);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('loadIframeLibrary called, LTI config:', ltiConfig);
|
||||
|
||||
// Check if LTI is configured with a content item URL
|
||||
if (ltiConfig?.contentItemUrl) {
|
||||
this.loadIframeLibraryViaLti(root, ltiConfig);
|
||||
this.loadIframeLibraryViaLti(root);
|
||||
} else {
|
||||
// Fallback to static URL if LTI not configured
|
||||
this.loadIframeLibraryStatic(root);
|
||||
@@ -847,21 +828,14 @@ export default class IframeEmbed {
|
||||
* tool's content selection interface (e.g., /lti/select-media/).
|
||||
*
|
||||
* @param {HTMLElement} root - Modal root element
|
||||
* @param {Object} ltiConfig - LTI configuration
|
||||
*/
|
||||
loadIframeLibraryViaLti(root, ltiConfig) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('loadIframeLibraryViaLti called, config:', ltiConfig);
|
||||
|
||||
loadIframeLibraryViaLti(root) {
|
||||
const form = root.querySelector(Selectors.IFRAME.elements.form);
|
||||
const pane = form.querySelector(
|
||||
Selectors.IFRAME.elements.paneIframeLibrary,
|
||||
);
|
||||
|
||||
if (!pane) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('paneIframeLibrary not found!');
|
||||
return;
|
||||
if (!pane) { return;
|
||||
}
|
||||
|
||||
const placeholderEl = pane.querySelector(
|
||||
@@ -874,10 +848,7 @@ export default class IframeEmbed {
|
||||
Selectors.IFRAME.elements.iframeLibraryFrame,
|
||||
);
|
||||
|
||||
if (!iframeEl) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('iframeEl not found!');
|
||||
return;
|
||||
if (!iframeEl) { return;
|
||||
}
|
||||
|
||||
// Hide placeholder, show loading state
|
||||
@@ -890,10 +861,7 @@ export default class IframeEmbed {
|
||||
iframeEl.classList.add('d-none');
|
||||
|
||||
// Set up load listener - note: this may fire multiple times during LTI redirects
|
||||
const loadHandler = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('LTI iframe loaded');
|
||||
this.handleIframeLibraryLoad(root);
|
||||
const loadHandler = () => { this.handleIframeLibraryLoad(root);
|
||||
};
|
||||
iframeEl.addEventListener('load', loadHandler);
|
||||
|
||||
@@ -902,12 +870,8 @@ export default class IframeEmbed {
|
||||
// 1. contentitem.php initiates OIDC login
|
||||
// 2. LTI provider authenticates
|
||||
// 3. Moodle sends LtiDeepLinkingRequest
|
||||
// 4. Tool provider shows content selection interface (e.g., /lti/select-media/)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Setting iframe src to LTI content item URL:',
|
||||
ltiConfig.contentItemUrl,
|
||||
);
|
||||
// 4. Tool provider shows content selection interface
|
||||
const ltiConfig = getLti(this.editor);
|
||||
iframeEl.src = ltiConfig.contentItemUrl;
|
||||
}
|
||||
|
||||
@@ -917,21 +881,12 @@ export default class IframeEmbed {
|
||||
* @param {HTMLElement} root - Modal root element
|
||||
*/
|
||||
loadIframeLibraryStatic(root) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'loadIframeLibraryStatic called, URL:',
|
||||
this.iframeLibraryUrl,
|
||||
);
|
||||
|
||||
const form = root.querySelector(Selectors.IFRAME.elements.form);
|
||||
const pane = form.querySelector(
|
||||
Selectors.IFRAME.elements.paneIframeLibrary,
|
||||
);
|
||||
|
||||
if (!pane) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('paneIframeLibrary not found!');
|
||||
return;
|
||||
if (!pane) { return;
|
||||
}
|
||||
|
||||
const placeholderEl = pane.querySelector(
|
||||
@@ -943,14 +898,7 @@ export default class IframeEmbed {
|
||||
const iframeEl = pane.querySelector(
|
||||
Selectors.IFRAME.elements.iframeLibraryFrame,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Elements found:', { placeholderEl, loadingEl, iframeEl });
|
||||
|
||||
if (!iframeEl) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('iframeEl not found!');
|
||||
return;
|
||||
if (!iframeEl) { return;
|
||||
}
|
||||
|
||||
// Hide placeholder, show loading state
|
||||
@@ -963,10 +911,7 @@ export default class IframeEmbed {
|
||||
iframeEl.classList.add('d-none');
|
||||
|
||||
// Set up load listener before setting src
|
||||
const loadHandler = () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('iframe loaded, src:', iframeEl.src);
|
||||
// Only handle if the src matches our target URL
|
||||
const loadHandler = () => { // Only handle if the src matches our target URL
|
||||
if (iframeEl.src === this.iframeLibraryUrl) {
|
||||
this.handleIframeLibraryLoad(root);
|
||||
// Remove the listener after successful load
|
||||
@@ -976,10 +921,7 @@ export default class IframeEmbed {
|
||||
iframeEl.addEventListener('load', loadHandler);
|
||||
|
||||
// Set the iframe source
|
||||
iframeEl.src = this.iframeLibraryUrl;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('iframe src set to:', iframeEl.src);
|
||||
}
|
||||
iframeEl.src = this.iframeLibraryUrl; }
|
||||
|
||||
/**
|
||||
* Handle iframe library load event.
|
||||
@@ -987,9 +929,6 @@ export default class IframeEmbed {
|
||||
* @param {HTMLElement} root - Modal root element
|
||||
*/
|
||||
handleIframeLibraryLoad(root) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('handleIframeLibraryLoad called');
|
||||
|
||||
const form = root.querySelector(Selectors.IFRAME.elements.form);
|
||||
const pane = form.querySelector(
|
||||
Selectors.IFRAME.elements.paneIframeLibrary,
|
||||
@@ -1031,29 +970,14 @@ export default class IframeEmbed {
|
||||
* @param {MessageEvent} event - The message event
|
||||
*/
|
||||
handleIframeLibraryMessage(root, event) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'handleIframeLibraryMessage received:',
|
||||
event.data,
|
||||
'from origin:',
|
||||
event.origin,
|
||||
);
|
||||
|
||||
const data = event.data;
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle custom videoSelected message format (from static iframe or custom MediaCMS implementation)
|
||||
// Handle custom videoSelected message format
|
||||
if (data.type === 'videoSelected' && data.embedUrl) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Video selected (videoSelected):',
|
||||
data.embedUrl,
|
||||
'videoId:',
|
||||
data.videoId,
|
||||
);
|
||||
this.selectIframeLibraryVideo(root, data.embedUrl, data.videoId);
|
||||
return;
|
||||
}
|
||||
@@ -1062,25 +986,14 @@ export default class IframeEmbed {
|
||||
if (
|
||||
data.type === 'ltiDeepLinkingResponse' ||
|
||||
data.messageType === 'LtiDeepLinkingResponse'
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('LTI Deep Linking response received:', data);
|
||||
const contentItems = data.content_items || data.contentItems || [];
|
||||
) { const contentItems = data.content_items || data.contentItems || [];
|
||||
if (contentItems.length > 0) {
|
||||
const item = contentItems[0];
|
||||
// Extract embed URL from the content item
|
||||
const embedUrl =
|
||||
item.url || item.embed_url || item.embedUrl || '';
|
||||
const videoId = item.id || item.mediaId || '';
|
||||
if (embedUrl) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Video selected (LTI):',
|
||||
embedUrl,
|
||||
'videoId:',
|
||||
videoId,
|
||||
);
|
||||
this.selectIframeLibraryVideo(root, embedUrl, videoId);
|
||||
if (embedUrl) { this.selectIframeLibraryVideo(root, embedUrl, videoId);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -1090,15 +1003,7 @@ export default class IframeEmbed {
|
||||
if (data.action === 'selectMedia' || data.action === 'mediaSelected') {
|
||||
const embedUrl = data.embedUrl || data.url || '';
|
||||
const videoId = data.mediaId || data.videoId || data.id || '';
|
||||
if (embedUrl) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'Video selected (mediaSelected):',
|
||||
embedUrl,
|
||||
'videoId:',
|
||||
videoId,
|
||||
);
|
||||
this.selectIframeLibraryVideo(root, embedUrl, videoId);
|
||||
if (embedUrl) { this.selectIframeLibraryVideo(root, embedUrl, videoId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1118,7 +1023,13 @@ export default class IframeEmbed {
|
||||
const urlInput = form.querySelector(Selectors.IFRAME.elements.url);
|
||||
urlInput.value = embedUrl;
|
||||
|
||||
// Switch to the URL tab using our method
|
||||
// Show the Configure tab (it starts hidden)
|
||||
const configureTabItem = root.querySelector('.tiny_iframecms_tab_url_item');
|
||||
if (configureTabItem) {
|
||||
configureTabItem.style.display = '';
|
||||
}
|
||||
|
||||
// Switch to the Configure tab to show embed options
|
||||
this.switchToUrlTab(root);
|
||||
|
||||
// Update the preview
|
||||
|
||||
@@ -132,15 +132,15 @@ $string['aspectratio_1_1'] = '1:1';
|
||||
$string['aspectratio_custom'] = 'Custom';
|
||||
$string['dimensions'] = 'Dimensions';
|
||||
$string['preview'] = 'Preview';
|
||||
$string['insertiframe'] = 'Insert video';
|
||||
$string['insertiframe'] = 'Insert';
|
||||
$string['updateiframe'] = 'Update video';
|
||||
$string['removeiframe'] = 'Remove video';
|
||||
$string['removeiframeconfirm'] = 'Are you sure you want to remove this video from the editor?';
|
||||
|
||||
// Iframe modal tabs.
|
||||
$string['tabembedurl'] = 'Embed URL';
|
||||
$string['tabembedurl'] = 'Configure';
|
||||
$string['tabvideolibrary'] = 'Video Library';
|
||||
$string['tabvideolibraryiframe'] = 'Media Library';
|
||||
$string['tabvideolibraryiframe'] = 'My Media';
|
||||
|
||||
// Video library strings.
|
||||
$string['librarysearchplaceholder'] = 'Search videos...';
|
||||
|
||||
@@ -31,66 +31,28 @@
|
||||
<form class="tiny_iframecms_form" id="{{elementid}}_tiny_iframecms_form">
|
||||
<!-- Tab Navigation -->
|
||||
<ul class="nav nav-tabs mb-3 tiny_iframecms_tabs" role="tablist">
|
||||
<!-- My Media tab (first, active by default) -->
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active tiny_iframecms_tab_url_btn" id="{{elementid}}_tab_url"
|
||||
data-bs-toggle="tab" data-bs-target="#{{elementid}}_pane_url"
|
||||
type="button" role="tab" aria-controls="{{elementid}}_pane_url" aria-selected="true">
|
||||
{{#str}} tabembedurl, tiny_mediacms {{/str}}
|
||||
<button class="nav-link active tiny_iframecms_tab_iframe_library_btn" id="{{elementid}}_tab_iframe_library"
|
||||
data-bs-toggle="tab" data-bs-target="#{{elementid}}_pane_iframe_library"
|
||||
type="button" role="tab" aria-controls="{{elementid}}_pane_iframe_library" aria-selected="true">
|
||||
{{#str}} tabvideolibraryiframe, tiny_mediacms {{/str}}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link tiny_iframecms_tab_iframe_library_btn" id="{{elementid}}_tab_iframe_library"
|
||||
data-bs-toggle="tab" data-bs-target="#{{elementid}}_pane_iframe_library"
|
||||
type="button" role="tab" aria-controls="{{elementid}}_pane_iframe_library" aria-selected="false">
|
||||
{{#str}} tabvideolibraryiframe, tiny_mediacms {{/str}}
|
||||
<!-- Configure tab (second, hidden initially) -->
|
||||
<li class="nav-item tiny_iframecms_tab_url_item" role="presentation" style="display: none;">
|
||||
<button class="nav-link tiny_iframecms_tab_url_btn" id="{{elementid}}_tab_url"
|
||||
data-bs-toggle="tab" data-bs-target="#{{elementid}}_pane_url"
|
||||
type="button" role="tab" aria-controls="{{elementid}}_pane_url" aria-selected="false">
|
||||
{{#str}} tabembedurl, tiny_mediacms {{/str}}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="tab-content">
|
||||
<!-- Tab 1: Embed URL (existing content) -->
|
||||
<div class="tab-pane fade show active tiny_iframecms_pane_url" id="{{elementid}}_pane_url" role="tabpanel" aria-labelledby="{{elementid}}_tab_url">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left column: URL and Options -->
|
||||
<div class="col-md-6">
|
||||
<!-- URL Input -->
|
||||
<div class="mb-3">
|
||||
<label for="{{elementid}}_iframe_url" class="form-label font-weight-bold">
|
||||
{{#str}} iframeurl, tiny_mediacms {{/str}}
|
||||
</label>
|
||||
<textarea
|
||||
class="form-control tiny_iframecms_url"
|
||||
id="{{elementid}}_iframe_url"
|
||||
rows="3"
|
||||
placeholder="{{#str}} iframeurlplaceholder, tiny_mediacms {{/str}}"
|
||||
>{{url}}</textarea>
|
||||
<div class="tiny_iframecms_url_warning text-danger small mt-1 d-none">
|
||||
{{#str}} iframeurlinvalid, tiny_mediacms {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{> tiny_mediacms/iframe_embed_options }}
|
||||
</div>
|
||||
|
||||
<!-- Right column: Preview -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label font-weight-bold">
|
||||
{{#str}} preview, tiny_mediacms {{/str}}
|
||||
</label>
|
||||
<div class="tiny_iframecms_preview_container border rounded p-2 bg-light" style="min-height: 300px;">
|
||||
<div class="tiny_iframecms_preview d-flex align-items-center justify-content-center text-muted" style="min-height: 280px;">
|
||||
<span>{{#str}} iframeurlplaceholder, tiny_mediacms {{/str}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Media Library -->
|
||||
<div class="tab-pane fade tiny_iframecms_pane_iframe_library" id="{{elementid}}_pane_iframe_library" role="tabpanel" aria-labelledby="{{elementid}}_tab_iframe_library">
|
||||
<!-- Tab 1: My Media (now first and active) -->
|
||||
<div class="tab-pane fade show active tiny_iframecms_pane_iframe_library" id="{{elementid}}_pane_iframe_library" role="tabpanel" aria-labelledby="{{elementid}}_tab_iframe_library">
|
||||
<div class="tiny_iframecms_iframe_library_container" style="min-height: 500px;">
|
||||
<div class="tiny_iframecms_iframe_library_placeholder text-center py-5">
|
||||
<p class="text-muted">{{#str}} libraryloading, tiny_mediacms {{/str}}</p>
|
||||
@@ -110,6 +72,38 @@
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Configure (now second) -->
|
||||
<div class="tab-pane fade tiny_iframecms_pane_url" id="{{elementid}}_pane_url" role="tabpanel" aria-labelledby="{{elementid}}_tab_url">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row">
|
||||
<!-- Left column: Options only (URL field removed) -->
|
||||
<div class="col-md-6">
|
||||
<!-- Hidden URL input (still needed for internal logic) -->
|
||||
<textarea
|
||||
class="form-control tiny_iframecms_url d-none"
|
||||
id="{{elementid}}_iframe_url"
|
||||
rows="3"
|
||||
>{{url}}</textarea>
|
||||
<div class="tiny_iframecms_url_warning text-danger small mt-1 d-none"></div>
|
||||
|
||||
{{> tiny_mediacms/iframe_embed_options }}
|
||||
</div>
|
||||
|
||||
<!-- Right column: Preview -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label font-weight-bold">
|
||||
{{#str}} preview, tiny_mediacms {{/str}}
|
||||
</label>
|
||||
<div class="tiny_iframecms_preview_container border rounded p-2 bg-light" style="min-height: 300px;">
|
||||
<div class="tiny_iframecms_preview d-flex align-items-center justify-content-center text-muted" style="min-height: 280px;">
|
||||
<span>{{#str}} iframeurlplaceholder, tiny_mediacms {{/str}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{/body}}
|
||||
|
||||
Reference in New Issue
Block a user