mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Update, rebuild, and add a new API endpoint.
This commit is contained in:
		
							
								
								
									
										42
									
								
								resources/assets/v2/libraries/dark-editable/Modes/BaseMode.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								resources/assets/v2/libraries/dark-editable/Modes/BaseMode.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  * BaseMode.js | ||||
|  * Copyright (c) 2024 https://github.com/DarKsandr/dark-editable | ||||
|  * | ||||
|  * License: MIT | ||||
|  * | ||||
|  * Copied and slightly edited by James Cole <james@firefly-iii.org> | ||||
|  */ | ||||
| export default class BaseMode{ | ||||
|     constructor(context) { | ||||
|         if(this.constructor === BaseMode){ | ||||
|             throw new Error(`It's abstract class`); | ||||
|         } | ||||
|         this.context = context; | ||||
|     } | ||||
|     event_show(){ | ||||
|         this.context.typeElement.hideError(); | ||||
|         this.context.typeElement.element.value = this.context.value; | ||||
|         this.context.element.dispatchEvent(new CustomEvent("show")); | ||||
|     } | ||||
|     event_shown(){ | ||||
|         this.context.element.dispatchEvent(new CustomEvent("shown")); | ||||
|     } | ||||
|     event_hide(){ | ||||
|         this.context.element.dispatchEvent(new CustomEvent("hide")); | ||||
|     } | ||||
|     event_hidden(){ | ||||
|         this.context.element.dispatchEvent(new CustomEvent("hidden")); | ||||
|     } | ||||
|     init(){ | ||||
|         throw new Error('Method `init` not define!'); | ||||
|     } | ||||
|     enable(){ | ||||
|         throw new Error('Method `enable` not define!'); | ||||
|     } | ||||
|     disable(){ | ||||
|         throw new Error('Method `disable` not define!'); | ||||
|     } | ||||
|     hide(){ | ||||
|         throw new Error('Method `hide` not define!'); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								resources/assets/v2/libraries/dark-editable/Modes/InlineMode.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										40
									
								
								resources/assets/v2/libraries/dark-editable/Modes/InlineMode.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| /* | ||||
|  * InlineMode.js | ||||
|  * Copyright (c) 2024 https://github.com/DarKsandr/dark-editable | ||||
|  * | ||||
|  * License: MIT | ||||
|  * | ||||
|  * Copied and slightly edited by James Cole <james@firefly-iii.org> | ||||
|  */ | ||||
|  | ||||
| import BaseMode from "./BaseMode.js"; | ||||
|  | ||||
| export default class InlineMode extends BaseMode{ | ||||
|     init(){ | ||||
|         const open = () => { | ||||
|             if(!this.context.disabled){ | ||||
|                 const item = this.context.typeElement.create(); | ||||
|                 this.event_show(); | ||||
|                 this.context.element.removeEventListener('click', open); | ||||
|                 this.context.element.innerHTML = ''; | ||||
|                 this.context.element.append(item); | ||||
|                 this.event_shown(); | ||||
|             } | ||||
|         } | ||||
|         this.context.element.addEventListener('click', open); | ||||
|     } | ||||
|     enable(){ | ||||
|  | ||||
|     } | ||||
|     disable(){ | ||||
|  | ||||
|     } | ||||
|     hide(){ | ||||
|         this.event_hide(); | ||||
|         this.context.element.innerHTML = this.context.value; | ||||
|         setTimeout(() => { | ||||
|             this.init(); | ||||
|             this.event_hidden(); | ||||
|         }, 100); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										53
									
								
								resources/assets/v2/libraries/dark-editable/Modes/PopupMode.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										53
									
								
								resources/assets/v2/libraries/dark-editable/Modes/PopupMode.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  * PopupMode.js | ||||
|  * Copyright (c) 2024 https://github.com/DarKsandr/dark-editable | ||||
|  * | ||||
|  * License: MIT | ||||
|  * | ||||
|  * Copied and slightly edited by James Cole <james@firefly-iii.org> | ||||
|  */ | ||||
|  | ||||
| import BaseMode from "./BaseMode.js"; | ||||
|  | ||||
| export default class PopupMode extends BaseMode{ | ||||
|     init(){ | ||||
|         this.popover = new bootstrap.Popover(this.context.element, { | ||||
|             container: "body", | ||||
|             content: this.context.typeElement.create(), | ||||
|             html: true, | ||||
|             customClass: "dark-editable", | ||||
|             title: this.context.title, | ||||
|         }); | ||||
|         this.context.element.addEventListener('show.bs.popover', () => { | ||||
|             this.event_show(); | ||||
|         }); | ||||
|         this.context.element.addEventListener('shown.bs.popover', () => { | ||||
|             this.event_shown(); | ||||
|         }); | ||||
|         this.context.element.addEventListener('hide.bs.popover', () => { | ||||
|             this.event_hide(); | ||||
|         }); | ||||
|         this.context.element.addEventListener('hidden.bs.popover', () => { | ||||
|             this.event_hidden(); | ||||
|         }); | ||||
|  | ||||
|         document.addEventListener('click', (e) => { | ||||
|             const target = e.target; | ||||
|             if(target === this.popover.tip || target === this.context.element) return; | ||||
|             let current = target; | ||||
|             while(current = current.parentNode){ | ||||
|                 if(current === this.popover.tip) return; | ||||
|             } | ||||
|             this.hide(); | ||||
|         }) | ||||
|     } | ||||
|     enable(){ | ||||
|         this.popover.enable(); | ||||
|     } | ||||
|     disable(){ | ||||
|         this.popover.disable(); | ||||
|     } | ||||
|     hide(){ | ||||
|         this.popover.hide(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										255
									
								
								resources/assets/v2/libraries/dark-editable/Types/BaseType.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										255
									
								
								resources/assets/v2/libraries/dark-editable/Types/BaseType.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,255 @@ | ||||
| /* | ||||
|  * BaseMode.js | ||||
|  * Copyright (c) 2024 https://github.com/DarKsandr/dark-editable | ||||
|  * | ||||
|  * License: MIT | ||||
|  * | ||||
|  * Copied and slightly edited by James Cole <james@firefly-iii.org> | ||||
|  */ | ||||
|  | ||||
| export default class BaseType { | ||||
|     context = null; | ||||
|     element = null; | ||||
|     error = null; | ||||
|     form = null; | ||||
|     load = null; | ||||
|     buttonGroup = null; | ||||
|     buttons = {success: null, cancel: null}; | ||||
|  | ||||
|     constructor(context) { | ||||
|         if (this.constructor === BaseType) { | ||||
|             throw new Error(`It's abstract class`); | ||||
|         } | ||||
|         this.context = context; | ||||
|     } | ||||
|  | ||||
|     create() { | ||||
|         throw new Error('Method `create` not define!'); | ||||
|     } | ||||
|  | ||||
|     createContainer(element) { | ||||
|         const div = document.createElement(`div`); | ||||
|  | ||||
|         // original list of elements: | ||||
|         this.element = element; | ||||
|         this.error = this.createContainerError(); | ||||
|         this.form = this.createContainerForm(); | ||||
|         this.load = this.createContainerLoad(); | ||||
|         this.buttons.success = this.createButtonSuccess(); | ||||
|         this.buttons.cancel = this.createButtonCancel(); | ||||
|  | ||||
|         // create first div, with label and input: | ||||
|         const topDiv = document.createElement(`div`); | ||||
|         topDiv.classList.add("col-12"); | ||||
|  | ||||
|         // create label: | ||||
|         const label = document.createElement(`label`); | ||||
|         label.classList.add("visually-hidden"); | ||||
|         label.for = element.id; | ||||
|  | ||||
|         // add label + input to top div: | ||||
|         topDiv.append(label, element); | ||||
|  | ||||
|         // create second div, with button group: | ||||
|         const bottomDiv = document.createElement(`div`); | ||||
|         bottomDiv.classList.add("col-12"); | ||||
|  | ||||
|         // create button group: | ||||
|         this.buttonGroup = this.createButtonGroup(); | ||||
|  | ||||
|         // append buttons to button group: | ||||
|         this.buttonGroup.append(this.buttons.success, this.buttons.cancel); | ||||
|         bottomDiv.append(this.buttonGroup); | ||||
|  | ||||
|         // append bottom and top div to form: | ||||
|         this.form.append(topDiv, bottomDiv); | ||||
|  | ||||
|         //this.form.append(element, this.load, this.buttons.success, this.buttons.cancel); | ||||
|         //this.form.append(element, this.load, this.buttonGroup); | ||||
|         div.append(this.error, this.form); | ||||
|         return div; | ||||
|     } | ||||
|  | ||||
|     createButtonGroup() { | ||||
|         const div = document.createElement(`div`); | ||||
|         div.classList.add("btn-group", "btn-group-sm"); | ||||
|         return div; | ||||
|     } | ||||
|  | ||||
|     createContainerError() { | ||||
|         const div = document.createElement(`div`); | ||||
|         div.classList.add("text-danger", "fst-italic", "mb-2", "fw-bold"); | ||||
|         div.style.display = "none"; | ||||
|         return div; | ||||
|     } | ||||
|  | ||||
|     createContainerForm() { | ||||
|  | ||||
|  | ||||
|         const form = document.createElement(`form`); | ||||
|         form.classList.add("row", "row-cols-lg-auto", "g-3", "align-items-center"); | ||||
|         //form.style.gap = "20px"; | ||||
|         form.addEventListener('submit', async e => { | ||||
|             e.preventDefault(); | ||||
|             const newValue = this.getValue(); | ||||
|             if (this.context.send && this.context.pk && this.context.url && (this.context.value !== newValue)) { | ||||
|                 this.showLoad(); | ||||
|                 let msg; | ||||
|                 try { | ||||
|                     const response = await this.ajax(newValue); | ||||
|                     if (response.ok) { | ||||
|                         msg = await this.context.success(response, newValue); | ||||
|                     } else { | ||||
|                         msg = await this.context.error(response, newValue) || `${response.status} ${response.statusText}`; | ||||
|                     } | ||||
|                 } catch (error) { | ||||
|                     console.error(error); | ||||
|                     msg = error; | ||||
|                 } | ||||
|  | ||||
|                 if (msg) { | ||||
|                     this.setError(msg); | ||||
|                     this.showError(); | ||||
|                 } else { | ||||
|                     this.setError(null); | ||||
|                     this.hideError(); | ||||
|                     this.context.value = this.getValue(); | ||||
|                     this.context.modeElement.hide(); | ||||
|                     this.initText(); | ||||
|                 } | ||||
|                 this.hideLoad(); | ||||
|             } else { | ||||
|                 this.context.value = this.getValue(); | ||||
|                 this.context.modeElement.hide(); | ||||
|                 this.initText(); | ||||
|             } | ||||
|             this.context.element.dispatchEvent(new CustomEvent("save")); | ||||
|         }) | ||||
|         return form; | ||||
|     } | ||||
|  | ||||
|     createContainerLoad() { | ||||
|         const div = document.createElement(`div`); | ||||
|         div.style.display = "none"; | ||||
|         div.style.position = "absolute"; | ||||
|         div.style.background = "white"; | ||||
|         div.style.width = "100%"; | ||||
|         div.style.height = "100%"; | ||||
|         div.style.top = 0; | ||||
|         div.style.left = 0; | ||||
|         const loader = document.createElement(`div`); | ||||
|         loader.classList.add("dark-editable-loader"); | ||||
|         div.append(loader); | ||||
|         return div; | ||||
|     } | ||||
|  | ||||
|     createButton() { | ||||
|         const button = document.createElement("button"); | ||||
|         button.type = "button"; | ||||
|         button.classList.add("btn", "btn-sm"); | ||||
|         button.style.color = "transparent"; | ||||
|         button.style.textShadow = "0 0 0 white"; | ||||
|         return button; | ||||
|     } | ||||
|  | ||||
|     createButtonSuccess() { | ||||
|         const btn_success = this.createButton(); | ||||
|         btn_success.type = "submit"; | ||||
|         btn_success.classList.add("btn-success"); | ||||
|         btn_success.innerHTML = "✔"; | ||||
|         return btn_success; | ||||
|     } | ||||
|  | ||||
|     createButtonCancel() { | ||||
|         const btn_cancel = this.createButton(); | ||||
|         btn_cancel.classList.add("btn-danger"); | ||||
|         const div = document.createElement("div"); | ||||
|         div.innerHTML = "✖"; | ||||
|         btn_cancel.append(div); | ||||
|         btn_cancel.addEventListener("click", () => { | ||||
|             this.context.modeElement.hide(); | ||||
|         }); | ||||
|         return btn_cancel; | ||||
|     } | ||||
|  | ||||
|     hideLoad() { | ||||
|         this.load.style.display = "none"; | ||||
|     } | ||||
|  | ||||
|     showLoad() { | ||||
|         this.load.style.display = "block"; | ||||
|     } | ||||
|  | ||||
|     ajax(new_value) { | ||||
|         let url = this.context.url; | ||||
|         const form = new FormData; | ||||
|         form.append("pk", this.context.pk); | ||||
|         form.append("name", this.context.name); | ||||
|         form.append("value", new_value); | ||||
|         const option = {}; | ||||
|         option.method = this.context.ajaxOptions.method; | ||||
|         if (option.method === "POST") { | ||||
|             option.body = form; | ||||
|         } else { | ||||
|             url += "?" + new URLSearchParams(form).toString(); | ||||
|         } | ||||
|         return fetch(url, option); | ||||
|     } | ||||
|  | ||||
|     async successResponse(response, newValue) { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     async errorResponse(response, newValue) { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     setError(errorMsg) { | ||||
|         this.error.innerHTML = errorMsg; | ||||
|     } | ||||
|  | ||||
|     showError() { | ||||
|         this.error.style.display = "block"; | ||||
|     } | ||||
|  | ||||
|     hideError() { | ||||
|         if (this.error) { | ||||
|             this.error.style.display = "none"; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     createElement(name) { | ||||
|         const element = document.createElement(name); | ||||
|         console.log(element); | ||||
|         element.classList.add("form-control"); | ||||
|         if (this.context.required) { | ||||
|             element.required = this.context.required; | ||||
|         } | ||||
|         this.add_focus(element); | ||||
|         return element; | ||||
|     } | ||||
|  | ||||
|     add_focus(element) { | ||||
|         this.context.element.addEventListener('shown', function () { | ||||
|             element.focus(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     initText() { | ||||
|         if (this.context.value === "") { | ||||
|             this.context.element.innerHTML = this.context.emptytext; | ||||
|             return true; | ||||
|         } else { | ||||
|             this.context.element.innerHTML = this.context.value; | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     initOptions() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     getValue() { | ||||
|         return this.element.value; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								resources/assets/v2/libraries/dark-editable/Types/DateTimeType.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								resources/assets/v2/libraries/dark-editable/Types/DateTimeType.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import DateType from "./DateType.js"; | ||||
|  | ||||
| export default class DateTimeType extends DateType{ | ||||
|     create(){ | ||||
|         const input = this.createElement(`input`); | ||||
|         input.type = "datetime-local"; | ||||
|  | ||||
|         return this.createContainer(input); | ||||
|     } | ||||
|  | ||||
|     initOptions(){ | ||||
|         this.context.get_opt("format", "YYYY-MM-DD HH:mm"); | ||||
|         this.context.get_opt("viewformat", "YYYY-MM-DD HH:mm"); | ||||
|         this.context.value = moment(this.context.value).format("YYYY-MM-DDTHH:mm"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								resources/assets/v2/libraries/dark-editable/Types/DateType.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										25
									
								
								resources/assets/v2/libraries/dark-editable/Types/DateType.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import BaseType from "./BaseType.js"; | ||||
|  | ||||
| export default class DateType extends BaseType{ | ||||
|     create(){ | ||||
|         const input = this.createElement(`input`); | ||||
|         input.type = "date"; | ||||
|  | ||||
|         return this.createContainer(input); | ||||
|     } | ||||
|  | ||||
|     initText(){ | ||||
|         if(this.value === ""){ | ||||
|             this.context.element.innerHTML = this.context.emptytext; | ||||
|             return true; | ||||
|         } else { | ||||
|             this.context.element.innerHTML = moment(this.context.value).format(this.context.viewformat); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     initOptions(){ | ||||
|         this.context.get_opt("format", "YYYY-MM-DD"); | ||||
|         this.context.get_opt("viewformat", "YYYY-MM-DD"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								resources/assets/v2/libraries/dark-editable/Types/InputType.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								resources/assets/v2/libraries/dark-editable/Types/InputType.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import BaseType from "./BaseType.js"; | ||||
|  | ||||
| export default class InputType extends BaseType{ | ||||
|     create(){ | ||||
|         // expand input element with necessary classes and things. | ||||
|         // <input type="text" class="form-control form-control-md" id="inlineFormInputGroupUsername" placeholder="Username"> | ||||
|  | ||||
|  | ||||
|         const input = this.createElement(`input`); | ||||
|         const id = this.context.element.id + '_input'; | ||||
|         input.type = this.context.type; | ||||
|         input.id = id; | ||||
|         input.autocomplete = 'off'; | ||||
|         input.placeholder = this.context.element.innerText; | ||||
|         input.classList.add("form-control", "form-control-md"); | ||||
|  | ||||
|         return this.createContainer(input); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										36
									
								
								resources/assets/v2/libraries/dark-editable/Types/SelectType.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										36
									
								
								resources/assets/v2/libraries/dark-editable/Types/SelectType.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import BaseType from "./BaseType.js"; | ||||
|  | ||||
| export default class SelectType extends BaseType{ | ||||
|     create(){ | ||||
|         const select = this.createElement(`select`); | ||||
|         this.context.source.forEach(item => { | ||||
|             const opt = document.createElement(`option`); | ||||
|             opt.value = item.value; | ||||
|             opt.innerHTML = item.text; | ||||
|             select.append(opt); | ||||
|         }); | ||||
|  | ||||
|         return this.createContainer(select); | ||||
|     } | ||||
|  | ||||
|     initText(){ | ||||
|         this.context.element.innerHTML = this.context.emptytext; | ||||
|         if(this.context.value !== "" && this.context.source.length > 0){ | ||||
|             for(const key in this.context.source){ | ||||
|                 const item = this.context.source[ key ]; | ||||
|                 if(item.value == this.context.value){ | ||||
|                     this.context.element.innerHTML = item.text; | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     initOptions(){ | ||||
|         this.context.get_opt("source", []); | ||||
|         if(typeof this.context.source === "string" && this.context.source !== ""){ | ||||
|             this.context.source = JSON.parse(this.context.source); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										9
									
								
								resources/assets/v2/libraries/dark-editable/Types/TextAreaType.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								resources/assets/v2/libraries/dark-editable/Types/TextAreaType.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import BaseType from "./BaseType.js"; | ||||
|  | ||||
| export default class TextAreaType extends BaseType{ | ||||
|     create(){ | ||||
|         const textarea = this.createElement(`textarea`); | ||||
|  | ||||
|         return this.createContainer(textarea); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										87
									
								
								resources/assets/v2/libraries/dark-editable/dark-editable.css
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										87
									
								
								resources/assets/v2/libraries/dark-editable/dark-editable.css
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| .dark-editable-element{ | ||||
|     border-bottom: dashed 1px #0088cc; | ||||
|     text-decoration: none; | ||||
|     cursor: pointer; | ||||
| } | ||||
| .dark-editable-element-disabled{ | ||||
|     border-bottom: none; | ||||
|     cursor: default; | ||||
| } | ||||
| .dark-editable-element-empty{ | ||||
|   font-style: italic; | ||||
|   color: #DD1144; | ||||
| } | ||||
| .dark-editable{ | ||||
|     max-width: none; | ||||
| } | ||||
| 
 | ||||
| .dark-editable-loader { | ||||
|     font-size: 5px; | ||||
|     left: 50%; | ||||
|     top: 50%; | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
|     border-radius: 50%; | ||||
|     position: relative; | ||||
|     text-indent: -9999em; | ||||
|     -webkit-animation: load5 1.1s infinite ease; | ||||
|     animation: load5 1.1s infinite ease; | ||||
|     -webkit-transform: translateZ(0); | ||||
|     -ms-transform: translateZ(0); | ||||
|     transform: translateZ(0); | ||||
|   } | ||||
|   @-webkit-keyframes load5 { | ||||
|     0%, | ||||
|     100% { | ||||
|       box-shadow: 0em -2.6em 0em 0em #000000, 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.5), -1.8em -1.8em 0 0em rgba(0,0,0, 0.7); | ||||
|     } | ||||
|     12.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.7), 1.8em -1.8em 0 0em #000000, 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.5); | ||||
|     } | ||||
|     25% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.5), 1.8em -1.8em 0 0em rgba(0,0,0, 0.7), 2.5em 0em 0 0em #000000, 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     37.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.5), 2.5em 0em 0 0em rgba(0,0,0, 0.7), 1.75em 1.75em 0 0em #000000, 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     50% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.5), 1.75em 1.75em 0 0em rgba(0,0,0, 0.7), 0em 2.5em 0 0em #000000, -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     62.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.5), 0em 2.5em 0 0em rgba(0,0,0, 0.7), -1.8em 1.8em 0 0em #000000, -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     75% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.5), -1.8em 1.8em 0 0em rgba(0,0,0, 0.7), -2.6em 0em 0 0em #000000, -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     87.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.5), -2.6em 0em 0 0em rgba(0,0,0, 0.7), -1.8em -1.8em 0 0em #000000; | ||||
|     } | ||||
|   } | ||||
|   @keyframes load5 { | ||||
|     0%, | ||||
|     100% { | ||||
|       box-shadow: 0em -2.6em 0em 0em #000000, 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.5), -1.8em -1.8em 0 0em rgba(0,0,0, 0.7); | ||||
|     } | ||||
|     12.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.7), 1.8em -1.8em 0 0em #000000, 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.5); | ||||
|     } | ||||
|     25% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.5), 1.8em -1.8em 0 0em rgba(0,0,0, 0.7), 2.5em 0em 0 0em #000000, 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     37.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.5), 2.5em 0em 0 0em rgba(0,0,0, 0.7), 1.75em 1.75em 0 0em #000000, 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     50% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.5), 1.75em 1.75em 0 0em rgba(0,0,0, 0.7), 0em 2.5em 0 0em #000000, -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     62.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.5), 0em 2.5em 0 0em rgba(0,0,0, 0.7), -1.8em 1.8em 0 0em #000000, -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     75% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.5), -1.8em 1.8em 0 0em rgba(0,0,0, 0.7), -2.6em 0em 0 0em #000000, -1.8em -1.8em 0 0em rgba(0,0,0, 0.2); | ||||
|     } | ||||
|     87.5% { | ||||
|       box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.5), -2.6em 0em 0 0em rgba(0,0,0, 0.7), -1.8em -1.8em 0 0em #000000; | ||||
|     } | ||||
|   } | ||||
|    | ||||
							
								
								
									
										181
									
								
								resources/assets/v2/libraries/dark-editable/dark-editable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								resources/assets/v2/libraries/dark-editable/dark-editable.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| /* | ||||
|  * dark-editable.js | ||||
|  * Copyright (c) 2024 https://github.com/DarKsandr/dark-editable | ||||
|  * | ||||
|  * License: MIT | ||||
|  * | ||||
|  * Copied and slightly edited by James Cole <james@firefly-iii.org> | ||||
|  */ | ||||
|  | ||||
| import "./dark-editable.css"; | ||||
| import PopupMode from "./Modes/PopupMode.js"; | ||||
| import InlineMode from "./Modes/InlineMode.js"; | ||||
| import BaseType from "./Types/BaseType.js"; | ||||
| import InputType from "./Types/InputType.js"; | ||||
| import TextAreaType from "./Types/TextAreaType.js"; | ||||
| import SelectType from "./Types/SelectType.js"; | ||||
| import DateType from "./Types/DateType.js"; | ||||
| import DateTimeType from "./Types/DateTimeType.js"; | ||||
|  | ||||
| export default class DarkEditable{ | ||||
|     modeElement = null; | ||||
|     typeElement = null; | ||||
|     mode = null; | ||||
|     type = null; | ||||
|     emptytext = null; | ||||
|     viewformat = null; | ||||
|     pk = null; | ||||
|     name = null; | ||||
|  | ||||
|     constructor(element, options = {}){ | ||||
|         this.element = element; | ||||
|         this.options = options; | ||||
|  | ||||
|         this.init_options(); | ||||
|         this.typeElement = this.route_type(); | ||||
|         this.typeElement.initOptions(); | ||||
|         this.modeElement = this.route_mode(); | ||||
|         this.modeElement.init(); | ||||
|         this.init_text(); | ||||
|         this.init_style(); | ||||
|         if(this.disabled){ | ||||
|             this.disable(); | ||||
|         } | ||||
|         this.element.dispatchEvent(new CustomEvent("init")); | ||||
|     } | ||||
|  | ||||
|     /* INIT METHODS */ | ||||
|  | ||||
|     get_opt(name, default_value){ | ||||
|         return this[ name ] = this.element.dataset?.[ name ] ?? this.options?.[ name ] ?? default_value; | ||||
|     } | ||||
|     get_opt_bool(name, default_value){ | ||||
|         this.get_opt(name, default_value); | ||||
|         if(typeof this[ name ] !== "boolean"){ | ||||
|             if(this[ name ] === "true") { | ||||
|                 this[ name ] = true; | ||||
|             } else if(this[ name ] === "false") { | ||||
|                 this[ name ] = false; | ||||
|             } else { | ||||
|                 this[ name ] = default_value; | ||||
|             } | ||||
|         } | ||||
|         return this[ name ]; | ||||
|     } | ||||
|  | ||||
|     init_options(){ | ||||
|         //priority date elements | ||||
|         this.get_opt("value", this.element.innerHTML); | ||||
|         this.get_opt("name", this.element.id); | ||||
|         this.get_opt("pk", null); | ||||
|         this.get_opt("title", ""); | ||||
|         this.get_opt("type", "text"); | ||||
|         this.get_opt("emptytext", "Empty"); | ||||
|         this.get_opt("mode", "popup"); | ||||
|         this.get_opt("url", null); | ||||
|         this.get_opt("ajaxOptions", {}); | ||||
|         this.ajaxOptions = Object.assign({ | ||||
|             method: "POST", | ||||
|             dataType: "text", | ||||
|         }, this.ajaxOptions); | ||||
|         this.get_opt_bool("send", true); | ||||
|         this.get_opt_bool("disabled", false); | ||||
|         this.get_opt_bool("required", false); | ||||
|         if(this.options?.success && typeof this.options?.success == "function"){ | ||||
|             this.success = this.options.success; | ||||
|         } | ||||
|         if(this.options?.error && typeof this.options?.error == "function"){ | ||||
|             this.error = this.options.error; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     init_text(){ | ||||
|         const empty_class = "dark-editable-element-empty"; | ||||
|         this.element.classList.remove(empty_class); | ||||
|         if(this.typeElement.initText()){ | ||||
|             this.element.classList.add(empty_class); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     init_style(){ | ||||
|         this.element.classList.add("dark-editable-element"); | ||||
|     } | ||||
|  | ||||
|     /* INIT METHODS END */ | ||||
|     route_mode(){ | ||||
|         switch (this.mode){ | ||||
|             default: | ||||
|                 throw new Error(`Mode ${this.mode} not found!`) | ||||
|             case 'popup': | ||||
|                 return new PopupMode(this); | ||||
|             case 'inline': | ||||
|                 return new InlineMode(this); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     route_type(){ | ||||
|         if(this.type.prototype instanceof BaseType){ | ||||
|             return new this.type(this); | ||||
|         } | ||||
|         if(typeof this.type === 'string'){ | ||||
|             switch(this.type){ | ||||
|                 case "text": | ||||
|                 case "password": | ||||
|                 case "email": | ||||
|                 case "url": | ||||
|                 case "tel": | ||||
|                 case "number": | ||||
|                 case "range": | ||||
|                 case "time": | ||||
|                     return new InputType(this); | ||||
|                 case "textarea": | ||||
|                     return new TextAreaType(this); | ||||
|                 case "select": | ||||
|                     return new SelectType(this); | ||||
|                 case "date": | ||||
|                     return new DateType(this); | ||||
|                 case "datetime": | ||||
|                     return new DateTimeType(this); | ||||
|             } | ||||
|         } | ||||
|         throw new Error(`Undefined type`); | ||||
|     } | ||||
|  | ||||
|     /* AJAX */ | ||||
|  | ||||
|     async success(response, newValue){ | ||||
|         return await this.typeElement.successResponse(response, newValue); | ||||
|     } | ||||
|  | ||||
|     async error(response, newValue){ | ||||
|         return await this.typeElement.errorResponse(response, newValue); | ||||
|     } | ||||
|  | ||||
|     /* AJAX END */ | ||||
|  | ||||
|     /* METHODS */ | ||||
|  | ||||
|     enable(){ | ||||
|         this.disabled = false; | ||||
|         this.element.classList.remove("dark-editable-element-disabled"); | ||||
|         this.modeElement.enable(); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     disable(){ | ||||
|         this.disabled = true; | ||||
|         this.element.classList.add("dark-editable-element-disabled"); | ||||
|         this.modeElement.enable(); | ||||
|     } | ||||
|  | ||||
|     setValue(value){ | ||||
|         this.value = value; | ||||
|         this.init_text(); | ||||
|     } | ||||
|  | ||||
|     getValue(){ | ||||
|         return this.value; | ||||
|     } | ||||
|  | ||||
|     /* METHODS END */ | ||||
| } | ||||
| @@ -349,7 +349,7 @@ let transactions = function () { | ||||
|             this.detectTransactionType(); | ||||
|  | ||||
|             // parse transaction: | ||||
|             let transactions = parseFromEntries(this.entries, this.groupProperties.transactionType); | ||||
|             let transactions = parseFromEntries(this.entries, null, this.groupProperties.transactionType); | ||||
|             let submission = { | ||||
|                 group_title: this.groupProperties.title, | ||||
|                 fire_webhooks: this.formStates.webhooksButton, | ||||
| @@ -466,7 +466,7 @@ let transactions = function () { | ||||
|                     // onRenderItem: renderAccount, | ||||
|                     onChange: changeSourceAccount, | ||||
|                     onSelectItem: selectSourceAccount, | ||||
|                     hiddenValue: this.items[count].source_account.alpine_name, | ||||
|                     hiddenValue: this.entries[count].source_account.alpine_name | ||||
|                 }); | ||||
|                 addAutocomplete({ | ||||
|                     selector: 'input.ac-dest', | ||||
|   | ||||
| @@ -58,6 +58,7 @@ let transactions = function () { | ||||
|     return { | ||||
|         // transactions are stored in "entries": | ||||
|         entries: [], | ||||
|         originals: [], | ||||
|  | ||||
|         // state of the form is stored in formState: | ||||
|         formStates: { | ||||
| @@ -126,7 +127,7 @@ let transactions = function () { | ||||
|             this.formStates.isSubmitting = true; | ||||
|  | ||||
|             // parse transaction: | ||||
|             let transactions = parseFromEntries(this.entries, this.groupProperties.transactionType); | ||||
|             let transactions = parseFromEntries(this.entries, this.originals, this.groupProperties.transactionType); | ||||
|             let submission = { | ||||
|                 group_title: this.groupProperties.title, | ||||
|                 fire_webhooks: this.formStates.webhooksButton, | ||||
| @@ -263,7 +264,7 @@ let transactions = function () { | ||||
|             getter.show(groupId, {}).then((response) => { | ||||
|                 const data = response.data.data; | ||||
|                 this.groupProperties.id = parseInt(data.id); | ||||
|                 this.groupProperties.transactionType = data.attributes.transactions[0].type; | ||||
|                 this.groupProperties.transactionType = data.attributes.transactions[0].type.toLowerCase(); | ||||
|                 this.groupProperties.title = data.attributes.title ?? data.attributes.transactions[0].description; | ||||
|                 this.entries = parseDownloadedSplits(data.attributes.transactions); | ||||
|  | ||||
|   | ||||
| @@ -43,12 +43,14 @@ function addPointToMap(e) { | ||||
|         markers[index].on('dragend', dragEnd); | ||||
|         markers[index].addTo(maps[index]); | ||||
|  | ||||
|         const setEvent = new CustomEvent('location-set', {detail: { | ||||
|         const setEvent = new CustomEvent('location-set', { | ||||
|             detail: { | ||||
|                 latitude: e.latlng.lat, | ||||
|                 longitude: e.latlng.lng, | ||||
|                 index: index, | ||||
|                 zoomLevel: maps[index].getZoom() | ||||
|             }}); | ||||
|             } | ||||
|         }); | ||||
|         document.dispatchEvent(setEvent); | ||||
|     } | ||||
| } | ||||
| @@ -56,10 +58,12 @@ function addPointToMap(e) { | ||||
| function saveZoomOfMap(e) { | ||||
|     //let index = parseInt(e.sourceTarget._container.attributes['data-index'].value); | ||||
|     let index = 0; | ||||
|     const zoomEvent = new CustomEvent('location-zoom', {detail: { | ||||
|     const zoomEvent = new CustomEvent('location-zoom', { | ||||
|         detail: { | ||||
|             index: index, | ||||
|             zoomLevel: maps[index].getZoom() | ||||
|         }}); | ||||
|         } | ||||
|     }); | ||||
|     document.dispatchEvent(zoomEvent); | ||||
| } | ||||
|  | ||||
| @@ -87,13 +91,15 @@ export function addLocation(index) { | ||||
|  | ||||
|         //let holder = document.getElementById('location_map_' + index); | ||||
|         let holder = document.getElementById('location_map'); | ||||
|         maps[index] = L.map(holder).setView([holder.dataset.latitude, holder.dataset.longitude], holder.dataset.zoomLevel); | ||||
|         if (holder) { | ||||
|             maps[index] = L.map(holder).setView([holder.dataset.latitude, holder.dataset.longitude], holder.dataset.zoomLevel); | ||||
|  | ||||
|         L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { | ||||
|             maxZoom: 19, | ||||
|             attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' | ||||
|         }).addTo(maps[index]); | ||||
|         maps[index].on('click', addPointToMap); | ||||
|         maps[index].on('zoomend', saveZoomOfMap); | ||||
|             L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { | ||||
|                 maxZoom: 19, | ||||
|                 attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' | ||||
|             }).addTo(maps[index]); | ||||
|             maps[index].on('click', addPointToMap); | ||||
|             maps[index].on('zoomend', saveZoomOfMap); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -21,15 +21,23 @@ | ||||
| /** | ||||
|  * | ||||
|  */ | ||||
| export function parseFromEntries(entries, transactionType) { | ||||
| export function parseFromEntries(entries, originals, transactionType) { | ||||
|     let returnArray = []; | ||||
|     for (let i in entries) { | ||||
|         if (entries.hasOwnProperty(i)) { | ||||
|             const entry = entries[i]; | ||||
|             let compare = false; | ||||
|             let original = {}; | ||||
|             if (originals !== null && originals.hasOwnProperty(i)) { | ||||
|                 compare = true; | ||||
|                 let original = originals[i]; | ||||
|             } | ||||
|             let current = {}; | ||||
|  | ||||
|             // fields for transaction | ||||
|             current.description = entry.description; | ||||
|             if ((compare && original.description !== entry.description) || !compare) { | ||||
|                 current.description = entry.description; | ||||
|             } | ||||
|  | ||||
|             // source and destination | ||||
|             current.source_name = entry.source_account.name; | ||||
|   | ||||
| @@ -25,6 +25,8 @@ import Get from "../../api/v2/model/transaction/get.js"; | ||||
| import {parseDownloadedSplits} from "./shared/parse-downloaded-splits.js"; | ||||
| import {format} from "date-fns"; | ||||
| import formatMoney from "../../util/format-money.js"; | ||||
| import DarkEditable from "../../libraries/dark-editable/dark-editable.js"; | ||||
|  | ||||
|  | ||||
| let show = function () { | ||||
|     return { | ||||
| @@ -56,6 +58,10 @@ let show = function () { | ||||
|  | ||||
|         pageProperties: {}, | ||||
|         formatMoney(amount, currencyCode) { | ||||
|             console.log('formatting', amount, currencyCode); | ||||
|             if('' === currencyCode) { | ||||
|                 currencyCode = 'EUR'; | ||||
|             } | ||||
|             return formatMoney(amount, currencyCode); | ||||
|         }, | ||||
|         format(date) { | ||||
| @@ -95,6 +101,12 @@ let show = function () { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // at this point do the inline change fields | ||||
|                 //inlineEdit('journal_description') | ||||
|                 const usernameEl = document.getElementById('journal_description'); | ||||
|                 const popover = new DarkEditable(usernameEl, {mode: 'inline', url: '/something-else'}); | ||||
|  | ||||
|             }).catch((error) => { | ||||
|                 // todo auto generated. | ||||
|                 this.notifications.error.show = true; | ||||
|   | ||||
| @@ -45,6 +45,16 @@ | ||||
|                                 </tbody> | ||||
|                             </table> | ||||
|                         </div> | ||||
|                         <div class="card-footer text-end"> | ||||
|                             <div class="btn-group btn-group-sm"> | ||||
|                                 <a class="btn btn-primary" :href="'./transactions/edit/' + groupProperties.id"> | ||||
|                                     <em class="fa-solid fa-edit"></em> {{ __('firefly.edit') }} | ||||
|                                 </a> | ||||
|                                 <a class="btn btn-danger" :href="'./transactions/delete/' + groupProperties.id"> | ||||
|                                     <em class="fa-solid fa-trash"></em> {{ __('firefly.delete') }} | ||||
|                                 </a> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12"> | ||||
| @@ -123,10 +133,17 @@ | ||||
|             <div class="row"> | ||||
|                 <template x-for="(entry, index) in entries"> | ||||
|                     <div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12"> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|                         <div class="card mb-3"> | ||||
|                             <div class="card-header"> | ||||
|                                 <h3 class="card-title"> | ||||
|                                     <span x-text="entry.description"></span> | ||||
|                                     <span | ||||
|                                         id="journal_description" | ||||
|                                           data-type="text" | ||||
|                                           data-pk="12" | ||||
|                                           data-title="{{ __('firefly.description') }}" x-text="entry.description"></span> | ||||
|                                     <template x-if="entries.length > 1"> | ||||
|                                     <span class="badge bg-secondary"> | ||||
|                                         <span x-text="index + 1"></span> / <span x-text="entries.length"></span> | ||||
| @@ -215,7 +232,7 @@ | ||||
|                                         </template> | ||||
|                                     </template> | ||||
|                                     <template x-for="meta in metaFields"> | ||||
|                                         <template x-if="null !== entry[meta] and '' !== entry[meta]"> | ||||
|                                         <template x-if="typeof entry[meta] !== 'undefined' && null !== entry[meta] && '' !== entry[meta]"> | ||||
|                                             <tr> | ||||
|                                                 <th><span x-text="meta"></span></th> | ||||
|                                                 <td><span x-text="entry[meta]"></span></td> | ||||
| @@ -224,7 +241,7 @@ | ||||
|                                     </template> | ||||
|                                     <tr> | ||||
|                                         <th>recurring things</th> | ||||
|                                         <td>TODO meta</td> | ||||
|                                         <td>TODO recurring</td> | ||||
|                                     </tr> | ||||
|                                     <template x-if="entry.tags.length > 0"> | ||||
|                                         <tr> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user