MDL-84806 tiny_aiplacement: Announce action results for accessibility

The generatetext.js and associated templates have been adjusted to
match more closely with the generate image files. The generate image
way of displaying content was better suited to aria.
This commit is contained in:
David Woloszyn
2025-05-23 12:47:47 +10:00
parent 4aefa6b85d
commit a55c0e0067
13 changed files with 100 additions and 49 deletions
@@ -1,3 +1,3 @@
define("tiny_aiplacement/generateimage",["exports","tiny_aiplacement/imagemodal","core/ajax","core/str","core/templates","./mediaimage","tiny_aiplacement/options","tiny_aiplacement/generatebase"],(function(_exports,_imagemodal,_ajax,_str,_templates,_mediaimage,_options,_generatebase){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_imagemodal=_interopRequireDefault(_imagemodal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_mediaimage=_interopRequireDefault(_mediaimage),_generatebase=_interopRequireDefault(_generatebase);class GenerateImage extends _generatebase.default{constructor(){super(...arguments),_defineProperty(this,"SELECTORS",{GENERATEBUTTON:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generatebutton"]'),PROMPTAREA:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_imageprompt"]'),IMAGECONTAINER:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generate_image"]'),GENERATEBTN:'[data-action="generate"]',INSERTBTN:'[data-action="inserter"]',BACKTBTN:'[data-action="back"]',GENERATEDIMAGE:()=>'[id="'.concat(this.editor.id,'_tiny_generated_image"]')}),_defineProperty(this,"imageURL",null)}getModalClass(){return _imagemodal.default}handleContentModalClick(e,root){const actions={generate:()=>this.handleSubmit(root,e.target),inserter:()=>this.handleInsert(),cancel:()=>this.modalObject.destroy(),back:()=>{this.modalObject.destroy(),this.displayContentModal()}},actionKey=Object.keys(actions).find((key=>e.target.closest('[data-action="'.concat(key,'"]'))));actionKey&&(e.preventDefault(),actions[actionKey]())}setupPromptArea(root){const generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON()),promptArea=root.querySelector(this.SELECTORS.PROMPTAREA());promptArea.addEventListener("input",(()=>{generateBtn.disabled=""===promptArea.value.trim()}))}async handleSubmit(root,submitBtn){await this.displayLoading(root,submitBtn);const request={methodname:"aiplacement_editor_generate_image",args:this.getDisplayArgs(root)};try{this.responseObj=await _ajax.default.call([request])[0],this.responseObj.error?this.handleGenerationError(root,submitBtn,""):(await this.displayGeneratedImage(root),this.hideLoading(root,submitBtn))}catch(error){this.handleGenerationError(root,submitBtn,"")}}async handleInsert(){const mediaImage=new _mediaimage.default(this.editor,this.imageURL,this.promptText);await mediaImage.displayDialogue(),this.modalObject.destroy()}async handleGenerationError(root,submitBtn){let errorMessage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";errorMessage||(errorMessage=await(0,_str.getString)("errorgeneral","tiny_aiplacement")),this.hideLoading(root,submitBtn),this.modalObject.setBody(_templates.default.render("tiny_aiplacement/modalbodyerror",{errorMessage:errorMessage}));const backBtn=root.querySelector(this.SELECTORS.BACKTBTN),generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON());backBtn.classList.remove("hidden"),generateBtn.classList.add("hidden")}async displayGeneratedImage(root){const imageDisplayContainer=root.querySelector(this.SELECTORS.IMAGECONTAINER()),insertBtn=root.querySelector(this.SELECTORS.INSERTBTN);this.imageURL=this.responseObj.drafturl,imageDisplayContainer.innerHTML=await _templates.default.render("tiny_aiplacement/image",{url:this.responseObj.drafturl,elementid:this.editor.id,alt:this.promptText});const imagElement=root.querySelector(this.SELECTORS.GENERATEDIMAGE());return new Promise(((resolve,reject)=>{imagElement.onload=()=>{insertBtn.classList.remove("hidden"),resolve()},imagElement.onerror=error=>{reject(error)}}))}getDisplayArgs(root){const contextId=(0,_options.getContextId)(this.editor),promptText=root.querySelector(this.SELECTORS.PROMPTAREA()).value;this.promptText=promptText;return{contextid:contextId,prompttext:promptText,aspectratio:this.getSelectedRadioValue("aspect-ratio","square"),quality:this.getSelectedRadioValue("quality","standard"),numimages:1}}getSelectedRadioValue(radioName){let defaultValue=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;const radios=document.getElementsByName(radioName);for(const radio of radios)if(radio.checked)return radio.value;return defaultValue}}return _exports.default=GenerateImage,_exports.default}));
define("tiny_aiplacement/generateimage",["exports","tiny_aiplacement/imagemodal","core/ajax","core/str","core/templates","./mediaimage","tiny_aiplacement/options","tiny_aiplacement/generatebase"],(function(_exports,_imagemodal,_ajax,_str,_templates,_mediaimage,_options,_generatebase){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_imagemodal=_interopRequireDefault(_imagemodal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_mediaimage=_interopRequireDefault(_mediaimage),_generatebase=_interopRequireDefault(_generatebase);class GenerateImage extends _generatebase.default{constructor(){super(...arguments),_defineProperty(this,"SELECTORS",{GENERATEBUTTON:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generatebutton"]'),PROMPTAREA:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_imageprompt"]'),IMAGECONTAINER:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generate_image"]'),GENERATEBTN:'[data-action="generate"]',INSERTBTN:'[data-action="inserter"]',BACKTBTN:'[data-action="back"]',GENERATEDIMAGE:()=>'[id="'.concat(this.editor.id,'_tiny_generated_image"]')}),_defineProperty(this,"imageURL",null)}getModalClass(){return _imagemodal.default}handleContentModalClick(e,root){const actions={generate:()=>this.handleSubmit(root,e.target),inserter:()=>this.handleInsert(),cancel:()=>this.modalObject.destroy(),back:()=>{this.modalObject.destroy(),this.displayContentModal()}},actionKey=Object.keys(actions).find((key=>e.target.closest('[data-action="'.concat(key,'"]'))));actionKey&&(e.preventDefault(),actions[actionKey]())}setupPromptArea(root){const generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON()),promptArea=root.querySelector(this.SELECTORS.PROMPTAREA());promptArea.addEventListener("input",(()=>{generateBtn.disabled=""===promptArea.value.trim()}))}async handleSubmit(root,submitBtn){await this.displayLoading(root,submitBtn);const request={methodname:"aiplacement_editor_generate_image",args:this.getDisplayArgs(root)};try{if(this.responseObj=await _ajax.default.call([request])[0],this.responseObj.error)this.handleGenerationError(root,submitBtn,"");else{await this.displayGeneratedImage(root),await this.hideLoading(root,submitBtn);root.querySelector(this.SELECTORS.IMAGECONTAINER()).focus()}}catch(error){this.handleGenerationError(root,submitBtn,"")}}async handleInsert(){const mediaImage=new _mediaimage.default(this.editor,this.imageURL,this.promptText);await mediaImage.displayDialogue(),this.modalObject.destroy()}async handleGenerationError(root,submitBtn){let errorMessage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";errorMessage||(errorMessage=await(0,_str.getString)("errorgeneral","tiny_aiplacement")),this.modalObject.setBody(await _templates.default.render("tiny_aiplacement/modalbodyerror",{errorMessage:errorMessage}));const backBtn=root.querySelector(this.SELECTORS.BACKTBTN),generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON());backBtn.classList.remove("hidden"),generateBtn.classList.add("hidden"),await this.hideLoading(root,submitBtn),backBtn.focus()}async displayGeneratedImage(root){const imageDisplayContainer=root.querySelector(this.SELECTORS.IMAGECONTAINER()),insertBtn=root.querySelector(this.SELECTORS.INSERTBTN);this.imageURL=this.responseObj.drafturl,imageDisplayContainer.innerHTML=await _templates.default.render("tiny_aiplacement/image",{url:this.responseObj.drafturl,elementid:this.editor.id,alt:this.promptText});const imagElement=root.querySelector(this.SELECTORS.GENERATEDIMAGE());return new Promise(((resolve,reject)=>{imagElement.onload=()=>{insertBtn.classList.remove("hidden"),imagElement.focus(),resolve()},imagElement.onerror=error=>{reject(error)}}))}getDisplayArgs(root){const contextId=(0,_options.getContextId)(this.editor),promptText=root.querySelector(this.SELECTORS.PROMPTAREA()).value;this.promptText=promptText;return{contextid:contextId,prompttext:promptText,aspectratio:this.getSelectedRadioValue("aspect-ratio","square"),quality:this.getSelectedRadioValue("quality","standard"),numimages:1}}getSelectedRadioValue(radioName){let defaultValue=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;const radios=document.getElementsByName(radioName);for(const radio of radios)if(radio.checked)return radio.value;return defaultValue}}return _exports.default=GenerateImage,_exports.default}));
//# sourceMappingURL=generateimage.min.js.map
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
define("tiny_aiplacement/generatetext",["exports","./textmodal","core/ajax","core/str","core/templates","core_ai/helper","./options","./textmark","./generatebase"],(function(_exports,_textmodal,_ajax,_str,_templates,_helper,_options,_textmark,_generatebase){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_textmodal=_interopRequireDefault(_textmodal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_helper=_interopRequireDefault(_helper),_textmark=_interopRequireDefault(_textmark),_generatebase=_interopRequireDefault(_generatebase);class GenerateText extends _generatebase.default{constructor(){var obj,key,value;super(...arguments),value={GENERATEBUTTON:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generatebutton"]'),PROMPTAREA:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textprompt"]'),RESPONSEWRAPPER:".tiny_aiplacement_textresponse",RESPONSEPLACEHOLDER:".tiny_aiplacement_textresponse_placeholder",GENERATEDRESPONSE:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textresponse"]'),INSERTBTN:'[data-action="inserter"]',BACKTBTN:'[data-action="back"]'},(key="SELECTORS")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}getModalClass(){return _textmodal.default}handleContentModalClick(e,root){const actions={generate:()=>this.handleSubmit(root,e.target),inserter:()=>this.handleInsert(root,e.target),cancel:()=>this.modalObject.destroy(),back:()=>{this.modalObject.destroy(),this.displayContentModal()}},actionKey=Object.keys(actions).find((key=>e.target.closest('[data-action="'.concat(key,'"]'))));actionKey&&(e.preventDefault(),actions[actionKey]())}setupPromptArea(root){const generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON()),promptArea=root.querySelector(this.SELECTORS.PROMPTAREA());promptArea.addEventListener("input",(()=>{generateBtn.disabled=""===promptArea.value.trim()}))}async handleSubmit(root,submitBtn){await this.displayLoading(root,submitBtn);const request={methodname:"aiplacement_editor_generate_text",args:this.getRequestArgs(root)};try{this.responseObj=await _ajax.default.call([request])[0],this.responseObj.error?this.handleGenerationError(root,submitBtn,""):(await this.displayGeneratedText(root),this.hideLoading(root,submitBtn))}catch(error){this.handleGenerationError(root,submitBtn,"")}}async handleInsert(root,submitBtn){await this.displayLoading(root,submitBtn);const generatedResponseDiv=root.querySelector(this.SELECTORS.GENERATEDRESPONSE()),wrappedEditedResponse=await _textmark.default.wrapEditedSections(this.responseObj.generatedcontent,generatedResponseDiv.value);this.responseObj.editedtext=_helper.default.replaceLineBreaks(wrappedEditedResponse);const formattedResponse=await _templates.default.render("tiny_aiplacement/textinsert",this.responseObj);this.editor.insertContent(formattedResponse),this.editor.execCommand("mceRepaint"),this.editor.windowManager.close(),this.modalObject.hide()}async handleGenerationError(root,submitBtn){let errorMessage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";errorMessage||(errorMessage=await(0,_str.getString)("errorgeneral","tiny_aiplacement")),this.modalObject.setBody(_templates.default.render("tiny_aiplacement/modalbodyerror",{errorMessage:errorMessage}));const backBtn=root.querySelector(this.SELECTORS.BACKTBTN),generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON());backBtn.classList.remove("hidden"),generateBtn.classList.add("hidden"),this.hideLoading(root,submitBtn)}async displayGeneratedText(root){root.querySelector(this.SELECTORS.INSERTBTN).classList.remove("hidden");root.querySelector(this.SELECTORS.GENERATEDRESPONSE()).value=this.responseObj.generatedcontent;root.querySelector(this.SELECTORS.RESPONSEWRAPPER).classList.remove("hidden");root.querySelector(this.SELECTORS.RESPONSEPLACEHOLDER).classList.add("hidden")}getRequestArgs(root){return{contextid:(0,_options.getContextId)(this.editor),prompttext:root.querySelector(this.SELECTORS.PROMPTAREA()).value}}}return _exports.default=GenerateText,_exports.default}));
define("tiny_aiplacement/generatetext",["exports","./textmodal","core/ajax","core/str","core/templates","core_ai/helper","./options","./textmark","./generatebase"],(function(_exports,_textmodal,_ajax,_str,_templates,_helper,_options,_textmark,_generatebase){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_textmodal=_interopRequireDefault(_textmodal),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates),_helper=_interopRequireDefault(_helper),_textmark=_interopRequireDefault(_textmark),_generatebase=_interopRequireDefault(_generatebase);class GenerateText extends _generatebase.default{constructor(){var obj,key,value;super(...arguments),value={GENERATEBUTTON:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generatebutton"]'),PROMPTAREA:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textprompt"]'),TEXTCONTAINER:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_generate_text"]'),GENERATEDRESPONSE:()=>'[id="'.concat(this.editor.id,'_tiny_aiplacement_textresponse"]'),INSERTBTN:'[data-action="inserter"]',BACKTBTN:'[data-action="back"]'},(key="SELECTORS")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}getModalClass(){return _textmodal.default}handleContentModalClick(e,root){const actions={generate:()=>this.handleSubmit(root,e.target),inserter:()=>this.handleInsert(root,e.target),cancel:()=>this.modalObject.destroy(),back:()=>{this.modalObject.destroy(),this.displayContentModal()}},actionKey=Object.keys(actions).find((key=>e.target.closest('[data-action="'.concat(key,'"]'))));actionKey&&(e.preventDefault(),actions[actionKey]())}setupPromptArea(root){const generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON()),promptArea=root.querySelector(this.SELECTORS.PROMPTAREA());promptArea.addEventListener("input",(()=>{generateBtn.disabled=""===promptArea.value.trim()}))}async handleSubmit(root,submitBtn){await this.displayLoading(root,submitBtn);const request={methodname:"aiplacement_editor_generate_text",args:this.getRequestArgs(root)};try{if(this.responseObj=await _ajax.default.call([request])[0],this.responseObj.error)this.handleGenerationError(root,submitBtn,"");else{await this.displayGeneratedText(root),await this.hideLoading(root,submitBtn);root.querySelector(this.SELECTORS.TEXTCONTAINER()).focus()}}catch(error){this.handleGenerationError(root,submitBtn,"")}}async handleInsert(root,submitBtn){await this.displayLoading(root,submitBtn);const generatedResponseDiv=root.querySelector(this.SELECTORS.GENERATEDRESPONSE()),wrappedEditedResponse=await _textmark.default.wrapEditedSections(this.responseObj.generatedcontent,generatedResponseDiv.value);this.responseObj.editedtext=_helper.default.replaceLineBreaks(wrappedEditedResponse);const formattedResponse=await _templates.default.render("tiny_aiplacement/textinsert",this.responseObj);this.editor.insertContent(formattedResponse),this.editor.execCommand("mceRepaint"),this.editor.windowManager.close(),this.modalObject.hide()}async handleGenerationError(root,submitBtn){let errorMessage=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";errorMessage||(errorMessage=await(0,_str.getString)("errorgeneral","tiny_aiplacement")),this.modalObject.setBody(await _templates.default.render("tiny_aiplacement/modalbodyerror",{errorMessage:errorMessage}));const backBtn=root.querySelector(this.SELECTORS.BACKTBTN),generateBtn=root.querySelector(this.SELECTORS.GENERATEBUTTON());backBtn.classList.remove("hidden"),generateBtn.classList.add("hidden"),await this.hideLoading(root,submitBtn),backBtn.focus()}async displayGeneratedText(root){const textDisplayContainer=root.querySelector(this.SELECTORS.TEXTCONTAINER()),insertBtn=root.querySelector(this.SELECTORS.INSERTBTN);textDisplayContainer.innerHTML=await _templates.default.render("tiny_aiplacement/textarea",{elementid:this.editor.id,text:this.responseObj.generatedcontent});const textareaElement=root.querySelector(this.SELECTORS.GENERATEDRESPONSE());return new Promise(((resolve,reject)=>{textareaElement.textLength>0?(insertBtn.classList.remove("hidden"),textareaElement.focus(),resolve()):reject()}))}getRequestArgs(root){return{contextid:(0,_options.getContextId)(this.editor),prompttext:root.querySelector(this.SELECTORS.PROMPTAREA()).value}}}return _exports.default=GenerateText,_exports.default}));
//# sourceMappingURL=generatetext.min.js.map
File diff suppressed because one or more lines are too long
@@ -105,7 +105,10 @@ export default class GenerateImage extends GenerateBase {
this.handleGenerationError(root, submitBtn, '');
} else {
await this.displayGeneratedImage(root);
this.hideLoading(root, submitBtn);
await this.hideLoading(root, submitBtn);
// Focus the container for accessibility.
const imageDisplayContainer = root.querySelector(this.SELECTORS.IMAGECONTAINER());
imageDisplayContainer.focus();
}
} catch (error) {
this.handleGenerationError(root, submitBtn, '');
@@ -134,12 +137,14 @@ export default class GenerateImage extends GenerateBase {
// Get the default error message.
errorMessage = await getString('errorgeneral', 'tiny_aiplacement');
}
this.hideLoading(root, submitBtn);
this.modalObject.setBody(Templates.render('tiny_aiplacement/modalbodyerror', {'errorMessage': errorMessage}));
this.modalObject.setBody(await Templates.render('tiny_aiplacement/modalbodyerror', {'errorMessage': errorMessage}));
const backBtn = root.querySelector(this.SELECTORS.BACKTBTN);
const generateBtn = root.querySelector(this.SELECTORS.GENERATEBUTTON());
backBtn.classList.remove('hidden');
generateBtn.classList.add('hidden');
await this.hideLoading(root, submitBtn);
// Focus the back button for accessibility.
backBtn.focus();
}
/**
@@ -164,6 +169,7 @@ export default class GenerateImage extends GenerateBase {
return new Promise((resolve, reject) => {
imagElement.onload = () => {
insertBtn.classList.remove('hidden');
imagElement.focus();
resolve(); // Resolve the promise when the image is loaded.
};
imagElement.onerror = (error) => {
@@ -34,8 +34,7 @@ export default class GenerateText extends GenerateBase {
SELECTORS = {
GENERATEBUTTON: () => `[id="${this.editor.id}_tiny_aiplacement_generatebutton"]`,
PROMPTAREA: () => `[id="${this.editor.id}_tiny_aiplacement_textprompt"]`,
RESPONSEWRAPPER: '.tiny_aiplacement_textresponse',
RESPONSEPLACEHOLDER: '.tiny_aiplacement_textresponse_placeholder',
TEXTCONTAINER: () => `[id="${this.editor.id}_tiny_aiplacement_generate_text"]`,
GENERATEDRESPONSE: () => `[id="${this.editor.id}_tiny_aiplacement_textresponse"]`,
INSERTBTN: '[data-action="inserter"]',
BACKTBTN: '[data-action="back"]',
@@ -104,7 +103,10 @@ export default class GenerateText extends GenerateBase {
this.handleGenerationError(root, submitBtn, '');
} else {
await this.displayGeneratedText(root);
this.hideLoading(root, submitBtn);
await this.hideLoading(root, submitBtn);
// Focus the container for accessibility.
const textDisplayContainer = root.querySelector(this.SELECTORS.TEXTCONTAINER());
textDisplayContainer.focus();
}
} catch (error) {
this.handleGenerationError(root, submitBtn, '');
@@ -158,30 +160,41 @@ export default class GenerateText extends GenerateBase {
// Get the default error message.
errorMessage = await getString('errorgeneral', 'tiny_aiplacement');
}
this.modalObject.setBody(Templates.render('tiny_aiplacement/modalbodyerror', {'errorMessage': errorMessage}));
this.modalObject.setBody(await Templates.render('tiny_aiplacement/modalbodyerror', {'errorMessage': errorMessage}));
const backBtn = root.querySelector(this.SELECTORS.BACKTBTN);
const generateBtn = root.querySelector(this.SELECTORS.GENERATEBUTTON());
backBtn.classList.remove('hidden');
generateBtn.classList.add('hidden');
this.hideLoading(root, submitBtn);
await this.hideLoading(root, submitBtn);
// Focus the back button for accessibility.
backBtn.focus();
}
/**
* Display the generated image in the modal.
* Display the generated text in the modal.
*
* @param {HTMLElement} root - The root element of the modal.
*/
async displayGeneratedText(root) {
const textDisplayContainer = root.querySelector(this.SELECTORS.TEXTCONTAINER());
const insertBtn = root.querySelector(this.SELECTORS.INSERTBTN);
insertBtn.classList.remove('hidden');
// Add generated text to the modal.
const generatedResponseDiv = root.querySelector(this.SELECTORS.GENERATEDRESPONSE());
generatedResponseDiv.value = this.responseObj.generatedcontent;
const responseWrapper = root.querySelector(this.SELECTORS.RESPONSEWRAPPER);
responseWrapper.classList.remove('hidden');
const responsePlaceholder = root.querySelector(this.SELECTORS.RESPONSEPLACEHOLDER);
responsePlaceholder.classList.add('hidden');
// Render the textarea template and insert it into the modal.
textDisplayContainer.innerHTML = await Templates.render('tiny_aiplacement/textarea', {
elementid: this.editor.id,
text: this.responseObj.generatedcontent,
});
const textareaElement = root.querySelector(this.SELECTORS.GENERATEDRESPONSE());
return new Promise((resolve, reject) => {
if (textareaElement.textLength > 0) {
insertBtn.classList.remove('hidden');
textareaElement.focus();
resolve();
} else {
reject();
}
});
}
/**
@@ -41,7 +41,7 @@
}
.tiny-aiplacement-generate-image,
.tiny_aiplacement_textresponse_placeholder {
.tiny-aiplacement-generate-text {
border-radius: 15px;
border: 2px dashed #bbb;
padding: 20px;
@@ -55,7 +55,7 @@
margin-top: 0;
}
.tiny_aiplacement_textresponse textarea:disabled {
.tiny-aiplacement-generate-text textarea:disabled {
background-color: unset;
}
@@ -170,13 +170,15 @@
object-fit: contain;
}
.tiny_aiplacement_textresponse_placeholder {
.tiny-aiplacement-fa-text {
height: 100%;
width: 100%;
background-image: url('[[pix:tiny_aiplacement|sparkles-text]]');
background-repeat: no-repeat;
background-position: center;
background-color: rgba(255, 255, 255, 0.5);
background-blend-mode: lighten;
background-size: 6em 6em;
background-size: 100px 100px;
}
.tiny-aiplacement-generate-footer button img {
@@ -35,4 +35,4 @@
"alt": "Example image"
}
}}
<img id="{{elementid}}_tiny_generated_image" alt="{{alt}}" src="{{{url}}}" class="img-fluid mw-100">
<img id="{{elementid}}_tiny_generated_image" alt="{{alt}}" src="{{{url}}}" class="img-fluid mw-100" tabindex="-1">
@@ -36,12 +36,8 @@
}}
<div id="{{elementid}}_tiny_aiplacement_spinner" class="tiny-aiplacement-loading-spinner-container hidden">
<div class="tiny-aiplacement-loading-spinner d-flex justify-content-center align-items-center flex-column">
<div class="spinner-border tiny-aiplacement-spinner-style" role="status">
<span class="sr-only">
{{#str}} loading, tiny_aiplacement{{/str}}
</span>
</div>
<div id="{{elementid}}_tiny_aiplacement_loading_text" class="text-center tiny-aiplacement-spinner-text-style">
<div class="spinner-border tiny-aiplacement-spinner-style" aria-hidden="true"></div>
<div id="{{elementid}}_tiny_aiplacement_loading_text" class="text-center tiny-aiplacement-spinner-text-style" role="status">
{{#str}} loading, tiny_aiplacement{{/str}}
</div>
</div>
@@ -34,7 +34,7 @@
}
}}
<div class="d-flex align-items-center h-100">
<div class="tiny-aiplacement-error-display w-50">
<div class="tiny-aiplacement-error-display w-50" role="alert">
<div class="font-weight-bold">
<i class="icon fa fa-info-circle m-0 text-danger" aria-hidden="true"></i>
<span class="d-inline-block">
@@ -48,18 +48,8 @@
rows="12"></textarea>
</div>
<div class="col-md-8">
<div class="tiny_aiplacement_textresponse_placeholder">
&nbsp;
</div>
<div class="tiny_aiplacement_textresponse hidden">
<textarea
class="form-control"
id="{{elementid}}_tiny_aiplacement_textresponse"
rows="14"
placeholder="{{#str}} textresponse_placeholder, tiny_aiplacement {{/str}}"
autocomplete="off"
disabled
></textarea>
<div id="{{elementid}}_tiny_aiplacement_generate_text" class="tiny-aiplacement-generate-text text-center mt-3 mt-lg-0">
<div class="tiny-aiplacement-fa-text"></div>
</div>
</div>
</div>
@@ -0,0 +1,44 @@
{{!
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_aiplacement/textarea
Template for the generated text content within the Tiny Editor.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* elementid - The unique element id
* text - The generated text
Example context (json):
{
"elementid": "exampleId",
"text": "Example text"
}
}}
<textarea
class="form-control h-100"
id="{{elementid}}_tiny_aiplacement_textresponse"
placeholder="{{#str}} textresponse_placeholder, tiny_aiplacement {{/str}}"
aria-label="{{#str}} textresponse_label, tiny_aiplacement {{/str}}"
autocomplete="off"
>{{text}}</textarea>
@@ -31,13 +31,13 @@
{
"prompttext": "prompt text",
"timecreated": "1681901609",
"generatedcontent": "returned AI content"
"editedtext": "returned AI content"
}
}}
<span
data-generated="true"
data-prompt="{{prompttext}}"
data-time-created="{{timecreated}}">
data-generated="true"
data-prompt="{{prompttext}}"
data-time-created="{{timecreated}}">
{{{editedtext}}}
</span>