-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathattach.min.js
More file actions
11 lines (11 loc) · 14.2 KB
/
Copy pathattach.min.js
File metadata and controls
11 lines (11 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
/*!
* Attach component v1.0
*
* @author Serge Galich <gaserge@mail.ru>
* @copyright 2025
* @license MIT
* @website http://qujs.ru/attach/
*
* @requires Qu
*/
!function(t){"use strict";const e="Attach",i="qu-attach";if(t.Qu&&t.Qu[e])return void t.Qu.debug(`⚠️ [${e}] Already registered, skipping duplicate`);let n=null,a=!1;const o={selector:"attach",buttons:null,parallelUpload:!0,enableCaptcha:!1,captchaAction:"upload_file",enableCaptchaForRemove:!1,removeCaptchaAction:"remove_file",maxFiles:10,fileInputName:"file",filesInputName:"attach_field_name",addFormInputs:[],uploadUrl:"upload.php",removeUrl:"remove.php",fieldMap:{},trimFields:!0,startPath:"/",urlTemplates:{file:"#startPath##folder#/#filename#",thumb:"#startPath##folder#/#thumb#",copyUrl:"#startPath##folder#/#filename#",copyImgUrl:"#startPath##folder#/#filename#"},lexicon:{maxFilesExceeded:"Максимум %max% файлов",downloadError:"Ошибка при загрузке",fileDeleted:"Файл удален",deleteError:"Ошибка удаления",uploadSuccess:"Все файлы успешно загружены",urlCopied:"URL к файлу скопирован в буфер",urlCopyManually:"Скопируйте вручную:"}};function r(t={}){this._config=Object.assign({},o,t),this.files=[]}r.libName=e,r._setData=function(t,e,n,a=i){const o=`data-${a}-${e}`;void 0===n?t.setAttribute(o,""):t.setAttribute(o,n)},r._getData=function(t,e,n=i){return t&&"function"==typeof t.getAttribute?t.getAttribute(`data-${n}-${e}`):null},r._hasData=function(t,e,n=i){return!(!t||"function"!=typeof t.hasAttribute)&&t.hasAttribute(`data-${n}-${e}`)},r._removeData=function(t,e,n=i){t.removeAttribute(`data-${n}-${e}`)},r._getDataAttrName=function(t,e=i){return`data-${e}-${t}`},r._Qu={debug:function(...t){if(n&&n.debug)return n.debug(...t);console.log(...t)},loading:function(t,e){if(n&&n.loading)return n.loading(t,e);e&&(e.style.opacity=t?.5:1)},on:function(t,e,i,a){return n&&n.on?n.on(t,e,i,a):"string"!=typeof e&&e.addEventListener?("string"==typeof t&&(t=t.split(" ").filter(t=>t.trim())),void t.forEach(t=>{e.addEventListener(t.trim(),i,a)})):"string"==typeof e?("string"==typeof t&&(t=t.split(" ").filter(t=>t.trim())),void t.forEach(t=>{document.addEventListener(t.trim(),function(t){const n=t.target.closest(e);n&&(t._target=n,i(t))},a)})):void 0},dragScroll:function(t,e={}){if(n&&n.dragScroll)return n.dragScroll(t,e)},dom:function(){return n&&n.dom?n.dom():Promise.resolve()},get Notifyer(){return n&&n.Notifyer?n.Notifyer:{success:function(t,e={}){const i=e.title||"✅";alert(i+"\n\n"+t)},error:function(t,e={}){const i=e.title||"❌";alert(i+"\n\n"+t)},info:function(t,e={}){const i=e.title||"ℹ️";alert(i+"\n\n"+t)}}},get GreCaptcha(){return n&&n.GreCaptcha?n.GreCaptcha:null}},r.use=function(t){"function"==typeof t&&t(r)},r.extend=function(){Array.isArray(t[e+"Extend"])&&(t[e+"Extend"].forEach(t=>{r.use(t)}),t[e+"Extend"]=[])},r.loaded=function(t){n=t,r.extend(),r.debug(`📗 [${e}] loaded`)},r.debug=function(...t){r._debug&&r._Qu.debug(...t)},r.initOnce=function(t={}){!0!==a&&(a=!0)},r.init=function(t,i={}){n=t,r.initOnce(i),r.config(i),r.debug(`⚙️ [${e}] init`,o)},r.config=function(t){return Object.assign(o,t),r},r.prototype={constructor:r,use:function(t){"function"==typeof t&&t(this)},test:function(){return"test all"},init:function(t){return r._hasData(t,"inited")||(this.extendConfigFromData(t),this.container=t,this.form=t.closest("form"),this.input=this.container.querySelector(`[${r._getDataAttrName("input")}]`),this.dropzone=this.container.querySelector(`[${r._getDataAttrName("dropzone")}]`),this.fileInput=this.container.querySelector(`[${r._getDataAttrName("file-input")}]`),this.previews=this.container.querySelector(`[${r._getDataAttrName("previews")}]`),this.templateEl=this.container.querySelector(`template[${r._getDataAttrName("tpl")}]`),this.templateEl||r.debug(`❌ [${e}] Template not found`),this.bindEvents(),this.loadExistingFiles(),this.updateHiddenInput(),t._attachInstance=this,r._setData(t,"inited","true"),r.debug(`🧩 [${e}] init `,{config:this._config,container:t})),this},extendConfigFromData:function(t){const e=t.closest("form");e&&r._Qu.GreCaptcha&&e.hasAttribute(r._Qu.GreCaptcha._config.selector)&&(this._config.enableCaptcha=!0);for(let e in t.dataset)if(e.startsWith("quAttach")){let i=e.slice(8);i=i.charAt(0).toLowerCase()+i.slice(1);let n=t.dataset[e];if("string"==typeof n&&(n.startsWith("[")||n.startsWith("{")))try{n=JSON.parse(n)}catch(t){}else"true"===n?n=!0:"false"===n&&(n=!1);n&&"object"==typeof n&&!Array.isArray(n)?this._config[i]={...this._config[i],...n}:this._config[i]=n}},getAllFieldsFromMap:function(){const t=new Set;return Object.keys(this._config.fieldMap||{}).forEach(e=>{t.add(e)}),Array.from(t)},convertData:function(t,e="local"){const i=this._config.fieldMap||{},n={};if("local"===e){const e={};for(let[t,n]of Object.entries(i))e[n]=t;for(let[i,a]of Object.entries(t))e[i]?n[e[i]]=a:n[i]=a}else for(let[e,a]of Object.entries(t))i[e]?n[i[e]]=a:n[e]=a;return n},updateHiddenInput(){const t=this.files.map(t=>this.convertData(t,"server"));this.input.value=JSON.stringify(t)},bindEvents:function(){this.dropzone.addEventListener("click",t=>{t.target.closest(`[${r._getDataAttrName("previews")}]`)||t.target.closest(`[${r._getDataAttrName("tpl-el")}]`)||this.fileInput.click()}),this.fileInput.addEventListener("change",t=>{this.handleFiles(t.target.files)}),this.dropzone.addEventListener("dragover",t=>{t.preventDefault(),this.dropzone.classList.add("dragover")}),this.dropzone.addEventListener("dragleave",()=>{this.dropzone.classList.remove("dragover")}),this.dropzone.addEventListener("drop",t=>{t.preventDefault(),this.dropzone.classList.remove("dragover"),this.handleFiles(t.dataTransfer.files)}),"horizontal"===r._getData(this.container,"mode")&&r._Qu.dragScroll(this.previews,{exclude:"button, a, input, [data-qu-attach-field]",speed:1.5})},handleFiles(t){const i=Array.from(t);let n=this;if(this.files.length+i.length>this._config.maxFiles){const t=this._config.lexicon.maxFilesExceeded.replace("%max%",this._config.maxFiles);return void r._Qu.Notifyer.error(t)}if(r._Qu.loading(!0,this.container),this._config.parallelUpload){const t=i.map(t=>this.uploadFile(t));Promise.all(t).then(()=>{r.debug(`✅ [${e}] All files uploaded in parallel`)}).catch(t=>{r._Qu.Notifyer.error(n._config.lexicon.downloadError),console.error(t)}).finally(()=>{r._Qu.loading(!1,this.container)})}else this.uploadFilesSequentially(i)},async uploadFilesSequentially(t){let i=!1,n=this;for(let a=0;a<t.length;a++)try{r.debug(`⏳ [${e}] Uploading file ${a+1} of ${t.length}`),await this.uploadFile(t[a])}catch(e){i=!0,r._Qu.Notifyer.error(n._config.lexicon.downloadError+" "+t[a].name),console.error(e)}i||r._Qu.Notifyer.success(n._config.lexicon.uploadSuccess),r.debug(`✅ [${e}] files uploaded in sequentially`),r._Qu.loading(!1,this.container)},additionalInputs(t){return this._config.addFormInputs.forEach(i=>{if("object"==typeof i){const n=Object.keys(i)[0],a=i[n],o=this.form.querySelector(`[name="${n}"]`);o?t.append(a,o.value||""):r.debug(`❌ [${e}]`,`input with name="${n}" in form not found...`,{form:this.form})}else{const n=this.form.querySelector(`[name="${i}"]`);n?t.append(i,n.value||""):r.debug(`❌ [${e}]`,`input with name="${i}" in form not found...`,{form:this.form})}}),t},async getCaptchaToken(t){const i=r._Qu.GreCaptcha,n=this.container.closest("form");if(i&&i._config.enabled&&""!==i._config.siteKey&&n.hasAttribute(i._config.selector))try{const e=t||"upload_file";return await i.check(e)}catch(t){throw console.error(`❌ [${e}] Captcha error:`,t),new Error("Captcha error")}return null},async uploadFile(t){r._Qu.loading(!0,this.container);let i=this;try{const n=await this.getCaptchaToken(this._config.captchaAction),a=new FormData;a.append(this._config.fileInputName,t),a.append(this._config.filesInputName,this.input.name),n&&(a.append(r._Qu.GreCaptcha?._config?.tokenInput||"g-recaptcha-response",n),r.debug(`🔑 [${e}] Adding captcha token to upload request`)),this.additionalInputs(a);const o=await fetch(this._config.uploadUrl,{method:"POST",body:a}),s=await o.json();if(r.debug(`🚀 [${e}] upload fetch data`,{data:s,file:t,this:this}),s.success){const t=this.convertData(s.data,"local"),e=this.getAllFieldsFromMap(),i={};e.forEach(t=>{i[t]=""});const n={...i,...t,id:this.generateId()};if(this.files.push(n),this.templateEl){const t=this.renderTemplate(n);this.attachPreviewEvents(t,n),this.previews.appendChild(t),this.updateMoveButtons()}this.updateHiddenInput(),r._Qu.Notifyer.success(s.message)}else r._Qu.Notifyer.error(s.data?.message||s.message||i._config.lexicon.downloadError+" "+t.name)}catch(t){throw r._Qu.Notifyer.error(i._config.lexicon.downloadError),console.error(t),t}finally{r._Qu.loading(!1,this.container)}},loadExistingFiles:function(){try{if(this.input&&this.input.value){const t=JSON.parse(this.input.value);if(Array.isArray(t)&&t.length){const e=this.getAllFieldsFromMap();this.files=t.map(t=>{const i=this.convertData(t,"local");return e.forEach(t=>{i.hasOwnProperty(t)||(i[t]="")}),i}),this.renderAllPreviews()}}}catch(t){console.warn("Error loading existing files",t)}},renderAllPreviews:function(){this.previews&&(this.previews.innerHTML="",this.templateEl?(this.files.forEach(t=>{const e=this.renderTemplate(t);this.attachPreviewEvents(e,t),this.previews.appendChild(e)}),this.updateMoveButtons()):this.files.forEach(t=>{const e=document.createElement("div");e.textContent=t.filename||t.originalName||"File",this.previews.appendChild(e)}))},attachPreviewEvents:function(t,e){const i=t.querySelector(`[${r._getDataAttrName("tpl-remove")}]`);i&&i.addEventListener("click",t=>{t.preventDefault(),this.removeFile(e)});const n=t.querySelector(`[${r._getDataAttrName("tpl-up")}]`);n&&n.addEventListener("click",t=>{t.preventDefault(),this.moveFileUp(e.id)});const a=t.querySelector(`[${r._getDataAttrName("tpl-down")}]`);a&&a.addEventListener("click",t=>{t.preventDefault(),this.moveFileDown(e.id)});const o=t.querySelector(`[${r._getDataAttrName("tpl-copy")}]`);o&&o.addEventListener("click",t=>{t.preventDefault(),this.copyUrl(e)})},buildUrl:function(t,e="file"){return(this._config.urlTemplates[e]||this._config.urlTemplate).replace(/#startPath#/g,this._config.startPath).replace(/#folder#/g,t.folder||"").replace(/#filename#/g,t.filename||"").replace(/#thumb#/g,t.thumb||"").replace(/#caption#/g,t.caption||"").replace(/#ext#/g,t.ext||"")},copyUrl:function(t){let e,i=this;e=!1===t.thumb||""==t.thumb?this.buildUrl(t,"copyUrl"):this.buildUrl(t,"copyImgUrl"),navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(e).then(function(){r._Qu.Notifyer.success(i._config.lexicon.urlCopied)}).catch(()=>{prompt(i._config.lexicon.urlCopyManually,e)}):prompt(i._config.lexicon.urlCopyManually,e)},moveFileUp:function(t){this.previews.classList.add("is-moving");const e=this.files.findIndex(e=>e.id==t);if(e>0){[this.files[e-1],this.files[e]]=[this.files[e],this.files[e-1]],this.updateHiddenInput();const t=this.previews.children;t.length>e&&e-1>=0&&this.previews.insertBefore(t[e],t[e-1]),this.updateMoveButtons()}setTimeout(()=>{this.previews.classList.remove("is-moving")},100)},moveFileDown:function(t){this.previews.classList.add("is-moving");const e=this.files.findIndex(e=>e.id==t);if(e<this.files.length-1){[this.files[e],this.files[e+1]]=[this.files[e+1],this.files[e]],this.updateHiddenInput();const t=this.previews.children;t.length>e+1&&this.previews.insertBefore(t[e+1],t[e]),this.updateMoveButtons()}setTimeout(()=>{this.previews.classList.remove("is-moving")},100)},updateMoveButtons:function(){const t=this.previews.children;for(let e=0;e<t.length;e++){const i=t[e],n=i.querySelector(`[${r._getDataAttrName("tpl-up")}]`),a=i.querySelector(`[${r._getDataAttrName("tpl-down")}]`);n&&(n.style.display=0===e?"none":""),a&&(a.style.display=e===t.length-1?"none":"")}},attachInputListener:function(t,e,i){t.addEventListener("input",t=>{const n=this.files.findIndex(t=>t.id==e);-1!==n&&(this.files[n][i]=t.target.value,this.updateHiddenInput())}),t.addEventListener("change",t=>{const n=this.files.findIndex(t=>t.id==e);-1!==n&&(this.files[n][i]=t.target.value,this.updateHiddenInput())})},async isCaptchaRequiredForRemove(){return!(!this._config.enableCaptcha||!this._config.enableCaptchaForRemove)},removeFile:async function(t){const i=this.convertData(t,"server");let n=this;r._Qu.loading(!0,this.container);try{const a=new FormData;a.append("file_id",i.id||t.id),a.append("filename",t.filename),a.append("caption",t.caption),a.append(this._config.filesInputName,this.input.name),this.additionalInputs(a);if(await this.isCaptchaRequiredForRemove()){const t=await this.getCaptchaToken(this._config.removeCaptchaAction);t?(a.append(r._Qu.GreCaptcha?._config?.tokenInput||"g-recaptcha-response",t),r.debug(`🔑 [${e}] Adding captcha token to remove request`)):r.debug(`⚠️ [${e}] Captcha required but no token received`)}const o=await fetch(this._config.removeUrl,{method:"POST",body:a}),s=await o.json();r.debug(`🚀 [${e}] remove fetch data`,{data:s,file:t,this:this}),r._Qu.loading(!1,this.container),s.success?(this.files=this.files.filter(e=>e.id!=t.id),this.updateHiddenInput(),this.renderAllPreviews(),r._Qu.Notifyer.success(s.message||n._config.lexicon.fileDeleted)):r._Qu.Notifyer.error(s.message||n._config.lexicon.deleteError)}catch(t){r._Qu.Notifyer.error(`Network error: ${t.message}`),r._Qu.loading(!1,this.container)}},renderTemplate:function(t){const e=document.importNode(this.templateEl.content,!0),i=e.querySelector(`[${r._getDataAttrName("tpl-el")}]`);i&&r._setData(i,"id",t.id);const n=this.getAllFieldsFromMap();e.querySelectorAll(`[${r._getDataAttrName("field")}]`).forEach(e=>{const i=r._getData(e,"field");n.includes(i)&&!t.hasOwnProperty(i)&&(t[i]=""),"INPUT"===e.tagName||"TEXTAREA"===e.tagName||"SELECT"===e.tagName?(e.value=void 0!==t[i]?t[i]:"",this.attachInputListener(e,t.id,i)):e.textContent=void 0!==t[i]?t[i]:""});const a=e.querySelector(`[${r._getDataAttrName("thumb-field")}]`);if(a&&!1!==t.thumb&&""!==t.thumb){const e=this.buildUrl(t,"thumb");a.innerHTML=`<img src="${e}" alt="" loading="lazy">`}else a.innerHTML=`<span>${t.ext||"📄"}</span>`;return e},generateId:function(){let t=0;return this.files.forEach(e=>{const i=e.id;if(i){const e=parseInt(i,10);!isNaN(e)&&e>t&&(t=e)}}),(t+1).toString()}},t.Qu?t.Qu.lib(e,r):(t._QuLibs=t._QuLibs||[],t._QuLibs.push({name:e,instance:r}))}("undefined"!=typeof window?window:global);