From 0b1fc8e574de16e280a6dd00ab5dc4477a7c8ee6 Mon Sep 17 00:00:00 2001 From: Xavier Cazin Date: Wed, 31 Aug 2022 18:32:55 +0200 Subject: [PATCH 1/4] Make dialogs over deleting AdvancedSearch filter results translatable (#6933) * Add fr-FR strings over deleting AvancedSearch filter results * Add default strings over deleting AvancedSearch filter results * Make dialogs over deleting results from AdvancedSearch filters translatable --- core/language/en-GB/Buttons.multids | 2 ++ core/language/en-GB/Misc.multids | 1 + core/ui/AdvancedSearch/FilterButtons/delete.tid | 6 +++--- languages/fr-FR/Buttons.multids | 2 ++ languages/fr-FR/Misc.multids | 5 +++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/language/en-GB/Buttons.multids b/core/language/en-GB/Buttons.multids index 83598f410..7cf1c9955 100644 --- a/core/language/en-GB/Buttons.multids +++ b/core/language/en-GB/Buttons.multids @@ -18,6 +18,8 @@ CopyToClipboard/Caption: copy to clipboard CopyToClipboard/Hint: Copy this text to the clipboard Delete/Caption: delete Delete/Hint: Delete this tiddler +DeleteTiddlers/Caption: delete tiddlers +DeleteTiddlers/Hint: Delete tiddlers Edit/Caption: edit Edit/Hint: Edit this tiddler Encryption/Caption: encryption diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids index b677494e6..00cb3c99c 100644 --- a/core/language/en-GB/Misc.multids +++ b/core/language/en-GB/Misc.multids @@ -8,6 +8,7 @@ CloseAll/Button: close all ColourPicker/Recent: Recent: ConfirmCancelTiddler: Do you wish to discard changes to the tiddler "<$text text=<>/>"? ConfirmDeleteTiddler: Do you wish to delete the tiddler "<$text text=<<title>>/>"? +ConfirmDeleteTiddlers: Are you sure you wish to delete <<resultCount>> tiddler(s)? ConfirmOverwriteTiddler: Do you wish to overwrite the tiddler "<$text text=<<title>>/>"? ConfirmEditShadowTiddler: You are about to edit a ShadowTiddler. Any changes will override the default system making future upgrades non-trivial. Are you sure you want to edit "<$text text=<<title>>/>"? ConfirmAction: Do you wish to proceed? diff --git a/core/ui/AdvancedSearch/FilterButtons/delete.tid b/core/ui/AdvancedSearch/FilterButtons/delete.tid index 85d9b224e..8d3069b57 100644 --- a/core/ui/AdvancedSearch/FilterButtons/delete.tid +++ b/core/ui/AdvancedSearch/FilterButtons/delete.tid @@ -3,7 +3,7 @@ tags: $:/tags/AdvancedSearch/FilterButton \whitespace trim <$reveal state="$:/temp/advancedsearch" type="nomatch" text=""> -<$button popup=<<qualify "$:/state/filterDeleteDropdown">> class="tc-btn-invisible"> +<$button tooltip={{$:/language/Buttons/DeleteTiddlers/Hint}} popup=<<qualify "$:/state/filterDeleteDropdown">> class="tc-btn-invisible"> {{$:/core/images/delete-button}} </$button> </$reveal> @@ -13,13 +13,13 @@ tags: $:/tags/AdvancedSearch/FilterButton <div class="tc-block-dropdown tc-edit-type-dropdown"> <div class="tc-dropdown-item-plain"> <$set name="resultCount" value="""<$count filter={{$:/temp/advancedsearch}}/>"""> -Are you sure you wish to delete <<resultCount>> tiddler(s)? +{{$:/language/ConfirmDeleteTiddlers}} </$set> </div> <div class="tc-dropdown-item-plain"> <$button class="tc-btn"> <$action-deletetiddler $filter={{$:/temp/advancedsearch}}/> -Delete these tiddlers +{{$:/language/Buttons/DeleteTiddlers/Hint}} </$button> </div> </div> diff --git a/languages/fr-FR/Buttons.multids b/languages/fr-FR/Buttons.multids index 058cf8041..426a4c1fb 100644 --- a/languages/fr-FR/Buttons.multids +++ b/languages/fr-FR/Buttons.multids @@ -18,6 +18,8 @@ CopyToClipboard/Caption: copier dans le presse-papier CopyToClipboard/Hint: Copie ce texte dans le presse-papier Delete/Caption: supprimer Delete/Hint: Supprime ce tiddler +DeleteTiddlers/Caption: supprimer les tiddlers +DeleteTiddlers/Hint: Supprime ces tiddlers Edit/Caption: éditer Edit/Hint: Édite ce tiddler Encryption/Caption: chiffrement diff --git a/languages/fr-FR/Misc.multids b/languages/fr-FR/Misc.multids index 2d9c66655..1c0be807f 100644 --- a/languages/fr-FR/Misc.multids +++ b/languages/fr-FR/Misc.multids @@ -7,8 +7,9 @@ ClassicWarning/Upgrade/Caption: mettre à jour CloseAll/Button: tout fermer ColourPicker/Recent: Récent : ConfirmCancelTiddler: Souhaitez-vous annuler les modifications apportées au tiddler « <$text text=<<title>>/> » ? -ConfirmDeleteTiddler: Souhaitez-vous supprimer le tiddler « <$text text=<<title>>/> » ? -ConfirmOverwriteTiddler: Souhaitez-vous supplanter le tiddler « <$text text=<<title>>/> » ? +ConfirmDeleteTiddler: Souhaitez-vous supprimer le tiddler « <$text text=<<title>>/> » ? +ConfirmDeleteTiddlers: Êtes-vous sûr•e de vouloir supprimer <<resultCount>> tiddler(s) ? +ConfirmOverwriteTiddler: Souhaitez-vous supplanter le tiddler « <$text text=<<title>>/> » ? ConfirmEditShadowTiddler: Vous êtes sur le point d'éditer un ShadowTiddler. Toute modification supplantera la version par défaut du système, rendant les prochaines mises à jour non-triviales. Êtes-vous sûr(e) de vouloir éditer "<$text text=<<title>>/>"? ConfirmAction: Souhaitez-vous poursuivre ? Count: total From d62a16ee464fb9984b766b48504829a1a3eb143b Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" <jeremy@jermolene.com> Date: Wed, 31 Aug 2022 17:44:31 +0100 Subject: [PATCH 2/4] iPhone/iPad: Prevent long presses on tiddlylinks from triggering a preview --- themes/tiddlywiki/vanilla/base.tid | 1 + 1 file changed, 1 insertion(+) diff --git a/themes/tiddlywiki/vanilla/base.tid b/themes/tiddlywiki/vanilla/base.tid index cbb8b1784..9c4a342ff 100644 --- a/themes/tiddlywiki/vanilla/base.tid +++ b/themes/tiddlywiki/vanilla/base.tid @@ -397,6 +397,7 @@ a.tc-tiddlylink { font-weight: 500; color: <<colour tiddler-link-foreground>>; -webkit-user-select: inherit; /* Otherwise the draggable attribute makes links impossible to select */ + -webkit-touch-callout: none; /* Prevents long presses from bringing up a link preview */ } .tc-sidebar-lists a.tc-tiddlylink { From 7652aa5fed0f6fdef59c13239789136dfc1165ad Mon Sep 17 00:00:00 2001 From: Bram Chen <bram.chen@gmail.com> Date: Thu, 1 Sep 2022 15:03:37 +0800 Subject: [PATCH 3/4] Update chinese translations (#6935) * Add Hint and Caption of the delete button in the AdvancedSearch filter results * Add confirm messages for the above deleting action --- languages/zh-Hans/Buttons.multids | 2 ++ languages/zh-Hans/Misc.multids | 1 + languages/zh-Hant/Buttons.multids | 2 ++ languages/zh-Hant/Misc.multids | 1 + 4 files changed, 6 insertions(+) diff --git a/languages/zh-Hans/Buttons.multids b/languages/zh-Hans/Buttons.multids index 380bba3eb..6feef4e92 100644 --- a/languages/zh-Hans/Buttons.multids +++ b/languages/zh-Hans/Buttons.multids @@ -18,6 +18,8 @@ CopyToClipboard/Caption: 复制到剪贴板 CopyToClipboard/Hint: 将此文本复制到剪贴板 Delete/Caption: 删除 Delete/Hint: 删除此条目 +DeleteTiddlers/Caption: 删除条目 +DeleteTiddlers/Hint: 删除条目 Edit/Caption: 编辑 Edit/Hint: 编辑此条目 Encryption/Caption: 加密 diff --git a/languages/zh-Hans/Misc.multids b/languages/zh-Hans/Misc.multids index 7b8edb4b8..567ddfaee 100644 --- a/languages/zh-Hans/Misc.multids +++ b/languages/zh-Hans/Misc.multids @@ -8,6 +8,7 @@ CloseAll/Button: 全部关闭 ColourPicker/Recent: 最近︰ ConfirmCancelTiddler: 您确定要放弃对条目 "<$text text=<<title>>/>" 的更改? ConfirmDeleteTiddler: 您确定要删除条目 "<$text text=<<title>>/>"? +ConfirmDeleteTiddlers: 您确定要删除 <<resultCount>> 个条目? ConfirmOverwriteTiddler: 您确定要复写条目 "<$text text=<<title>>/>"? ConfirmEditShadowTiddler: 您即将要编辑默认条目,任何更改将会复盖默认的系统,使未来的升级不寻常。您确定要编辑 "<$text text=<<title>>/>"? ConfirmAction: 是否要继续? diff --git a/languages/zh-Hant/Buttons.multids b/languages/zh-Hant/Buttons.multids index aa94f7013..2a170ed8e 100644 --- a/languages/zh-Hant/Buttons.multids +++ b/languages/zh-Hant/Buttons.multids @@ -18,6 +18,8 @@ CopyToClipboard/Caption: 複製到剪貼簿 CopyToClipboard/Hint: 將此文字複製到剪貼簿 Delete/Caption: 刪除 Delete/Hint: 刪除此條目 +DeleteTiddlers/Caption: 刪除條目 +DeleteTiddlers/Hint: 刪除條目 Edit/Caption: 編輯 Edit/Hint: 編輯此條目 Encryption/Caption: 加密 diff --git a/languages/zh-Hant/Misc.multids b/languages/zh-Hant/Misc.multids index 3b67b65ba..0269aa7dd 100644 --- a/languages/zh-Hant/Misc.multids +++ b/languages/zh-Hant/Misc.multids @@ -8,6 +8,7 @@ CloseAll/Button: 全部關閉 ColourPicker/Recent: 最近︰ ConfirmCancelTiddler: 您確定要放棄對條目 "<$text text=<<title>>/>" 的更改? ConfirmDeleteTiddler: 您確定要刪除條目 "<$text text=<<title>>/>"? +ConfirmDeleteTiddlers: 您確定要刪除 <<resultCount>> 個條目? ConfirmOverwriteTiddler: 您確定要覆寫條目 "<$text text=<<title>>/>"? ConfirmEditShadowTiddler: 您即將要編輯預設條目,任何更改將會覆蓋預設的系統,使未來的升級不尋常。您確定要編輯 "<$text text=<<title>>/>"? ConfirmAction: 是否要繼續? From 35b9faaa8975fccaf8f68da01ee3a7f870df33ee Mon Sep 17 00:00:00 2001 From: Jeremy Ruston <jeremy@jermolene.com> Date: Fri, 2 Sep 2022 18:15:45 +0100 Subject: [PATCH 4/4] JSON Filter Operators (Revised Attempt) (#6936) * First commit Cherry-picked from #6666 * Adjust release version number Just in case we decide to make a release before we merge #6666 --- core/modules/filters/format/json.js | 35 ++++ core/modules/filters/json-ops.js | 153 ++++++++++++++++++ .../test/tiddlers/tests/test-json-filters.js | 95 +++++++++++ .../examples/format Operator (Examples).tid | 7 +- editions/tw5.com/tiddlers/filters/format.tid | 9 +- editions/tw5.com/tiddlers/filters/jsonget.tid | 86 ++++++++++ .../tw5.com/tiddlers/filters/jsonindexes.tid | 64 ++++++++ .../tw5.com/tiddlers/filters/jsontype.tid | 73 +++++++++ .../internals/editpreviews/parse-tree.tid | 122 +++++++++++++- 9 files changed, 631 insertions(+), 13 deletions(-) create mode 100644 core/modules/filters/format/json.js create mode 100644 core/modules/filters/json-ops.js create mode 100644 editions/test/tiddlers/tests/test-json-filters.js create mode 100644 editions/tw5.com/tiddlers/filters/jsonget.tid create mode 100644 editions/tw5.com/tiddlers/filters/jsonindexes.tid create mode 100644 editions/tw5.com/tiddlers/filters/jsontype.tid diff --git a/core/modules/filters/format/json.js b/core/modules/filters/format/json.js new file mode 100644 index 000000000..2130a76ed --- /dev/null +++ b/core/modules/filters/format/json.js @@ -0,0 +1,35 @@ +/*\ +title: $:/core/modules/filters/format/json.js +type: application/javascript +module-type: formatfilteroperator +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.json = function(source,operand,options) { + var results = [], + spaces = null; + if(operand) { + spaces = /^\d+$/.test(operand) ? parseInt(operand,10) : operand; + } + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title); + try { + data = JSON.parse(title); + } catch(e) { + data = undefined; + } + if(data !== undefined) { + results.push(JSON.stringify(data,null,spaces)); + } + }); + return results; +}; + +})(); \ No newline at end of file diff --git a/core/modules/filters/json-ops.js b/core/modules/filters/json-ops.js new file mode 100644 index 000000000..d5e8c33af --- /dev/null +++ b/core/modules/filters/json-ops.js @@ -0,0 +1,153 @@ +/*\ +title: $:/core/modules/filters/json-ops.js +type: application/javascript +module-type: filteroperator + +Filter operators for JSON operations + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports["jsonget"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,title); + if(data) { + var item = getDataItemValueAsString(data,operator.operands); + if(item !== undefined) { + results.push(item); + } + } + }); + return results; +}; + +exports["jsonindexes"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,title); + if(data) { + var item = getDataItemKeysAsStrings(data,operator.operands); + if(item !== undefined) { + results.push.apply(results,item); + } + } + }); + return results; +}; + +exports["jsontype"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,title); + if(data) { + var item = getDataItemType(data,operator.operands); + if(item !== undefined) { + results.push(item); + } + } + }); + return results; +}; + +/* +Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid +*/ +function getDataItemValueAsString(data,indexes) { + // Get the item + var item = getDataItem(data,indexes); + // Return the item as a string + return convertDataItemValueToString(item); +} + +/* +Given a JSON data structure and an array of index strings, return an array of the string representation of the keys of the item at the end of the index chain, or "undefined" if any of the index strings are invalid +*/ +function getDataItemKeysAsStrings(data,indexes) { + // Get the item + var item = getDataItem(data,indexes); + // Return the item keys as a string + return convertDataItemKeysToStrings(item); +} + +/* +Return an array of the string representation of the values of a data item, or "undefined" if the item is undefined +*/ +function convertDataItemValueToString(item) { + // Return the item as a string + if(item === undefined) { + return item; + } + if(typeof item === "object") { + return JSON.stringify(item); + } + return item.toString(); +} + +/* +Return an array of the string representation of the keys of a data item, or "undefined" if the item is undefined +*/ +function convertDataItemKeysToStrings(item) { + // Return the item as a string + if(item === undefined) { + return item; + } else if(typeof item === "object") { + if(item === null) { + return []; + } + var results = []; + if($tw.utils.isArray(item)) { + for(var i=0; i<item.length; i++) { + results.push(i.toString()); + } + return results; + } else { + $tw.utils.each(Object.keys(item).sort(),function(key) { + results.push(key); + }); + return results; + } + } + return []; +} + +function getDataItemType(data,indexes) { + // Get the item + var item = getDataItem(data,indexes); + // Return the item type + if(item === undefined) { + return item; + } else if(item === null) { + return "null"; + } else if($tw.utils.isArray(item)) { + return "array"; + } else if(typeof item === "object") { + return "object"; + } else { + return typeof item; + } +} + +/* +Given a JSON data structure and an array of index strings, return the value at the end of the index chain, or "undefined" if any of the index strings are invalid +*/ +function getDataItem(data,indexes) { + if(indexes.length === 0 || (indexes.length === 1 && indexes[0] === "")) { + return data; + } + // Get the item + var item = data; + for(var i=0; i<indexes.length; i++) { + if(item !== undefined) { + item = item[indexes[i]]; + } + } + return item; +} + +})(); + \ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-json-filters.js b/editions/test/tiddlers/tests/test-json-filters.js new file mode 100644 index 000000000..c892c2419 --- /dev/null +++ b/editions/test/tiddlers/tests/test-json-filters.js @@ -0,0 +1,95 @@ +/*\ +title: test-json-filters.js +type: application/javascript +tags: [[$:/tags/test-spec]] + +Tests the JSON filters and the format:json operator + +\*/ +(function(){ + +/* jslint node: true, browser: true */ +/* eslint-env node, browser, jasmine */ +/* eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/ +/* global $tw, require */ +"use strict"; + +describe("json filter tests", function() { + + var wiki = new $tw.Wiki(); + var tiddlers = [{ + title: "First", + text: '{"a":"one","b":"","c":1.618,"d": {"e": "four","f": ["five","six",true,false,null]}}', + type: "application/json" + },{ + title: "Second", + text: '["une","deux","trois"]', + type: "application/json" + },{ + title: "Third", + text: "This is not JSON", + type: "text/vnd.tiddlywiki" + }]; + wiki.addTiddlers(tiddlers); + + it("should support the getindex operator", function() { + expect(wiki.filterTiddlers("[{First}getindex[b]]")).toEqual([]); + }); + + it("should support the jsonget operator", function() { + expect(wiki.filterTiddlers("[{Third}jsonget[]]")).toEqual(["This is not JSON"]); + expect(wiki.filterTiddlers("[{First}jsonget[]]")).toEqual(['{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}']); + expect(wiki.filterTiddlers("[{First}jsonget[a]]")).toEqual(["one"]); + expect(wiki.filterTiddlers("[{First}jsonget[b]]")).toEqual([""]); + expect(wiki.filterTiddlers("[{First}jsonget[missing-property]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonget[d]]")).toEqual(['{"e":"four","f":["five","six",true,false,null]}']); + expect(wiki.filterTiddlers("[{First}jsonget[d]jsonget[f]]")).toEqual(['["five","six",true,false,null]']); + expect(wiki.filterTiddlers("[{First}jsonget[d],[e]]")).toEqual(["four"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f]]")).toEqual(['["five","six",true,false,null]']); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[0]]")).toEqual(["five"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[1]]")).toEqual(["six"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[2]]")).toEqual(["true"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[3]]")).toEqual(["false"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[4]]")).toEqual(["null"]); + }); + + it("should support the jsonindexes operator", function() { + expect(wiki.filterTiddlers("[{Second}jsonindexes[]]")).toEqual(["0","1","2"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[]]")).toEqual(["a","b","c","d"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[a]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[b]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d]]")).toEqual(["e","f"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[e]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f]]")).toEqual(["0","1","2","3","4"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[0]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[1]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[2]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[3]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[4]]")).toEqual([]); + }); + + it("should support the jsontype operator", function() { + expect(wiki.filterTiddlers("[{Third}jsontype[]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[]]")).toEqual(["object"]); + expect(wiki.filterTiddlers("[{First}jsontype[a]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[b]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[c]]")).toEqual(["number"]); + expect(wiki.filterTiddlers("[{First}jsontype[d]]")).toEqual(["object"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[e]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f]]")).toEqual(["array"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[0]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[1]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[2]]")).toEqual(["boolean"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[3]]")).toEqual(["boolean"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[4]]")).toEqual(["null"]); + }); + + it("should support the format:json operator", function() { + expect(wiki.filterTiddlers("[{First}format:json[]]")).toEqual(["{\"a\":\"one\",\"b\":\"\",\"c\":1.618,\"d\":{\"e\":\"four\",\"f\":[\"five\",\"six\",true,false,null]}}"]); + expect(wiki.filterTiddlers("[{First}format:json[4]]")).toEqual(["{\n \"a\": \"one\",\n \"b\": \"\",\n \"c\": 1.618,\n \"d\": {\n \"e\": \"four\",\n \"f\": [\n \"five\",\n \"six\",\n true,\n false,\n null\n ]\n }\n}"]); + expect(wiki.filterTiddlers("[{First}format:json[ ]]")).toEqual(["{\n \"a\": \"one\",\n \"b\": \"\",\n \"c\": 1.618,\n \"d\": {\n \"e\": \"four\",\n \"f\": [\n \"five\",\n \"six\",\n true,\n false,\n null\n ]\n }\n}"]); + }); + +}); + +})(); diff --git a/editions/tw5.com/tiddlers/filters/examples/format Operator (Examples).tid b/editions/tw5.com/tiddlers/filters/examples/format Operator (Examples).tid index d7bbc1271..c1d79ea8c 100644 --- a/editions/tw5.com/tiddlers/filters/examples/format Operator (Examples).tid +++ b/editions/tw5.com/tiddlers/filters/examples/format Operator (Examples).tid @@ -1,5 +1,5 @@ created: 20201020102735123 -modified: 20210524044020645 +modified: 20220611104737314 tags: [[Operator Examples]] [[format Operator]] title: format Operator (Examples) type: text/vnd.tiddlywiki @@ -18,9 +18,12 @@ Modified date shown as a relative date: A tiddler title with spaces formatted as a title list: <<.operator-example 4 """[[Hello There]format:titlelist[]]""">> -All tiddler titles tagged with <<tag TableOfContents>> formatted as a title list : +All tiddler titles tagged with <<tag TableOfContents>> formatted as a title list: <<.operator-example 5 """[tag[TableOfContents]format:titlelist[]]""">> +A JSON string formatted as JSON – note how the JSON string is normalised to remove the duplicated properties: +<<.operator-example 6 """[[{"one":"first","one":"another","two":"second"}]format:json[]]""">> + <<.tip "To create a string to save a [[title list|Title List]] into a list field, use `format:titlelist[]` with the [[join operator|join Operator]]">> <<.operator-example 6 """[tag[TableOfContents]format:titlelist[]join[ ]]""">> For example, to save titles tagged `TableOfContents` to the titles field of the tiddler [[format titlelist test]]: diff --git a/editions/tw5.com/tiddlers/filters/format.tid b/editions/tw5.com/tiddlers/filters/format.tid index 9cc846139..e5b12b134 100644 --- a/editions/tw5.com/tiddlers/filters/format.tid +++ b/editions/tw5.com/tiddlers/filters/format.tid @@ -1,6 +1,6 @@ caption: format created: 20201020100834443 -modified: 20220523075550449 +modified: 20220611104737314 op-input: a [[selection of titles|Title Selection]] op-output: input strings formatted according to the specified suffix <<.place B>> op-parameter: optional format string for the formats @@ -17,9 +17,10 @@ type: text/vnd.tiddlywiki The suffix <<.place B>> is one of the following supported string formats: |!Format |!Description | -|^`date` |The input string is interpreted as a UTC date and displayed according to the DateFormat specified in the optional operator parameter. (Defaults to "YYYY MM DD 0hh:0mm") | -|^`relativedate` |The input string is interpreted as a UTC date and displayed as the interval from the present instant. Any operator parameters are ignored. | -|^`titlelist` |<<.from-version "5.2.0">>The input string wrapped in double square brackets if it contains a space. Appropriate for use in a [[title list|Title List]]. | +|^`date` |The input string is interpreted as a UTC date and displayed according to the DateFormat specified in the optional operator operand. (Defaults to "YYYY MM DD 0hh:0mm") | +|^`json` |<<.from-version "5.2.4">> The input string is interpreted as JSON and displayed with standard formatting. The optional operator operand specifies the number of spaces to use for indenting, or a string to use for indenting. Nothing is returned if the input string is not valid JSON | +|^`relativedate` |The input string is interpreted as a UTC date and displayed as the interval from the present instant. Any operator parameters are ignored | +|^`titlelist` |<<.from-version "5.2.0">> The input string wrapped in double square brackets if it contains a space. Appropriate for use in a [[title list|Title List]]. | <<.warning """The [[Title List]] format cannot reliably represent items that contain certain specific character sequences such as `]] `. Thus it should not be used where there is a possibility of such sequences occurring.""">> diff --git a/editions/tw5.com/tiddlers/filters/jsonget.tid b/editions/tw5.com/tiddlers/filters/jsonget.tid new file mode 100644 index 000000000..dbc247d7b --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/jsonget.tid @@ -0,0 +1,86 @@ +created: 20220611104737314 +modified: 20220611104737314 +tags: [[Filter Operators]] [[JSON Operators]] +title: jsonget Operator +caption: jsonget +op-purpose: retrieve the value of a property from JSON strings +op-input: a selection of JSON strings +op-parameter: one or more indexes of the property to retrieve +op-output: the values of each of the retrieved properties + +<<.from-version "5.2.4">> See [[JSON in TiddlyWiki]] for background. + +The <<.op jsonget>> operator is used to retrieve values from JSON data. See also the following related operators: + +* <<.olink jsontype>> to retrieve the type of a JSON value +* <<.olink jsonindexes>> to retrieve the names of the fields of a JSON object, or the indexes of a JSON array + +Properties within a JSON object are identified by a sequence of indexes. In the following example, the value at `[a]` is `one`, and the value at `[d][f][0]` is `five`. + +``` +{ + "a": "one", + "b": "", + "c": "three", + "d": { + "e": "four", + "f": [ + "five", + "six", + true, + false, + null + ], + "g": { + "x": "max", + "y": "may", + "z": "maize" + } + } +} +``` + +The following examples assume that this JSON data is contained in a variable called `jsondata`. + +The <<.op jsonget>> operator uses multiple operands to specify the indexes of the property to retrieve: + +``` +[<jsondata>jsonget[a]] --> "one" +[<jsondata>jsonget[d],[e]] --> "four" +[<jsondata>jsonget[d],[f],[0]] --> "five" +``` + +Indexes can be dynamically composed from variables and transclusions: + +``` +[<jsondata>jsonget<variable>,{!!field},[0]] +``` + +Boolean values and null are returned as normal strings. The <<.olink jsontype>> operator can be used to retrieve a string identifying the original type. Thus: + +``` +[<jsondata>jsontype[a]] --> "string" +[<jsondata>jsontype[d]] --> "object" +[<jsondata>jsontype[d],[f]] --> "array" +[<jsondata>jsontype[d],[f],[2]] --> "boolean" +``` + +Using the <<.op jsonget>> operator to retrieve an object or an array returns a JSON string of the values. For example: + +``` +[<jsondata>jsonget[d],[f]] --> `["five","six",true,false,null]` +[<jsondata>jsonget[d],[g]] --> `{"x": "max","y": "may","z": "maize"}` +``` + +The <<.olink jsonindexes>> operator retrieves the corresponding indexes: + +``` +[<jsondata>jsonindexes[d],[f]] --> "0", "1", "2", "3", "4" +[<jsondata>jsonindexes[d],[g]] --> "x", "y", "z" +``` + +A subtlety is that the special case of a single blank operand is used to identify the root object. Thus: + +``` +[<jsondata>jsonindexes[]] --> "a", "b", "c", "d" +``` diff --git a/editions/tw5.com/tiddlers/filters/jsonindexes.tid b/editions/tw5.com/tiddlers/filters/jsonindexes.tid new file mode 100644 index 000000000..933f0f101 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/jsonindexes.tid @@ -0,0 +1,64 @@ +created: 20220611104737314 +modified: 20220611104737314 +tags: [[Filter Operators]] [[JSON Operators]] +title: jsonindexes Operator +caption: jsonindexes +op-purpose: retrieve the value of a property from JSON strings +op-input: a selection of JSON strings +op-parameter: one or more indexes of the property to retrieve +op-output: the values of each of the retrieved properties + +<<.from-version "5.2.4">> See [[JSON in TiddlyWiki]] for background. + +The <<.op jsonindexes>> operator is used to retrieve the property names of JSON objects or the index names of JSON arrays. See also the following related operators: + +* <<.olink jsonget>> to retrieve the values of a property in JSON data +* <<.olink jsontype>> to retrieve the type of a JSON value + +Properties within a JSON object are identified by a sequence of indexes. In the following example, the value at `[a]` is `one`, and the value at `[d][f][0]` is `five`. + +``` +{ + "a": "one", + "b": "", + "c": "three", + "d": { + "e": "four", + "f": [ + "five", + "six", + true, + false, + null + ], + "g": { + "x": "max", + "y": "may", + "z": "maize" + } + } +} +``` + +The following examples assume that this JSON data is contained in a variable called `jsondata`. + +The <<.op jsonindexes>> operator uses multiple operands to specify the indexes of the property to retrieve: + +``` +[<jsondata>jsonindexes[d],[f]] --> "0", "1", "2", "3", "4" +[<jsondata>jsonindexes[d],[g]] --> "x", "y", "z" +``` + +Indexes can be dynamically composed from variables and transclusions: + +``` +[<jsondata>jsonindexes<variable>,{!!field}] +``` + +Retrieving the indexes of JSON properties that are not objects or arrays will return nothing. + +A subtlety is that the special case of a single blank operand is used to identify the root object. Thus: + +``` +[<jsondata>jsonindexes[]] --> "a", "b", "c", "d" +``` diff --git a/editions/tw5.com/tiddlers/filters/jsontype.tid b/editions/tw5.com/tiddlers/filters/jsontype.tid new file mode 100644 index 000000000..766757af0 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/jsontype.tid @@ -0,0 +1,73 @@ +created: 20220611104737314 +modified: 20220611104737314 +tags: [[Filter Operators]] [[JSON Operators]] +title: jsontype Operator +caption: jsontype +op-purpose: retrieve the type of a property from JSON strings +op-input: a selection of JSON strings +op-parameter: one or more indexes of the property whose type is to be retrieved +op-output: the types of each of the retrieved properties + +<<.from-version "5.2.4">> See [[JSON in TiddlyWiki]] for background. + +The <<.op jsontype>> operator is used to retrieve the type of a property in JSON data. See also the following related operators: + +* <<.olink jsonget>> to retrieve the values of a property in JSON data +* <<.olink jsonindexes>> to retrieve the names of the fields of a JSON object, or the indexes of a JSON array + +JSON supports the following data types: + +* ''string'' - a Unicode string +* ''number'' - a floating point number +* ''boolean'' - Boolean value (true or false) +* ''array'' - an array of values +* ''object'' - an object of name/value pairs +* ''null'' - a special type representing a missing value + +Properties within a JSON object are identified by a sequence of indexes. In the following example, the value at `[a]` is `one`, and the value at `[d][f][0]` is `five`. + +``` +{ + "a": "one", + "b": "", + "c": "three", + "d": { + "e": "four", + "f": [ + "five", + "six", + true, + false, + null + ], + "g": { + "x": "max", + "y": "may", + "z": "maize" + } + } +} +``` + +The following examples assume that this JSON data is contained in a variable called `jsondata`. + +The <<.op jsontype>> operator uses multiple operands to specify the indexes of the property whose type is to be retrieved: + +``` +[<jsondata>jsontype[a]] --> "string" +[<jsondata>jsontype[d]] --> "object" +[<jsondata>jsontype[d],[f]] --> "array" +[<jsondata>jsontype[d],[f],[2]] --> "boolean" +``` + +Indexes can be dynamically composed from variables and transclusions: + +``` +[<jsondata>jsontype<variable>,{!!field},[0]] +``` + +A subtlety is that the special case of a single blank operand is used to identify the root object. Thus: + +``` +[<jsondata>jsontype[]] --> "object" +``` diff --git a/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid b/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid index aafa30ecf..205ef7cae 100644 --- a/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid +++ b/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid @@ -3,13 +3,121 @@ tags: $:/tags/EditPreview list-after: $:/core/ui/EditTemplate/body/preview/output caption: parse tree -\define preview(mode) -<$wikify name="preview-text" text={{!!text}} type={{!!type}} mode="$mode$" output="parsetree"> -<pre> -<code> -<$text text=<<preview-text>>/> -</code> -</pre> +\whitespace trim + +\procedure preview-node-properties(node) +<$let excludeProperties="text type tag children attributes orderedAttributes"> +<$list filter="[<node>jsonindexes[]] -[subfilter<excludeProperties>] +[limit[1]]" variable="ignore"> +<table> +<tbody> +<$list filter="[<node>jsonindexes[]] -[subfilter<excludeProperties>] +[sort[]]" variable="index"> +<tr> +<td> +<$text text=<<index>>/> +</td> +<td> +<$text text={{{ [<node>jsonget<index>] }}}/> +</td> +</tr> +</$list> +</tbody> +</table> +</$list> +</$let> +\end + +\procedure preview-node-attribute-string(attribute) +<$text text={{{ [<attribute>jsonget[value]] }}}/> +\end + +\procedure preview-node-attribute-indirect(attribute) +{{<$text text={{{ [<attribute>jsonget[textReference]] }}}/>}} +\end + +\procedure preview-node-attribute-macro(attribute) +<< +<$text text={{{ [<attribute>jsonget[value],[name]] }}}/> +<$list filter="[<attribute>jsonindexes[value],[params]]" variable="index"> +  +<$list filter="[<attribute>jsonget[value],[params],<index>,[name]]" variable="ignore"> +<$text text={{{ [<attribute>jsonget[value],[params],<index>,[name]] }}}/> +: +</$list> +<$text text={{{ [<attribute>jsonget[value],[params],<index>,[value]] }}}/> +</$list> +>> +\end + +\procedure preview-node-attributes(node) +<$list filter="[<node>jsonindexes[attributes]limit[1]]" variable="ignore"> +<table> +<tbody> +<$list filter="[<node>jsonindexes[attributes]sort[]]" variable="index"> +<tr> +<td> +<$text text=<<index>>/> +</td> +<td> +<$let type={{{ [<node>jsonget[attributes],<index>,[type]] }}}> +<$transclude $variable={{{ [<type>match[string]then[preview-node-attribute-string]] :else[<type>match[indirect]then[preview-node-attribute-indirect]] :else[<type>match[macro]then[preview-node-attribute-macro]] }}} attribute={{{ [<node>jsonget[attributes],<index>] }}}/> +</$let> +</td> +</tr> +</$list> +</tbody> +</table> +</$list> +\end + +\procedure preview-node-children(node) +<div style="padding:4px 4px 0 4px;"> +<$transclude $variable="preview-node-properties" node=<<node>>/> +<$transclude $variable="preview-node-attributes" node=<<node>>/> +<$transclude $variable="preview-node-list" nodeList={{{ [<node>jsonget[children]] }}}/> +</div> +\end + +\procedure preview-node-title-widget(node) +<div style="border:2px solid red;margin:4px;"> +<div style="background:red;color:white;padding:4px;"> +<$<$text text={{{ [<node>jsonget[type]] }}}/>> +</div> +<$transclude $variable="preview-node-children" node=<<node>>/> +</div> +\end + +\procedure preview-node-title-element(node) +<div style="border:2px solid purple;margin:4px;"> +<div style="background:purple;color:white;padding:4px;"> +<<$text text={{{ [<node>jsonget[tag]] }}}/>> +</div> +<$transclude $variable="preview-node-children" node=<<node>>/> +</div> +\end + +\procedure preview-node-title-text(node) +<div style="border:2px solid green;margin:4px;"> +<div style="background:green;color:white;padding:4px;"> +<span style="color:#ff0;font-weight:bold;">"</span><span style="white-space:pre-wrap;"><$text text={{{ [<node>jsonget[text]] }}}/></span><span style="color:#ff0;font-weight:bold;">"</span> +</div> +</div> +\end + +\procedure preview-node(node) +<$let type={{{ [<node>jsonget[type]] }}}> +<$transclude $variable={{{ [<type>match[element]then[preview-node-title-element]] :else[<type>match[text]then[preview-node-title-text]] :else[[preview-node-title-widget]] }}} node=<<node>>/> +</$let> +\end + +\procedure preview-node-list(nodeList) +<$list filter="[<nodeList>jsonindexes[]]" variable="index"> +<$transclude $variable="preview-node" node={{{ [<nodeList>jsonget<index>] }}}/> +</$list> +\end + +\procedure preview(mode) +<$wikify name="preview-json" text={{!!text}} type={{!!type}} mode=<<mode>> output="parsetree"> +<$transclude $variable="preview-node-list" nodeList=<<preview-json>>/> </$wikify> \end