From d5457d39793c5b850c3a8ef7d93799926a3dae26 Mon Sep 17 00:00:00 2001 From: Wirachot Date: Mon, 9 Feb 2026 11:50:32 +0700 Subject: [PATCH 1/4] migate text plugin --- AppBuilder/platform/plugins/included/index.js | 3 +- .../included/view_text/FNAbviewtext.js | 255 ++++++++++++++++++ .../view_text/FNAbviewtextComponent.js | 78 ++++++ 3 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js create mode 100644 AppBuilder/platform/plugins/included/view_text/FNAbviewtextComponent.js diff --git a/AppBuilder/platform/plugins/included/index.js b/AppBuilder/platform/plugins/included/index.js index 1d707c93..ddd1413c 100644 --- a/AppBuilder/platform/plugins/included/index.js +++ b/AppBuilder/platform/plugins/included/index.js @@ -1,7 +1,8 @@ +import viewText from "./view_text/FNAbviewtext.js"; import viewList from "./view_list/FNAbviewlist.js"; import viewTab from "./view_tab/FNAbviewtab.js"; -const AllPlugins = [viewTab, viewList]; +const AllPlugins = [viewTab, viewList, viewText]; export default { load: (AB) => { diff --git a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js new file mode 100644 index 00000000..b9f202bf --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js @@ -0,0 +1,255 @@ +import FNAbviewtextComponent from "./FNAbviewtextComponent.js"; + + +// FNAbviewtext Web +// A web side import for an ABView. +// +export default function FNAbviewtext({ + /*AB,*/ + ABViewWidgetPlugin, + ABViewComponentPlugin, + ABViewContainer +}) { + const ABAbviewtextComponent = FNAbviewtextComponent({ ABViewComponentPlugin }); + +const ABViewTextPropertyComponentDefaults = { + text: "", + // {string} + // A multilingual text template that is used to display a given set of + // values. + + height: 0, + // {integer} + // The default height of this widget. + + dataviewID: null, + // {uuid} + // The {ABDataCollection.id} of the datacollection this ABViewText is + // pulling data from. + // In most usage situations this ABView is tied to the data in an + // ABDataCollection. However, it is possible for an ABObject to be + // directly assigned to the ABView, and that will be used instead. +}; + +const ABViewDefaults = { + key: "text", + // {string} + // unique key for this view + + icon: "font", + // {string} + // fa-[icon] reference for this view + + labelKey: "Text", + // {string} + // the multilingual label key for the class label +}; + +class ABViewTextCore extends ABViewWidgetPlugin { + constructor(values, application, parent, defaultValues) { + super(values, application, parent, defaultValues || ABViewDefaults); + + this._object = null; + } + + static common() { + return ABViewDefaults; + } + + static defaultValues() { + return ABViewTextPropertyComponentDefaults; + } + + /// + /// Instance Methods + /// + + /** + * @method toObj() + * + * properly compile the current state of this ABViewLabel instance + * into the values needed for saving. + * + * @return {json} + */ + toObj() { + // NOTE: ABView auto translates/untranslates "label" + // add in any additional fields here: + this.unTranslate(this, this, ["text"]); + + var obj = super.toObj(); + obj.views = []; + return obj; + } + + /** + * @method fromValues() + * + * initialze this object with the given set of values. + * @param {obj} values + */ + fromValues(values) { + super.fromValues(values); + + this.settings = this.settings || {}; + + // convert from "0" => 0 + this.settings.height = parseInt( + this.settings.height || ABViewTextPropertyComponentDefaults.height + ); + + // if this is being instantiated on a read from the Property UI, + this.text = values.text || ABViewTextPropertyComponentDefaults.text; + + // NOTE: ABView auto translates/untranslates "label" + // add in any additional fields here: + this.translate(this, this, ["text"]); + } + + /** + * @method componentList + * return the list of components available on this view to display in the editor. + */ + componentList() { + return []; + } + + /** + * @property datacollection + * return ABDatacollection of this form + * + * @return {ABDatacollection} + */ + get datacollection() { + if (this.parent?.key == "dataview") { + return this.AB.datacollectionByID(this.parent.settings.dataviewID); + } else { + return this.AB.datacollectionByID(this.settings.dataviewID); + } + } + + fieldKey(field) { + let label = field.label || ""; + label = label.replace(/\(/g, "\\("); + label = label.replace(/\)/g, "\\)"); + return label; + } + + displayText(val, componentID) { + var result = this.text; + + let clearTemplateValue = (result) => { + return result.replace(/{(.*?)}/g, ""); + }; + + var dv = this.datacollection; + // if (!dv) return clearTemplateValue(result); + + var object = dv?.datasource ?? this._object; + if (!object) return clearTemplateValue(result); + + const rowData = val || dv.getCursor() || {}; + + object.fields().forEach((f) => { + // add \\ in front of the regular expression special charactors + // let label = f.label || ""; + // label = label.replace(/\(/g, "\\("); + // label = label.replace(/\)/g, "\\)"); + let label = this.fieldKey(f); + + var template = new RegExp("{" + label + "}", "g"); + + // IDEA: I'd like to keep all the image url logic INSIDE the ABFieldImage + // object. Is there some way we can simply call: f.imageTemplate(rowData) + // and parse the results for the url to display here? + + var data = f.format(rowData); + if (f.key == "image") { + var fData = data; + data = f.urlImage(fData); + + // Question: should we change f.urlImage() to return the defaultImageUrl + // if fData is "" and .useDefaultImage = true? + + if ( + !fData && + f.settings.defaultImageUrl && + f.settings.useDefaultImage + ) { + data = f.urlImage(f.settings.defaultImageUrl); + + //// + //// James: Revisit this and make sure we are handling things ok now. + // result = result.replace( + // "img", + // 'img onload=\'AD.comm.hub.publish("component.adjust", {"containerID": "' + + // componentID + + // "\"});' " + // ); + // } else if ( + // fData != "" && + // result.indexOf("onload") == -1 && + // componentID + // ) { + // result = result.replace( + // "img", + // 'img onload=\'AD.comm.hub.publish("component.adjust", {"containerID": "' + + // componentID + + // "\"});' " + // ); + } else { + //// + //// James: It looks like this routine assumes the this.text template will + //// only have 1 tag in it. Is that necessarilly true? + //// + //// If NOT, then we need to rethink this next line: + + result = result.replace( + "img", + "img onerror='this.parentNode.removeChild(this);' " + ); + } + } + + result = result.replace(template, data); + }); + + // Support {uuid} tag in tempalte + result = result.replace(/{PK}/g, rowData[object.PK()]); + + return result; + } + + objectLoad(object) { + this._object = object; + } +}; + + + +return class ABViewText extends ABViewTextCore { + +/** + * @method getPluginKey + * return the plugin key for this view. + * @return {string} plugin key + */ + static getPluginKey() { + return this.common().key; + } + +/** + * @method component() + * return a UI component based upon this view. + * @return {obj} UI component + */ + component(parentId) { + return new ABAbviewtextComponent(this, parentId); + } + + + +}; + +} + diff --git a/AppBuilder/platform/plugins/included/view_text/FNAbviewtextComponent.js b/AppBuilder/platform/plugins/included/view_text/FNAbviewtextComponent.js new file mode 100644 index 00000000..c35ec74e --- /dev/null +++ b/AppBuilder/platform/plugins/included/view_text/FNAbviewtextComponent.js @@ -0,0 +1,78 @@ +export default function FNAbviewtextComponent({ + /*AB,*/ + ABViewComponentPlugin, +}) { + return class ABAbviewtextComponent extends ABViewComponentPlugin { + + + constructor(baseView, idBase, ids) { + super( + baseView, + idBase || `ABViewText_${baseView.id}`, + Object.assign( + { + text: "", + }, + ids + ) + ); + } + + ui() { + const ids = this.ids; + const settings = this.settings; + + const _uiText = { + id: ids.text, + view: "template", + minHeight: 10, + css: "ab-custom-template", + borderless: true, + }; + + if (settings.height) _uiText.height = settings.height; + else _uiText.autoheight = true; + + const _ui = super.ui([_uiText]); + + delete _ui.type; + + return _ui; + } + + displayText(value) { + const ids = this.ids; + const result = this.view.displayText(value, ids.text); + + const $text = $$(ids.text); + + if (!$text) return; + + $text.define("template", result); + $text.refresh(); + } + + onShow() { + super.onShow(); + + // listen DC events + const dataview = this.datacollection; + const baseView = this.view; + + if (dataview && baseView.parent.key !== "dataview") { + ["changeCursor", "cursorStale"].forEach((key) => { + baseView.eventAdd({ + emitter: dataview, + eventName: key, + listener: (...p) => this.displayText(...p), + }); + }); + } + + this.displayText(); + } + + + }; + +} From f7621b9e3d3f417e0675ffbf9897241b942905aa Mon Sep 17 00:00:00 2001 From: benthongtiang Date: Mon, 9 Feb 2026 14:52:48 +0700 Subject: [PATCH 2/4] Potential fix for code scanning alert no. 633: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js index b9f202bf..d472db46 100644 --- a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js +++ b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js @@ -130,6 +130,8 @@ class ABViewTextCore extends ABViewWidgetPlugin { fieldKey(field) { let label = field.label || ""; + // First escape backslashes, then escape parentheses + label = label.replace(/\\/g, "\\\\"); label = label.replace(/\(/g, "\\("); label = label.replace(/\)/g, "\\)"); return label; From d670642820b9610a28c3c48285544b18dbff45de Mon Sep 17 00:00:00 2001 From: benthongtiang Date: Mon, 9 Feb 2026 14:52:59 +0700 Subject: [PATCH 3/4] Potential fix for code scanning alert no. 634: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js index d472db46..9cfaecd5 100644 --- a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js +++ b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js @@ -130,6 +130,8 @@ class ABViewTextCore extends ABViewWidgetPlugin { fieldKey(field) { let label = field.label || ""; + // First escape backslashes to avoid leaving metacharacters unescaped + label = label.replace(/\\/g, "\\\\"); // First escape backslashes, then escape parentheses label = label.replace(/\\/g, "\\\\"); label = label.replace(/\(/g, "\\("); From c781f35201ea1da1c06f6b0068ed9775f91bbfee Mon Sep 17 00:00:00 2001 From: benthongtiang Date: Mon, 9 Feb 2026 15:08:26 +0700 Subject: [PATCH 4/4] Potential fix for code scanning alert no. 635: Double escaping or unescaping Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js index 9cfaecd5..373bd679 100644 --- a/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js +++ b/AppBuilder/platform/plugins/included/view_text/FNAbviewtext.js @@ -132,8 +132,7 @@ class ABViewTextCore extends ABViewWidgetPlugin { let label = field.label || ""; // First escape backslashes to avoid leaving metacharacters unescaped label = label.replace(/\\/g, "\\\\"); - // First escape backslashes, then escape parentheses - label = label.replace(/\\/g, "\\\\"); + // Then escape parentheses label = label.replace(/\(/g, "\\("); label = label.replace(/\)/g, "\\)"); return label;