MDL-78096 tiny_media: Add a new insert image
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
||||
define("tiny_media/imagehelpers",["exports","core/templates"],(function(_exports,_templates){var obj;
|
||||
/**
|
||||
* Tiny media plugin image helpers.
|
||||
*
|
||||
* @module tiny_media/imagehelpers
|
||||
* @copyright 2024 Meirza <meirza.arson@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.footerImageInsert=_exports.bodyImageInsert=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};_exports.bodyImageInsert=async(templateContext,root)=>_templates.default.renderForPromise("tiny_media/insert_image_modal_insert",{...templateContext}).then((_ref=>{let{html:html,js:js}=_ref;_templates.default.replaceNodeContents(root.querySelector(".tiny_image_body_template"),html,js)})).catch((error=>{window.console.log(error)}));_exports.footerImageInsert=async(templateContext,root)=>_templates.default.renderForPromise("tiny_media/insert_image_modal_insert_footer",{...templateContext}).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.replaceNodeContents(root.querySelector(".tiny_image_footer_template"),html,js)})).catch((error=>{window.console.log(error)}))}));
|
||||
|
||||
//# sourceMappingURL=imagehelpers.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"imagehelpers.min.js","sources":["../src/imagehelpers.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny media plugin image helpers.\n *\n * @module tiny_media/imagehelpers\n * @copyright 2024 Meirza <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\n\n/**\n * Renders and inserts the body template for inserting an image into the modal.\n *\n * @param {object} templateContext - The context for rendering the template.\n * @param {HTMLElement} root - The root element where the template will be inserted.\n * @returns {Promise<void>}\n */\nexport const bodyImageInsert = async(templateContext, root) => {\n return Templates.renderForPromise('tiny_media/insert_image_modal_insert', {...templateContext})\n .then(({html, js}) => {\n Templates.replaceNodeContents(root.querySelector('.tiny_image_body_template'), html, js);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n};\n\n/**\n * Renders and inserts the footer template for inserting an image into the modal.\n *\n * @param {object} templateContext - The context for rendering the template.\n * @param {HTMLElement} root - The root element where the template will be inserted.\n * @returns {Promise<void>}\n */\nexport const footerImageInsert = async(templateContext, root) => {\n return Templates.renderForPromise('tiny_media/insert_image_modal_insert_footer', {...templateContext})\n .then(({html, js}) => {\n Templates.replaceNodeContents(root.querySelector('.tiny_image_footer_template'), html, js);\n return;\n })\n .catch(error => {\n window.console.log(error);\n });\n};\n"],"names":["async","templateContext","root","Templates","renderForPromise","then","_ref","html","js","replaceNodeContents","querySelector","catch","error","window","console","log","_ref2"],"mappings":";;;;;;;+MAgC+BA,MAAMC,gBAAiBC,OAC3CC,mBAAUC,iBAAiB,uCAAwC,IAAIH,kBAC7EI,MAAKC,WAACC,KAACA,KAADC,GAAOA,4BACAC,oBAAoBP,KAAKQ,cAAc,6BAA8BH,KAAMC,OAGxFG,OAAMC,QACHC,OAAOC,QAAQC,IAAIH,qCAWMZ,MAAMC,gBAAiBC,OAC7CC,mBAAUC,iBAAiB,8CAA+C,IAAIH,kBACpFI,MAAKW,YAACT,KAACA,KAADC,GAAOA,6BACAC,oBAAoBP,KAAKQ,cAAc,+BAAgCH,KAAMC,OAG1FG,OAAMC,QACHC,OAAOC,QAAQC,IAAIH"}
|
||||
@@ -0,0 +1,3 @@
|
||||
define("tiny_media/imageinsert",["exports","core/prefetch","core/str","./common"],(function(_exports,_prefetch,_str,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ImageInsert=void 0,(0,_prefetch.prefetchStrings)("tiny_media",["insertimage","enterurl","enterurlor","imageurlrequired","uploading","loading"]);_exports.ImageInsert=class{constructor(root,editor,currentModal,canShowFilePicker,canShowDropZone){var obj,key,value;value=async function(){const langStringKeys=["insertimage","enterurl","enterurlor","imageurlrequired","uploading","loading"],langStringvalues=await(0,_str.getStrings)([...langStringKeys].map((key=>({key:key,component:_common.component}))));this.langStrings=Object.fromEntries(langStringKeys.map(((key,index)=>[key,langStringvalues[index]]))),this.currentModal.setTitle(this.langStrings.insertimage)},(key="init")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.root=root,this.editor=editor,this.currentModal=currentModal,this.canShowFilePicker=canShowFilePicker,this.canShowDropZone=canShowDropZone}}}));
|
||||
|
||||
//# sourceMappingURL=imageinsert.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"imageinsert.min.js","sources":["../src/imageinsert.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny media plugin image insertion class for Moodle.\n *\n * @module tiny_media/imageinsert\n * @copyright 2024 Meirza <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {prefetchStrings} from 'core/prefetch';\nimport {getStrings} from 'core/str';\nimport {component} from \"./common\";\n\nprefetchStrings('tiny_media', [\n 'insertimage',\n 'enterurl',\n 'enterurlor',\n 'imageurlrequired',\n 'uploading',\n 'loading',\n]);\n\nexport class ImageInsert {\n\n constructor(\n root,\n editor,\n currentModal,\n canShowFilePicker,\n canShowDropZone,\n ) {\n this.root = root;\n this.editor = editor;\n this.currentModal = currentModal;\n this.canShowFilePicker = canShowFilePicker;\n this.canShowDropZone = canShowDropZone;\n }\n\n init = async function() {\n // Get the localization lang strings and turn them into object.\n const langStringKeys = [\n 'insertimage',\n 'enterurl',\n 'enterurlor',\n 'imageurlrequired',\n 'uploading',\n 'loading',\n ];\n const langStringvalues = await getStrings([...langStringKeys].map((key) => ({key, component})));\n\n // Convert array to object.\n this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringvalues[index]]));\n this.currentModal.setTitle(this.langStrings.insertimage);\n };\n}"],"names":["constructor","root","editor","currentModal","canShowFilePicker","canShowDropZone","async","langStringKeys","langStringvalues","map","key","component","langStrings","Object","fromEntries","index","setTitle","this","insertimage"],"mappings":"+OA2BgB,aAAc,CAC1B,cACA,WACA,aACA,mBACA,YACA,uCAKAA,YACIC,KACAC,OACAC,aACAC,kBACAC,yCASGC,uBAEGC,eAAiB,CACnB,cACA,WACA,aACA,mBACA,YACA,WAEEC,uBAAyB,mBAAW,IAAID,gBAAgBE,KAAKC,OAAUA,IAAAA,IAAKC,UAAAA,4BAG7EC,YAAcC,OAAOC,YAAYP,eAAeE,KAAI,CAACC,IAAKK,QAAU,CAACL,IAAKF,iBAAiBO,gBAC3FZ,aAAaa,SAASC,KAAKL,YAAYM,kJArBvCjB,KAAOA,UACPC,OAASA,YACTC,aAAeA,kBACfC,kBAAoBA,uBACpBC,gBAAkBA"}
|
||||
@@ -21,521 +21,69 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Templates from 'core/templates';
|
||||
import {getString} from 'core/str';
|
||||
import Pending from 'core/pending';
|
||||
import {displayFilepicker} from 'editor_tiny/utils';
|
||||
import Selectors from './selectors';
|
||||
import ImageModal from './imagemodal';
|
||||
import {getImagePermissions} from './options';
|
||||
import {getFilePicker} from 'editor_tiny/options';
|
||||
import {ImageInsert} from 'tiny_media/imageinsert';
|
||||
import {
|
||||
bodyImageInsert,
|
||||
footerImageInsert,
|
||||
} from 'tiny_media/imagehelpers';
|
||||
|
||||
export default class MediaImage {
|
||||
DEFAULTS = {
|
||||
WIDTH: 160,
|
||||
HEIGHT: 160,
|
||||
};
|
||||
|
||||
form = null;
|
||||
rawImageDimensions = null;
|
||||
canShowFilePicker = false;
|
||||
editor = null;
|
||||
currentModal = null;
|
||||
selectedImage = null;
|
||||
/**
|
||||
* @type {HTMLElement|null} The root element.
|
||||
*/
|
||||
root = null;
|
||||
|
||||
constructor(editor) {
|
||||
const permissions = getImagePermissions(editor);
|
||||
|
||||
const options = getFilePicker(editor, 'image');
|
||||
// Indicates whether the file picker can be shown.
|
||||
this.canShowFilePicker = permissions.filepicker && (typeof getFilePicker(editor, 'image') !== 'undefined');
|
||||
this.canShowFilePicker = permissions.filepicker
|
||||
&& (typeof options !== 'undefined')
|
||||
&& Object.keys(options.repositories).length > 0;
|
||||
// Indicates whether the drop zone area can be shown.
|
||||
this.canShowDropZone = Object.values(options.repositories).some(repository => repository.type === 'upload');
|
||||
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
async displayDialogue() {
|
||||
// Reset the image dimensions.
|
||||
this.rawImageDimensions = null;
|
||||
|
||||
const currentImageData = await this.getCurrentImageData();
|
||||
this.currentModal = await ImageModal.create({
|
||||
title: getString('imageproperties', 'tiny_media'),
|
||||
templateContext: await this.getTemplateContext(currentImageData),
|
||||
});
|
||||
|
||||
if (currentImageData && currentImageData.src) {
|
||||
this.loadPreviewImage(currentImageData.src);
|
||||
}
|
||||
|
||||
await this.registerEventListeners();
|
||||
this.currentModal = await ImageModal.create();
|
||||
this.root = this.currentModal.getRoot()[0];
|
||||
this.loadInsertImage();
|
||||
}
|
||||
|
||||
async getTemplateContext(data) {
|
||||
return {
|
||||
/**
|
||||
* Displays an insert image view asynchronously.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
loadInsertImage = async function() {
|
||||
const templateContext = {
|
||||
elementid: this.editor.id,
|
||||
showfilepicker: this.canShowFilePicker,
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
async getCurrentImageData() {
|
||||
const selectedImageProperties = this.getSelectedImageProperties();
|
||||
if (!selectedImageProperties) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const properties = {...selectedImageProperties};
|
||||
|
||||
if (properties.src) {
|
||||
properties.haspreview = true;
|
||||
}
|
||||
|
||||
if (!properties.alt) {
|
||||
properties.presentation = true;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
filePickerCallback(params, self) {
|
||||
if (params.url) {
|
||||
const input = self.form.querySelector(Selectors.IMAGE.elements.url);
|
||||
input.value = params.url;
|
||||
|
||||
// Auto set the width and height.
|
||||
self.form.querySelector(Selectors.IMAGE.elements.width).value = '';
|
||||
self.form.querySelector(Selectors.IMAGE.elements.height).value = '';
|
||||
|
||||
// Load the preview image.
|
||||
self.loadPreviewImage(params.url);
|
||||
}
|
||||
}
|
||||
|
||||
storeImageDimensions(image) {
|
||||
// Store dimensions of the raw image, falling back to defaults for images without dimensions (e.g. SVG).
|
||||
this.rawImageDimensions = {
|
||||
width: image.width || this.DEFAULTS.WIDTH,
|
||||
height: image.height || this.DEFAULTS.HEIGHT,
|
||||
showdropzone: this.canShowDropZone,
|
||||
};
|
||||
|
||||
const getCurrentWidth = (element) => {
|
||||
if (element.value === '') {
|
||||
element.value = this.rawImageDimensions.width;
|
||||
}
|
||||
return element.value;
|
||||
};
|
||||
const getCurrentHeight = (element) => {
|
||||
if (element.value === '') {
|
||||
element.value = this.rawImageDimensions.height;
|
||||
}
|
||||
return element.value;
|
||||
};
|
||||
|
||||
const widthInput = this.form.querySelector(Selectors.IMAGE.elements.width);
|
||||
const currentWidth = getCurrentWidth(widthInput);
|
||||
|
||||
const heightInput = this.form.querySelector(Selectors.IMAGE.elements.height);
|
||||
const currentHeight = getCurrentHeight(heightInput);
|
||||
|
||||
const preview = this.form.querySelector(Selectors.IMAGE.elements.preview);
|
||||
preview.setAttribute('src', image.src);
|
||||
preview.style.display = 'inline';
|
||||
|
||||
const constrain = this.form.querySelector(Selectors.IMAGE.elements.constrain);
|
||||
if (this.isPercentageValue(currentWidth) && this.isPercentageValue(currentHeight)) {
|
||||
constrain.checked = currentWidth === currentHeight;
|
||||
} else if (image.width === 0 || image.height === 0) {
|
||||
// If we don't have both dimensions of the image, we can't auto-size it, so disable control.
|
||||
constrain.disabled = 'disabled';
|
||||
} else {
|
||||
// This is the same as comparing to 3 decimal places.
|
||||
const widthRatio = Math.round(1000 * parseInt(currentWidth, 10) / image.width);
|
||||
const heightRatio = Math.round(1000 * parseInt(currentHeight, 10) / image.height);
|
||||
constrain.checked = widthRatio === heightRatio;
|
||||
}
|
||||
}
|
||||
|
||||
loadPreviewImage(url) {
|
||||
const image = new Image();
|
||||
|
||||
image.addEventListener('error', () => {
|
||||
const preview = this.form.querySelector(Selectors.IMAGE.elements.preview);
|
||||
preview.style.display = 'none';
|
||||
});
|
||||
image.addEventListener('load', () => {
|
||||
this.storeImageDimensions(image);
|
||||
this.autoAdjustSize();
|
||||
});
|
||||
|
||||
image.src = url;
|
||||
}
|
||||
|
||||
urlChanged() {
|
||||
const input = this.form.querySelector(Selectors.IMAGE.elements.url);
|
||||
|
||||
if (input.value) {
|
||||
// Load the preview image.
|
||||
this.loadPreviewImage(input.value);
|
||||
}
|
||||
}
|
||||
|
||||
hasErrorUrlField() {
|
||||
const url = this.form.querySelector(Selectors.IMAGE.elements.url).value;
|
||||
const urlError = url === '';
|
||||
this.toggleVisibility(Selectors.IMAGE.elements.urlWarning, urlError);
|
||||
this.toggleAriaInvalid([Selectors.IMAGE.elements.url], urlError);
|
||||
|
||||
return urlError;
|
||||
}
|
||||
|
||||
hasErrorAltField() {
|
||||
const alt = this.form.querySelector(Selectors.IMAGE.elements.alt).value;
|
||||
const presentation = this.form.querySelector(Selectors.IMAGE.elements.presentation).checked;
|
||||
const imageAltError = alt === '' && !presentation;
|
||||
this.toggleVisibility(Selectors.IMAGE.elements.altWarning, imageAltError);
|
||||
this.toggleAriaInvalid([Selectors.IMAGE.elements.alt, Selectors.IMAGE.elements.presentation], imageAltError);
|
||||
|
||||
return imageAltError;
|
||||
}
|
||||
|
||||
toggleVisibility(selector, predicate) {
|
||||
const elements = this.form.querySelectorAll(selector);
|
||||
elements.forEach((element) => {
|
||||
element.style.display = predicate ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
toggleAriaInvalid(selectors, predicate) {
|
||||
selectors.forEach((selector) => {
|
||||
const elements = this.form.querySelectorAll(selector);
|
||||
elements.forEach((element) => element.setAttribute('aria-invalid', predicate));
|
||||
});
|
||||
}
|
||||
|
||||
updateWarning() {
|
||||
const urlError = this.hasErrorUrlField();
|
||||
const imageAltError = this.hasErrorAltField();
|
||||
|
||||
return urlError || imageAltError;
|
||||
}
|
||||
|
||||
getImageContext() {
|
||||
// Check if there are any accessibility issues.
|
||||
if (this.updateWarning()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classList = [];
|
||||
|
||||
const constrain = this.form.querySelector(Selectors.IMAGE.elements.constrain).value;
|
||||
if (constrain) {
|
||||
classList.push(Selectors.IMAGE.styles.responsive);
|
||||
}
|
||||
|
||||
return {
|
||||
url: this.form.querySelector(Selectors.IMAGE.elements.url).value,
|
||||
alt: this.form.querySelector(Selectors.IMAGE.elements.alt).value,
|
||||
width: this.form.querySelector(Selectors.IMAGE.elements.width).value,
|
||||
height: this.form.querySelector(Selectors.IMAGE.elements.height).value,
|
||||
presentation: this.form.querySelector(Selectors.IMAGE.elements.presentation).checked,
|
||||
customStyle: this.form.querySelector(Selectors.IMAGE.elements.customStyle).value,
|
||||
classlist: classList.join(' '),
|
||||
};
|
||||
}
|
||||
|
||||
setImage() {
|
||||
const pendingPromise = new Pending('tiny_media:setImage');
|
||||
const url = this.form.querySelector(Selectors.IMAGE.elements.url).value;
|
||||
if (url === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there are any accessibility issues.
|
||||
if (this.updateWarning()) {
|
||||
pendingPromise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for invalid width or height.
|
||||
const width = this.form.querySelector(Selectors.IMAGE.elements.width).value;
|
||||
if (!this.isPercentageValue(width) && isNaN(parseInt(width, 10))) {
|
||||
this.form.querySelector(Selectors.IMAGE.elements.width).focus();
|
||||
pendingPromise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const height = this.form.querySelector(Selectors.IMAGE.elements.height).value;
|
||||
if (!this.isPercentageValue(height) && isNaN(parseInt(height, 10))) {
|
||||
this.form.querySelector(Selectors.IMAGE.elements.height).focus();
|
||||
pendingPromise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
Templates.render('tiny_media/image', this.getImageContext())
|
||||
.then((html) => {
|
||||
this.editor.insertContent(html);
|
||||
this.currentModal.destroy();
|
||||
pendingPromise.resolve();
|
||||
|
||||
return html;
|
||||
})
|
||||
.catch();
|
||||
}
|
||||
|
||||
handleKeyupCharacterCount() {
|
||||
const alt = this.form.querySelector(Selectors.IMAGE.elements.alt).value;
|
||||
const current = this.form.querySelector('#currentcount');
|
||||
current.innerHTML = alt.length;
|
||||
}
|
||||
|
||||
autoAdjustSize(forceHeight = false) {
|
||||
// If we do not know the image size, do not do anything.
|
||||
if (!this.rawImageDimensions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const widthField = this.form.querySelector(Selectors.IMAGE.elements.width);
|
||||
const heightField = this.form.querySelector(Selectors.IMAGE.elements.height);
|
||||
const normalizeFieldData = (fieldData) => {
|
||||
fieldData.isPercentageValue = !!this.isPercentageValue(fieldData.field.value);
|
||||
if (fieldData.isPercentageValue) {
|
||||
fieldData.percentValue = parseInt(fieldData.field.value, 10);
|
||||
fieldData.pixelSize = this.rawImageDimensions[fieldData.type] / 100 * fieldData.percentValue;
|
||||
} else {
|
||||
fieldData.pixelSize = parseInt(fieldData.field.value, 10);
|
||||
fieldData.percentValue = fieldData.pixelSize / this.rawImageDimensions[fieldData.type] * 100;
|
||||
}
|
||||
|
||||
return fieldData;
|
||||
};
|
||||
|
||||
const getKeyField = () => {
|
||||
const getValue = () => {
|
||||
if (forceHeight) {
|
||||
return {
|
||||
field: heightField,
|
||||
type: 'height',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
field: widthField,
|
||||
type: 'width',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const currentValue = getValue();
|
||||
if (currentValue.field.value === '') {
|
||||
currentValue.field.value = this.rawImageDimensions[currentValue.type];
|
||||
}
|
||||
|
||||
return normalizeFieldData(currentValue);
|
||||
};
|
||||
|
||||
const getRelativeField = () => {
|
||||
if (forceHeight) {
|
||||
return normalizeFieldData({
|
||||
field: widthField,
|
||||
type: 'width',
|
||||
});
|
||||
} else {
|
||||
return normalizeFieldData({
|
||||
field: heightField,
|
||||
type: 'height',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const setImageDimensions = (image, keyField, relativeField, forceHeight = false) => {
|
||||
const getStyleValue = (field) => field.isPercentageValue ? `${field.percentValue}%` : `${field.pixelSize}px`;
|
||||
|
||||
// If the values are constrained, then only update the width.
|
||||
if (forceHeight) {
|
||||
if (keyField.type === 'width') {
|
||||
image.style.width = getStyleValue(keyField);
|
||||
} else {
|
||||
image.style.width = getStyleValue(relativeField);
|
||||
}
|
||||
} else {
|
||||
image.style[keyField.type] = getStyleValue(keyField);
|
||||
image.style[relativeField.type] = getStyleValue(relativeField);
|
||||
}
|
||||
};
|
||||
|
||||
const imagePreview = this.form.querySelector(Selectors.IMAGE.elements.preview);
|
||||
// Clear the existing preview sizes.
|
||||
imagePreview.style.width = '';
|
||||
imagePreview.style.height = '';
|
||||
|
||||
// Now update with the new values.
|
||||
const constrainField = this.form.querySelector(Selectors.IMAGE.elements.constrain);
|
||||
const keyField = getKeyField();
|
||||
const relativeField = getRelativeField();
|
||||
if (constrainField.checked) {
|
||||
// We are keeping the image in proportion.
|
||||
// Calculate the size for the relative field.
|
||||
if (keyField.isPercentageValue) {
|
||||
// In proportion, so the percentages are the same.
|
||||
relativeField.field.value = keyField.field.value;
|
||||
relativeField.percentValue = keyField.percentValue;
|
||||
} else {
|
||||
relativeField.pixelSize = Math.round(
|
||||
keyField.pixelSize / this.rawImageDimensions[keyField.type] * this.rawImageDimensions[relativeField.type]
|
||||
Promise.all([bodyImageInsert(templateContext, this.root), footerImageInsert(templateContext, this.root)])
|
||||
.then(() => {
|
||||
const imageinsert = new ImageInsert(
|
||||
this.root,
|
||||
this.editor,
|
||||
this.currentModal,
|
||||
this.canShowFilePicker,
|
||||
this.canShowDropZone,
|
||||
);
|
||||
relativeField.field.value = relativeField.pixelSize;
|
||||
}
|
||||
}
|
||||
setImageDimensions(imagePreview, keyField, relativeField, !!constrainField.checked);
|
||||
}
|
||||
|
||||
getSelectedImageProperties() {
|
||||
const image = this.getSelectedImage();
|
||||
if (!image) {
|
||||
this.selectedImage = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
const properties = {
|
||||
src: null,
|
||||
alt: null,
|
||||
width: null,
|
||||
height: null,
|
||||
presentation: false,
|
||||
};
|
||||
|
||||
const getImageHeight = (image) => {
|
||||
if (!this.isPercentageValue(String(image.height))) {
|
||||
return parseInt(image.height, 10);
|
||||
}
|
||||
|
||||
return image.height;
|
||||
};
|
||||
|
||||
const getImageWidth = (image) => {
|
||||
if (!this.isPercentageValue(String(image.width))) {
|
||||
return parseInt(image.width, 10);
|
||||
}
|
||||
|
||||
return image.width;
|
||||
};
|
||||
|
||||
// Get the current selection.
|
||||
this.selectedImage = image;
|
||||
|
||||
properties.customStyle = image.style.cssText;
|
||||
|
||||
const width = getImageWidth(image);
|
||||
if (width !== 0) {
|
||||
properties.width = width;
|
||||
}
|
||||
|
||||
const height = getImageHeight(image);
|
||||
if (height !== 0) {
|
||||
properties.height = height;
|
||||
}
|
||||
|
||||
properties.src = image.getAttribute('src');
|
||||
properties.alt = image.getAttribute('alt') || '';
|
||||
properties.presentation = (image.getAttribute('role') === 'presentation');
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
getSelectedImage() {
|
||||
const imgElm = this.editor.selection.getNode();
|
||||
const figureElm = this.editor.dom.getParent(imgElm, 'figure.image');
|
||||
if (figureElm) {
|
||||
return this.editor.dom.select('img', figureElm)[0];
|
||||
}
|
||||
|
||||
if (imgElm && (imgElm.nodeName.toUpperCase() !== 'IMG' || this.isPlaceholderImage(imgElm))) {
|
||||
return null;
|
||||
}
|
||||
return imgElm;
|
||||
}
|
||||
|
||||
isPlaceholderImage(imgElm) {
|
||||
if (imgElm.nodeName.toUpperCase() !== 'IMG') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (imgElm.hasAttribute('data-mce-object') || imgElm.hasAttribute('data-mce-placeholder'));
|
||||
}
|
||||
|
||||
isPercentageValue(value) {
|
||||
return value.match(/\d+%/);
|
||||
}
|
||||
|
||||
async registerEventListeners() {
|
||||
await this.currentModal.getBody();
|
||||
const root = this.currentModal.getRoot()[0];
|
||||
|
||||
this.form = root.querySelector(Selectors.IMAGE.elements.form);
|
||||
root.addEventListener('click', (e) => {
|
||||
const submitAction = e.target.closest(Selectors.IMAGE.actions.submit);
|
||||
const imageBrowserAction = e.target.closest(Selectors.IMAGE.actions.imageBrowser);
|
||||
if (submitAction) {
|
||||
e.preventDefault();
|
||||
this.setImage();
|
||||
}
|
||||
if (imageBrowserAction && this.canShowFilePicker) {
|
||||
e.preventDefault();
|
||||
displayFilepicker(this.editor, 'image').then((params) => {
|
||||
const self = this;
|
||||
this.filePickerCallback(params, self);
|
||||
|
||||
return;
|
||||
}).catch();
|
||||
}
|
||||
});
|
||||
|
||||
root.addEventListener('change', (e) => {
|
||||
const urlEle = e.target.closest(Selectors.IMAGE.elements.url);
|
||||
if (urlEle) {
|
||||
this.hasErrorUrlField();
|
||||
}
|
||||
|
||||
const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);
|
||||
if (presentationEle) {
|
||||
this.hasErrorAltField();
|
||||
}
|
||||
|
||||
const constrainEle = e.target.closest(Selectors.IMAGE.elements.constrain);
|
||||
if (constrainEle) {
|
||||
this.autoAdjustSize(true);
|
||||
}
|
||||
});
|
||||
|
||||
root.addEventListener('blur', (e) => {
|
||||
if (e.target.nodeType === Node.ELEMENT_NODE) {
|
||||
const urlEle = e.target.closest(Selectors.IMAGE.elements.url);
|
||||
if (urlEle) {
|
||||
this.urlChanged();
|
||||
}
|
||||
|
||||
const altEle = e.target.closest(Selectors.IMAGE.elements.alt);
|
||||
if (altEle) {
|
||||
this.hasErrorAltField();
|
||||
}
|
||||
|
||||
const widthEle = e.target.closest(Selectors.IMAGE.elements.width);
|
||||
if (widthEle) {
|
||||
this.autoAdjustSize();
|
||||
}
|
||||
|
||||
const heightEle = e.target.closest(Selectors.IMAGE.elements.height);
|
||||
if (heightEle) {
|
||||
this.autoAdjustSize(true);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Character count.
|
||||
root.addEventListener('keyup', (e) => {
|
||||
const altEle = e.target.closest(Selectors.IMAGE.elements.alt);
|
||||
if (altEle) {
|
||||
this.handleKeyupCharacterCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
imageinsert.init();
|
||||
return;
|
||||
})
|
||||
.catch(error => {
|
||||
window.console.log(error);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tiny media plugin image helpers.
|
||||
*
|
||||
* @module tiny_media/imagehelpers
|
||||
* @copyright 2024 Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Templates from 'core/templates';
|
||||
|
||||
/**
|
||||
* Renders and inserts the body template for inserting an image into the modal.
|
||||
*
|
||||
* @param {object} templateContext - The context for rendering the template.
|
||||
* @param {HTMLElement} root - The root element where the template will be inserted.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const bodyImageInsert = async(templateContext, root) => {
|
||||
return Templates.renderForPromise('tiny_media/insert_image_modal_insert', {...templateContext})
|
||||
.then(({html, js}) => {
|
||||
Templates.replaceNodeContents(root.querySelector('.tiny_image_body_template'), html, js);
|
||||
return;
|
||||
})
|
||||
.catch(error => {
|
||||
window.console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders and inserts the footer template for inserting an image into the modal.
|
||||
*
|
||||
* @param {object} templateContext - The context for rendering the template.
|
||||
* @param {HTMLElement} root - The root element where the template will be inserted.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const footerImageInsert = async(templateContext, root) => {
|
||||
return Templates.renderForPromise('tiny_media/insert_image_modal_insert_footer', {...templateContext})
|
||||
.then(({html, js}) => {
|
||||
Templates.replaceNodeContents(root.querySelector('.tiny_image_footer_template'), html, js);
|
||||
return;
|
||||
})
|
||||
.catch(error => {
|
||||
window.console.log(error);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Tiny media plugin image insertion class for Moodle.
|
||||
*
|
||||
* @module tiny_media/imageinsert
|
||||
* @copyright 2024 Meirza <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {prefetchStrings} from 'core/prefetch';
|
||||
import {getStrings} from 'core/str';
|
||||
import {component} from "./common";
|
||||
|
||||
prefetchStrings('tiny_media', [
|
||||
'insertimage',
|
||||
'enterurl',
|
||||
'enterurlor',
|
||||
'imageurlrequired',
|
||||
'uploading',
|
||||
'loading',
|
||||
]);
|
||||
|
||||
export class ImageInsert {
|
||||
|
||||
constructor(
|
||||
root,
|
||||
editor,
|
||||
currentModal,
|
||||
canShowFilePicker,
|
||||
canShowDropZone,
|
||||
) {
|
||||
this.root = root;
|
||||
this.editor = editor;
|
||||
this.currentModal = currentModal;
|
||||
this.canShowFilePicker = canShowFilePicker;
|
||||
this.canShowDropZone = canShowDropZone;
|
||||
}
|
||||
|
||||
init = async function() {
|
||||
// Get the localization lang strings and turn them into object.
|
||||
const langStringKeys = [
|
||||
'insertimage',
|
||||
'enterurl',
|
||||
'enterurlor',
|
||||
'imageurlrequired',
|
||||
'uploading',
|
||||
'loading',
|
||||
];
|
||||
const langStringvalues = await getStrings([...langStringKeys].map((key) => ({key, component})));
|
||||
|
||||
// Convert array to object.
|
||||
this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringvalues[index]]));
|
||||
this.currentModal.setTitle(this.langStrings.insertimage);
|
||||
};
|
||||
}
|
||||
@@ -29,11 +29,13 @@ $string['addmetadatatrack'] = 'Add metadata track';
|
||||
$string['addsource_help'] = 'You are recommended to provide an alternative media source, as desktop and mobile browsers support different file formats.';
|
||||
$string['addsource'] = 'Add alternative source';
|
||||
$string['addsubtitlestrack'] = 'Add subtitle track';
|
||||
$string['addurl'] = 'Add';
|
||||
$string['advancedsettings'] = 'Advanced settings';
|
||||
$string['audio'] = 'Audio';
|
||||
$string['audiosourcelabel'] = 'Audio source URL';
|
||||
$string['autoplay'] = 'Play automatically';
|
||||
$string['browserepositories'] = 'Browse repositories...';
|
||||
$string['browserepositoriesimage'] = 'Browse repositories';
|
||||
$string['captions_help'] = 'Captions may be used to describe everything happening in the track, including non-verbal sounds such as a phone ringing.';
|
||||
$string['captions'] = 'Captions';
|
||||
$string['captionssourcelabel'] = 'Caption track URL';
|
||||
@@ -53,7 +55,8 @@ $string['enteralt'] = 'Describe this image for someone who cannot see it';
|
||||
$string['entername'] = 'Name';
|
||||
$string['entersource'] = 'Source URL';
|
||||
$string['entertitle'] = 'Title';
|
||||
$string['enterurl'] = 'URL';
|
||||
$string['enterurl'] = 'Add via URL';
|
||||
$string['enterurlor'] = 'Or add via URL';
|
||||
$string['filemanager'] = 'File manager';
|
||||
$string['hasmissingfiles'] = 'Warning! The following files that are referenced in the text area appear to be missing:';
|
||||
$string['height'] = 'Height';
|
||||
@@ -61,6 +64,7 @@ $string['helplinktext'] = 'Media helper';
|
||||
$string['imagebuttontitle'] = 'Image';
|
||||
$string['imageproperties'] = 'Image properties';
|
||||
$string['imageurlrequired'] = 'An image must have a URL.';
|
||||
$string['insertimage'] = 'Insert image';
|
||||
$string['label'] = 'Label';
|
||||
$string['languagesavailable'] = 'Languages available';
|
||||
$string['languagesinstalled'] = 'Languages installed';
|
||||
|
||||
@@ -33,3 +33,11 @@ iframe.mm_iframe {
|
||||
font-weight: 600;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tiny_image_form .tiny_image_dropzone {
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.tiny_image_form .tiny_image_loader_container {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
@@ -21,92 +21,41 @@
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"uniqid": 0,
|
||||
"elementid": "exampleId",
|
||||
"src": "https://moodle.org/logo.png",
|
||||
"alt": "Image description",
|
||||
"presentation": true,
|
||||
"width": 600,
|
||||
"height": 400,
|
||||
"customStyle": "",
|
||||
"haspreview": true
|
||||
"loading": {},
|
||||
"title": "Insert image"
|
||||
}
|
||||
}}
|
||||
{{< core/modal }}
|
||||
{{$body}}
|
||||
<form class="tiny_image_form">
|
||||
<div style="display:none" role="alert" class="alert alert-warning mb-1 tiny_image_urlwarning">
|
||||
<label for="{{elementid}}_tiny_image_urlentry">
|
||||
<!-- URL warning -->
|
||||
<div role="alert" class="d-none alert alert-warning mb-1 tiny_image_urlwarning">
|
||||
<label>
|
||||
{{#str}} imageurlrequired, tiny_media {{/str}}
|
||||
</label>
|
||||
</div>
|
||||
{{#showfilepicker}}
|
||||
<div class="mb-1">
|
||||
<label for="{{elementid}}_tiny_image_urlentry">{{#str}} enterurl, tiny_media {{/str}}</label>
|
||||
<div class="input-group input-append w-100">
|
||||
<input name=urlentry class="form-control tiny_image_urlentry" type="url" id="{{elementid}}_tiny_image_urlentry" size="32" value="{{src}}"/>
|
||||
<span class="input-group-append">
|
||||
<button class="btn btn-secondary openimagebrowser" type="button">
|
||||
{{#str}} browserepositories, tiny_media {{/str}}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/showfilepicker}}
|
||||
{{^showfilepicker}}
|
||||
<div class="mb-1">
|
||||
<label for="{{elementid}}_tiny_image_urlentry">{{#str}} enterurl, tiny_media {{/str}}</label>
|
||||
<input name="urlentry" class="form-control fullwidth tiny_image_urlentry" type="url" id="{{elementid}}_tiny_image_urlentry" size="32" value="{{src}}">
|
||||
</div>
|
||||
{{/showfilepicker}}
|
||||
<div style="display:none" role="alert" class="alert alert-warning mb-1 tiny_image_altwarning">
|
||||
<label for="{{elementid}}_tiny_image_altentry">
|
||||
<!-- Presentation warning -->
|
||||
<div role="alert" class="d-none tiny_image_altwarning alert alert-warning mb-1">
|
||||
<label>
|
||||
{{#str}} presentationoraltrequired, tiny_media {{/str}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label for="{{elementid}}_tiny_image_altentry">{{#str}} enteralt, tiny_media {{/str}}</label>
|
||||
<textarea class="form-control fullwidth tiny_image_altentry" id="{{elementid}}_tiny_image_altentry" name="altentry" maxlength="125">{{alt}}</textarea>
|
||||
|
||||
<div id="the-count" class="d-flex justify-content-end small">
|
||||
<span id="currentcount">0</span>
|
||||
<span id="maximumcount"> / 125</span>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input tiny_image_presentation" id="{{elementid}}_tiny_image_presentation" {{# presentation }}checked{{/ presentation }}/>
|
||||
<label class="form-check-label" for="{{elementid}}_tiny_image_presentation">{{#str}} presentation, tiny_media {{/str}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<label class="" for="{{elementid}}_tiny_image_size">{{#str}} size, tiny_media {{/str}}</label>
|
||||
<div id="{{elementid}}_tiny_image_size" class="d-flex flex-wrap align-items-center tiny_image_size">
|
||||
<label class="accesshide" for="{{elementid}}_tiny_image_widthentry">{{#str}} width, tiny_media {{/str}}</label>
|
||||
<input type="text" class="form-control mr-1 w-auto input-mini tiny_image_widthentry" id="{{elementid}}_tiny_image_widthentry" size="4" value="{{width}}"/>
|
||||
x
|
||||
<label class="accesshide" for="{{elementid}}_tiny_image_heightentry">{{#str}} height, tiny_media {{/str}}</label>
|
||||
<input type="text" class="form-control ml-1 w-auto input-mini tiny_image_heightentry" id="{{elementid}}_tiny_image_heightentry" size="4" value="{{height}}"/>
|
||||
|
||||
<div class="form-check ml-2">
|
||||
<input type="checkbox" class="form-check-input tiny_image_constrain" id="{{elementid}}_tiny_image_constrain"/>
|
||||
<label class="form-check-label" for="{{elementid}}_tiny_image_constrain">{{#str}} constrain, tiny_media {{/str}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" class="tiny_image_customstyle" value="{{customStyle}}"/>
|
||||
<br/>
|
||||
<div class="mdl-align">
|
||||
<div class="tiny_image_preview_box">
|
||||
<img class="tiny_image_preview" alt=""
|
||||
{{#haspreview}} src="{{src}}" {{/haspreview}}
|
||||
{{^haspreview}} style="display: none;" {{/haspreview}}
|
||||
/>
|
||||
<!-- Preloader icon -->
|
||||
<div role="progressbar" class="d-none tiny_image_loader lead border rounded text-center">
|
||||
<div class="tiny_image_loader_container d-flex flex-column justify-content-center">
|
||||
<span data-region="loading-icon-container">
|
||||
{{> core/loading }}
|
||||
</span>
|
||||
<div>{{#str}} loading, tiny_media {{/str}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tiny_image_body_template"></div>
|
||||
</form>
|
||||
{{/body}}
|
||||
|
||||
{{$footer}}
|
||||
<button class="btn btn-secondary tiny_image_urlentrysubmit" type="submit">{{#str}} saveimage, tiny_media {{/str}}</button>
|
||||
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancel, moodle {{/str}}</button>
|
||||
<div class="tiny_image_footer_template container"></div>
|
||||
{{/footer}}
|
||||
{{/ core/modal }}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template tiny_media/insert_image_modal_insert
|
||||
|
||||
Insert image body template.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"showdropzone": true,
|
||||
"showfilepicker": true
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="tiny_image_insert_image">
|
||||
<div class="tiny_image_dropzone mb-1 d-flex align-items-center flex-column justify-content-center border rounded">
|
||||
{{#showdropzone}}
|
||||
<div class="tiny_image_dropzone_iconlabel text-center" role="button">
|
||||
<!-- Dropzone icon -->
|
||||
<i class="tiny_image_dropzone_icon fa-6x pb-2 text-secondary fa fa-cloud-upload"></i>
|
||||
<!-- Dropzone string -->
|
||||
<div class="tiny_image_dropzone_label lead text-center">
|
||||
<p class="mb-0">{{#str}} draganddrop, tiny_media {{/str}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- File input -->
|
||||
<input type="file" id="tiny_image_fileinput" accept="image/*" class="d-none">
|
||||
{{/showdropzone}}
|
||||
{{^showfilepicker}}
|
||||
<div class="tiny_image_dropzone_iconlabel text-center">
|
||||
<!-- Dropzone icon -->
|
||||
<i class="tiny_image_dropzone_icon fa-6x pb-2 text-secondary fa fa-cloud"></i>
|
||||
<!-- Dropzone string -->
|
||||
<div class="tiny_image_dropzone_label lead text-center">
|
||||
<p class="mb-0">{{#str}} repositorynotpermitted, tiny_media {{/str}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/showfilepicker}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template tiny_media/insert_image_modal_insert_footer
|
||||
|
||||
Insert image footer template.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"showdropzone": true,
|
||||
"elementid": "exampleId",
|
||||
"src": "https://moodle.org/logo.png",
|
||||
"showfilepicker": true
|
||||
}
|
||||
|
||||
}}
|
||||
<div class="row">
|
||||
<!-- First Column -->
|
||||
<div class="col-md-6 d-flex align-items-center p-0">
|
||||
<!-- Row 1: URL related label -->
|
||||
{{#showdropzone}}
|
||||
<label for="{{elementid}}_tiny_image_urlentry" title="{{#str}} enterurlor, tiny_media {{/str}}" class="tiny_image_url_label mr-1">{{#str}} enterurlor, tiny_media {{/str}}</label>
|
||||
{{/showdropzone}}
|
||||
{{^showdropzone}}
|
||||
<label for="{{elementid}}_tiny_image_urlentry" title="{{#str}} enterurl, tiny_media {{/str}}" class="tiny_image_url_label mr-1">{{#str}} enterurl, tiny_media {{/str}}</label>
|
||||
{{/showdropzone}}
|
||||
<label title="{{#str}} filename, tiny_media {{/str}}" class="d-none tiny_image_filename text-truncate mr-1">{{#str}} filename, tiny_media {{/str}}</label>
|
||||
<!-- Row 2: URL entry input. Needed by the insert image step and image details step if the image URL source from external -->
|
||||
<input name="urlentry" class="tiny_image_urlentry form-control w-50 mr-1" type="url" id="{{elementid}}_tiny_image_urlentry" size="32" value="{{src}}">
|
||||
<!-- Row 3: Add button. Needed by the insert image step -->
|
||||
<button disabled class="tiny_image_addurl btn btn-secondary mr-1" type="button">{{#str}} addurl, tiny_media {{/str}}</button>
|
||||
</div>
|
||||
<!-- Column 2: Saving, canceling, browsing repositories buttons -->
|
||||
<div class="col-md-6 text-right mt-2 md-0 p-0">
|
||||
<!-- Row 1: Cancel button -->
|
||||
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancel, moodle {{/str}}</button>
|
||||
{{#showfilepicker}}
|
||||
<!-- Row 3: Browse repositories button -->
|
||||
<button class="openimagebrowser btn btn-secondary" type="button">{{#str}} browserepositoriesimage, tiny_media {{/str}}</button>
|
||||
{{/showfilepicker}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user