From d1f90f075f7cf41531f5e967b02acbc244885904 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Tue, 13 Jun 2023 10:35:55 +0100 Subject: [PATCH] Add tm-http-request message for making HTTP requests (#7422) * Initial Commit * HttpClient object shouldn't need to know about events * Add support for cancelling HTTP requests * Make the number of outstanding HTTP requests available in a state tiddler * Add a network activity button Click it to cancel outstanding requests * Fix typo Thanks @btheado Co-authored-by: btheado * Fix crash when cancelling more than one HTTP request Thanks @saqimtiaz * Further fixes to cancelling outstanding HTTP requests * Fix missing body --------- Co-authored-by: btheado --- core/images/network-activity.tid | 11 + core/language/en-GB/Buttons.multids | 2 + core/modules/startup/rootwidget.js | 32 +++ core/modules/utils/dom/http.js | 221 +++++++++++++++++- core/modules/wiki.js | 8 + core/palettes/Vanilla.tid | 1 + core/ui/PageControls/network-activity.tid | 16 ++ core/wiki/config/PageControlButtons.multids | 1 + core/wiki/tags/PageControls.tid | 2 +- ...etMessage_ tm-http-cancel-all-requests.tid | 12 + ...essage_ tm-http-request Example Zotero.tid | 115 +++++++++ .../WidgetMessage_ tm-http-request.tid | 51 ++++ .../tiddlers/messages/config-zotero-group.tid | 2 + themes/tiddlywiki/vanilla/base.tid | 4 + 14 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 core/images/network-activity.tid create mode 100644 core/ui/PageControls/network-activity.tid create mode 100644 editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-cancel-all-requests.tid create mode 100644 editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid create mode 100644 editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid create mode 100644 editions/tw5.com/tiddlers/messages/config-zotero-group.tid diff --git a/core/images/network-activity.tid b/core/images/network-activity.tid new file mode 100644 index 000000000..2efdfd4d4 --- /dev/null +++ b/core/images/network-activity.tid @@ -0,0 +1,11 @@ +title: $:/core/images/network-activity +tags: $:/tags/Image + + +<$list filter="[{$:/state/http-requests}match[0]]" variable="ignore"> + + +<$list filter="[{$:/state/http-requests}!match[0]]" variable="ignore"> + + + \ No newline at end of file diff --git a/core/language/en-GB/Buttons.multids b/core/language/en-GB/Buttons.multids index 85a71ac08..fa769d117 100644 --- a/core/language/en-GB/Buttons.multids +++ b/core/language/en-GB/Buttons.multids @@ -67,6 +67,8 @@ More/Caption: more More/Hint: More actions NewHere/Caption: new here NewHere/Hint: Create a new tiddler tagged with this one +NetworkActivity/Caption: network activity +NetworkActivity/Hint: Cancel all network activity NewJournal/Caption: new journal NewJournal/Hint: Create a new journal tiddler NewJournalHere/Caption: new journal here diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js index 1175f6f25..f5d90afb5 100644 --- a/core/modules/startup/rootwidget.js +++ b/core/modules/startup/rootwidget.js @@ -20,6 +20,38 @@ exports.before = ["story"]; exports.synchronous = true; exports.startup = function() { + // Install the HTTP client event handler + $tw.httpClient = new $tw.utils.HttpClient(); + var getPropertiesWithPrefix = function(properties,prefix) { + var result = Object.create(null); + $tw.utils.each(properties,function(value,name) { + if(name.indexOf(prefix) === 0) { + result[name.substring(prefix.length)] = properties[name]; + } + }); + return result; + }; + $tw.rootWidget.addEventListener("tm-http-request",function(event) { + var params = event.paramObject || {}; + $tw.httpClient.initiateHttpRequest({ + wiki: event.widget.wiki, + url: params.url, + method: params.method, + body: params.body, + oncompletion: params.oncompletion, + onprogress: params.onprogress, + bindStatus: params["bind-status"], + bindProgress: params["bind-progress"], + variables: getPropertiesWithPrefix(params,"var-"), + headers: getPropertiesWithPrefix(params,"header-"), + passwordHeaders: getPropertiesWithPrefix(params,"password-header-"), + queryStrings: getPropertiesWithPrefix(params,"query-"), + passwordQueryStrings: getPropertiesWithPrefix(params,"password-query-") + }); + }); + $tw.rootWidget.addEventListener("tm-http-cancel-all-requests",function(event) { + $tw.httpClient.cancelAllHttpRequests(); + }); // Install the modal message mechanism $tw.modal = new $tw.utils.Modal($tw.wiki); $tw.rootWidget.addEventListener("tm-modal",function(event) { diff --git a/core/modules/utils/dom/http.js b/core/modules/utils/dom/http.js index 6e07b1040..ba4b3d2a1 100644 --- a/core/modules/utils/dom/http.js +++ b/core/modules/utils/dom/http.js @@ -3,7 +3,7 @@ title: $:/core/modules/utils/dom/http.js type: application/javascript module-type: utils -Browser HTTP support +HTTP support \*/ (function(){ @@ -13,11 +13,204 @@ Browser HTTP support "use strict"; /* -A quick and dirty HTTP function; to be refactored later. Options are: +Manage tm-http-request events. Options include: +wiki: Reference to the wiki to be used for state tiddler tracking +stateTrackerTitle: Title of tiddler to be used for state tiddler tracking +*/ +function HttpClient(options) { + options = options || {}; + this.nextId = 1; + this.wiki = options.wiki || $tw.wiki; + this.stateTrackerTitle = options.stateTrackerTitle || "$:/state/http-requests"; + this.requests = []; // Array of {id: string,request: HttpClientRequest} + this.updateRequestTracker(); +} + +/* +Return the index into this.requests[] corresponding to a given ID. Returns null if not found +*/ +HttpClient.prototype.getRequestIndex = function(targetId) { + var targetIndex = null; + $tw.utils.each(this.requests,function(requestInfo,index) { + if(requestInfo.id === targetId) { + targetIndex = index; + } + }); + return targetIndex; +}; + +/* +Update the state tiddler that is tracking the outstanding requests +*/ +HttpClient.prototype.updateRequestTracker = function() { + this.wiki.addTiddler({title: this.stateTrackerTitle, text: "" + this.requests.length}); +}; + +HttpClient.prototype.initiateHttpRequest = function(options) { + var self = this, + id = this.nextId, + request = new HttpClientRequest(options); + this.nextId += 1; + this.requests.push({id: id, request: request}); + this.updateRequestTracker(); + request.send(function(err) { + var targetIndex = self.getRequestIndex(id); + if(targetIndex !== null) { + self.requests.splice(targetIndex,1); + self.updateRequestTracker(); + } + }); + return id; +}; + +HttpClient.prototype.cancelAllHttpRequests = function() { + var self = this; + if(this.requests.length > 0) { + for(var t=this.requests.length - 1; t--; t>=0) { + var requestInfo = this.requests[t]; + requestInfo.request.cancel(); + } + } + this.requests = []; + this.updateRequestTracker(); +}; + +HttpClient.prototype.cancelHttpRequest = function(targetId) { + var targetIndex = this.getRequestIndex(targetId); + if(targetIndex !== null) { + this.requests[targetIndex].request.cancel(); + this.requests.splice(targetIndex,1); + this.updateRequestTracker(); + } +}; + +/* +Initiate an HTTP request. Options: +wiki: wiki to be used for executing action strings +url: URL for request +method: method eg GET, POST +body: text of request body +oncompletion: action string to be invoked on completion +onprogress: action string to be invoked on progress updates +bindStatus: optional title of tiddler to which status ("pending", "complete", "error") should be written +bindProgress: optional title of tiddler to which the progress of the request (0 to 100) should be bound +variables: hashmap of variable name to string value passed to action strings +headers: hashmap of header name to header value to be sent with the request +passwordHeaders: hashmap of header name to password store name to be sent with the request +queryStrings: hashmap of query string parameter name to parameter value to be sent with the request +passwordQueryStrings: hashmap of query string parameter name to password store name to be sent with the request +*/ +function HttpClientRequest(options) { + var self = this; + console.log("Initiating an HTTP request",options) + this.wiki = options.wiki; + this.completionActions = options.oncompletion; + this.progressActions = options.onprogress; + this.bindStatus = options["bind-status"]; + this.bindProgress = options["bind-progress"]; + this.method = options.method || "GET"; + this.body = options.body || ""; + this.variables = options.variables; + var url = options.url; + $tw.utils.each(options.queryStrings,function(value,name) { + url = $tw.utils.setQueryStringParameter(url,name,value); + }); + $tw.utils.each(options.passwordQueryStrings,function(value,name) { + url = $tw.utils.setQueryStringParameter(url,name,$tw.utils.getPassword(value) || ""); + }); + this.url = url; + this.requestHeaders = {}; + $tw.utils.each(options.headers,function(value,name) { + self.requestHeaders[name] = value; + }); + $tw.utils.each(options.passwordHeaders,function(value,name) { + self.requestHeaders[name] = $tw.utils.getPassword(value) || ""; + }); +} + +HttpClientRequest.prototype.send = function(callback) { + var self = this, + setBinding = function(title,text) { + if(title) { + this.wiki.addTiddler(new $tw.Tiddler({title: title, text: text})); + } + }; + if(this.url) { + setBinding(this.bindStatus,"pending"); + setBinding(this.bindProgress,"0"); + // Set the request tracker tiddler + var requestTrackerTitle = this.wiki.generateNewTitle("$:/temp/HttpRequest"); + this.wiki.addTiddler({ + title: requestTrackerTitle, + tags: "$:/tags/HttpRequest", + text: JSON.stringify({ + url: this.url, + type: this.method, + status: "inprogress", + headers: this.requestHeaders, + data: this.body + }) + }); + this.xhr = $tw.utils.httpRequest({ + url: this.url, + type: this.method, + headers: this.requestHeaders, + data: this.body, + callback: function(err,data,xhr) { + var hasSucceeded = xhr.status >= 200 && xhr.status < 300, + completionCode = hasSucceeded ? "complete" : "error", + headers = {}; + $tw.utils.each(xhr.getAllResponseHeaders().split("\r\n"),function(line) { + var pos = line.indexOf(":"); + if(pos !== -1) { + headers[line.substr(0,pos)] = line.substr(pos + 1).trim(); + } + }); + setBinding(self.bindStatus,completionCode); + setBinding(self.bindProgress,"100"); + var resultVariables = { + status: xhr.status.toString(), + statusText: xhr.statusText, + error: (err || "").toString(), + data: (data || "").toString(), + headers: JSON.stringify(headers) + }; + self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getTiddler(requestTrackerTitle),{ + status: completionCode, + })); + self.wiki.invokeActionString(self.completionActions,undefined,$tw.utils.extend({},self.variables,resultVariables),{parentWidget: $tw.rootWidget}); + callback(hasSucceeded ? null : xhr.statusText); + // console.log("Back!",err,data,xhr); + }, + progress: function(lengthComputable,loaded,total) { + if(lengthComputable) { + setBinding(self.bindProgress,"" + Math.floor((loaded/total) * 100)) + } + self.wiki.invokeActionString(self.progressActions,undefined,{ + lengthComputable: lengthComputable ? "yes" : "no", + loaded: loaded, + total: total + },{parentWidget: $tw.rootWidget}); + } + }); + } +}; + +HttpClientRequest.prototype.cancel = function() { + if(this.xhr) { + this.xhr.abort(); + } +}; + +exports.HttpClient = HttpClient; + +/* +Make an HTTP request. Options are: url: URL to retrieve headers: hashmap of headers to send type: GET, PUT, POST etc callback: function invoked with (err,data,xhr) + progress: optional function invoked with (lengthComputable,loaded,total) returnProp: string name of the property to return as first argument of callback */ exports.httpRequest = function(options) { @@ -83,8 +276,16 @@ exports.httpRequest = function(options) { options.callback($tw.language.getString("Error/XMLHttpRequest") + ": " + this.status,null,this); } }; + // Handle progress + if(options.progress) { + request.onprogress = function(event) { + console.log("Progress event",event) + options.progress(event.lengthComputable,event.loaded,event.total); + }; + } // Make the request request.open(type,url,true); + // Headers if(headers) { $tw.utils.each(headers,function(header,headerTitle,object) { request.setRequestHeader(headerTitle,header); @@ -96,6 +297,7 @@ exports.httpRequest = function(options) { if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers)) { request.setRequestHeader("X-Requested-With","TiddlyWiki"); } + // Send data try { request.send(data); } catch(e) { @@ -104,4 +306,19 @@ exports.httpRequest = function(options) { return request; }; +exports.setQueryStringParameter = function(url,paramName,paramValue) { + var URL = $tw.browser ? window.URL : require("url").URL, + newUrl; + try { + newUrl = new URL(url); + } catch(e) { + } + if(newUrl && paramName) { + newUrl.searchParams.set(paramName,paramValue || ""); + return newUrl.toString(); + } else { + return url; + } +}; + })(); diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 8cb12cc39..ca31da8d2 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -1415,6 +1415,14 @@ exports.checkTiddlerText = function(title,targetText,options) { return text === targetText; } +/* +Execute an action string without an associated context widget +*/ +exports.invokeActionString = function(actions,event,variables,options) { + var widget = this.makeWidget(null,{parentWidget: options.parentWidget}); + widget.invokeActionString(actions,null,event,variables); +}; + /* Read an array of browser File objects, invoking callback(tiddlerFieldsArray) once they're all read */ diff --git a/core/palettes/Vanilla.tid b/core/palettes/Vanilla.tid index d84b4ec83..4c660e912 100644 --- a/core/palettes/Vanilla.tid +++ b/core/palettes/Vanilla.tid @@ -54,6 +54,7 @@ modal-footer-background: #f5f5f5 modal-footer-border: #dddddd modal-header-border: #eeeeee muted-foreground: #bbb +network-activity-foreground: #448844 notification-background: #ffffdd notification-border: #999999 page-background: #f4f4f4 diff --git a/core/ui/PageControls/network-activity.tid b/core/ui/PageControls/network-activity.tid new file mode 100644 index 000000000..763365f37 --- /dev/null +++ b/core/ui/PageControls/network-activity.tid @@ -0,0 +1,16 @@ +title: $:/core/ui/Buttons/network-activity +tags: $:/tags/PageControls +caption: {{$:/core/images/network-activity}} {{$:/language/Buttons/NetworkActivity/Caption}} +description: {{$:/language/Buttons/NetworkActivity/Hint}} + +\whitespace trim +<$button message="tm-http-cancel-all-requests" tooltip={{$:/language/Buttons/NetworkActivity/Hint}} aria-label={{$:/language/Buttons/NetworkActivity/Caption}} class=<>> +<$list filter="[match[yes]]"> +{{$:/core/images/network-activity}} + +<$list filter="[match[yes]]"> + +<$text text={{$:/language/Buttons/NetworkActivity/Caption}}/> + + + \ No newline at end of file diff --git a/core/wiki/config/PageControlButtons.multids b/core/wiki/config/PageControlButtons.multids index a437251f5..b66f11cc0 100644 --- a/core/wiki/config/PageControlButtons.multids +++ b/core/wiki/config/PageControlButtons.multids @@ -13,6 +13,7 @@ core/ui/Buttons/language: hide core/ui/Buttons/tag-manager: hide core/ui/Buttons/manager: hide core/ui/Buttons/more-page-actions: hide +core/ui/Buttons/network-activity: hide core/ui/Buttons/new-journal: hide core/ui/Buttons/new-image: hide core/ui/Buttons/palette: hide diff --git a/core/wiki/tags/PageControls.tid b/core/wiki/tags/PageControls.tid index c6234751c..c0f1cb233 100644 --- a/core/wiki/tags/PageControls.tid +++ b/core/wiki/tags/PageControls.tid @@ -1,2 +1,2 @@ title: $:/tags/PageControls -list: [[$:/core/ui/Buttons/home]] [[$:/core/ui/Buttons/close-all]] [[$:/core/ui/Buttons/fold-all]] [[$:/core/ui/Buttons/unfold-all]] [[$:/core/ui/Buttons/permaview]] [[$:/core/ui/Buttons/new-tiddler]] [[$:/core/ui/Buttons/new-journal]] [[$:/core/ui/Buttons/new-image]] [[$:/core/ui/Buttons/import]] [[$:/core/ui/Buttons/export-page]] [[$:/core/ui/Buttons/control-panel]] [[$:/core/ui/Buttons/advanced-search]] [[$:/core/ui/Buttons/manager]] [[$:/core/ui/Buttons/tag-manager]] [[$:/core/ui/Buttons/language]] [[$:/core/ui/Buttons/palette]] [[$:/core/ui/Buttons/theme]] [[$:/core/ui/Buttons/layout]] [[$:/core/ui/Buttons/storyview]] [[$:/core/ui/Buttons/encryption]] [[$:/core/ui/Buttons/timestamp]] [[$:/core/ui/Buttons/full-screen]] [[$:/core/ui/Buttons/print]] [[$:/core/ui/Buttons/save-wiki]] [[$:/core/ui/Buttons/refresh]] [[$:/core/ui/Buttons/more-page-actions]] +list: [[$:/core/ui/Buttons/home]] [[$:/core/ui/Buttons/close-all]] [[$:/core/ui/Buttons/fold-all]] [[$:/core/ui/Buttons/unfold-all]] [[$:/core/ui/Buttons/permaview]] [[$:/core/ui/Buttons/new-tiddler]] [[$:/core/ui/Buttons/new-journal]] [[$:/core/ui/Buttons/new-image]] [[$:/core/ui/Buttons/import]] [[$:/core/ui/Buttons/export-page]] [[$:/core/ui/Buttons/control-panel]] [[$:/core/ui/Buttons/advanced-search]] [[$:/core/ui/Buttons/manager]] [[$:/core/ui/Buttons/tag-manager]] [[$:/core/ui/Buttons/language]] [[$:/core/ui/Buttons/palette]] [[$:/core/ui/Buttons/theme]] [[$:/core/ui/Buttons/layout]] [[$:/core/ui/Buttons/storyview]] [[$:/core/ui/Buttons/encryption]] [[$:/core/ui/Buttons/timestamp]] [[$:/core/ui/Buttons/full-screen]] [[$:/core/ui/Buttons/print]] [[$:/core/ui/Buttons/save-wiki]] [[$:/core/ui/Buttons/refresh]] [[$:/core/ui/Buttons/network-activity]] [[$:/core/ui/Buttons/more-page-actions]] diff --git a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-cancel-all-requests.tid b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-cancel-all-requests.tid new file mode 100644 index 000000000..df94e5a0b --- /dev/null +++ b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-cancel-all-requests.tid @@ -0,0 +1,12 @@ +caption: tm-http-cancel-all-requests +created: 20230429161453032 +modified: 20230429161453032 +tags: Messages +title: WidgetMessage: tm-http-cancel-all-requests +type: text/vnd.tiddlywiki + +The ''tm-http-cancel-all-requests'' message is used to cancel all outstanding HTTP requests initiated with [[WidgetMessage: tm-http-request]]. + +Note that the state tiddler $:/state/http-requests contains a number representing the number of outstanding HTTP requests in progress. + +It does not take any parameters. diff --git a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid new file mode 100644 index 000000000..ea64dd3a2 --- /dev/null +++ b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request Example Zotero.tid @@ -0,0 +1,115 @@ +title: WidgetMessage: tm-http-request Example - Zotero +tags: $:/tags/Macro + +\procedure select-zotero-group() +Specify the Zotero group ID to import +<$edit-text tiddler="$:/config/zotero-group" tag="input"/> or +<$select tiddler="$:/config/zotero-group"> + + + + +\end + +\procedure zotero-save-item(item) +<$action-createtiddler + $basetitle={{{ =[[_zotero_import ]] =[jsonget[key]] =[[ ]] =[jsonget[title]] +[join[]] }}} + text={{{ [jsonget[title]] }}} + tags="$:/tags/ZoteroImport" +> + <$action-setmultiplefields $tiddler=<> $fields="[jsonindexes[]addprefix[zotero-]]" $values="[jsonindexes[]] :map[jsongetelse[.XXXXX.]]"/> + <$list filter="[jsonindexes[creators]]" variable="creatorIndex"> + <$action-setmultiplefields $tiddler=<> $fields="[jsonget[creators],,[creatorType]addprefix[zotero-]]" $values="[jsonget[creators],,[lastName]] [jsonget[creators],,[firstName]] +[join[, ]] :else[jsonget[creators],,[name]] "/> + + +\end zotero-save-item + +\procedure zotero-save-items(data) +<$list filter="[jsonindexes[]] :map[jsonextract,[data]]" variable="item"> + <$macrocall $name="zotero-save-item" item=<>/> + +\end zotero-save-items + +\procedure zotero-get-items(start:"0",limit:"25") + +\procedure completion() +\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] + <$action-log msg="In completion"/> + <$action-log/> + + <$list filter="[compare:number:gteq[200]compare:number:lteq[299]]" variable="ignore"> + + <$macrocall $name="zotero-save-items" data=<>/> + + <$list filter="[jsonget[total-results]subtractsubtractcompare:number:gt[0]]" variable="ignore"> + <$macrocall $name="zotero-get-items" start={{{ [add] }}} limit=<>/> + + +\end completion + +\procedure progress() +\import [[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]] + <$action-log message="In progress-actions"/> +\end progress + +\procedure request-url() +\rules only transcludeinline transcludeblock filteredtranscludeinline filteredtranscludeblock +https://api.zotero.org/groups/{{$:/config/zotero-group}}/items/ +\end request-url + +<$wikify name="url" text=<>> + <$action-sendmessage + $message="tm-http-request" + url=<> + method="GET" + query-format="json" + query-sort="title" + query-start=<> + query-limit=<> + header-accept="application/json" + bind-status="$:/temp/zotero/status" + bind-progress="$:/temp/zotero/progress" + oncompletion=<> + onprogress=<> + var-start=<> + var-limit=<> + /> + +\end + +\procedure zotero-actions() +<$macrocall $name="zotero-get-items" start="0" limit="50"/> +\end + +<> + +<$button actions=<>> +Start import from Zotero group + + +<$button message="tm-http-cancel-all-requests"> +Cancel all HTTP requests + Outstanding requests: {{$:/state/http-requests}} + +<$list filter="[tag[$:/tags/ZoteroImport]limit[1]]" variable="ignore"> + +!! Imported Tiddlers + +<$button> +<$action-deletetiddler $filter="[tag[$:/tags/ZoteroImport]]"/> +Delete these tiddlers + + +Export: <$macrocall $name="exportButton" exportFilter="[tag[$:/tags/ZoteroImport]]" lingoBase="$:/language/Buttons/ExportTiddlers/"/> + + + +
    +<$list filter="[tag[$:/tags/ZoteroImport]]"> +
  1. +<$link> +<$view field="title"/> + +
  2. + +
diff --git a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid new file mode 100644 index 000000000..f6c82e760 --- /dev/null +++ b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid @@ -0,0 +1,51 @@ +caption: tm-http-request +created: 20230429161453032 +modified: 20230429161453032 +tags: Messages +title: WidgetMessage: tm-http-request +type: text/vnd.tiddlywiki + +The ''tm-http-request'' message is used to make an HTTP request to a server. + +It uses the following properties on the `event` object: + +|!Name |!Description | +|param |Not used | +|paramObject |Hashmap of parameters (see below) | + +The following parameters are used: + +|!Name |!Description | +|method |HTTP method (eg "GET", "POST") | +|body |String data to be sent with the request | +|query-* |Query string parameters with string values | +|header-* |Headers with string values | +|password-header-* |Headers with values taken from the password store | +|password-query-* |Query string parameters with values taken from the password store | +|var-* |Variables to be passed to the completion and progress handlers (without the "var-" prefix) | +|bind-status |Title of tiddler to which the status of the request ("pending", "complete", "error") should be bound | +|bind-progress |Title of tiddler to which the progress of the request (0 to 100) should be bound | +|oncompletion |Action strings to be executed when the request completes | +|onprogress |Action strings to be executed when progress is reported | + +The following variables are passed to the completion handler: + +|!Name |!Description | +|status |HTTP result status code (see [[MDN|https://developer.mozilla.org/en-US/docs/Web/HTTP/Status]]) | +|statusText |HTTP result status text | +|error |Error string | +|data |Returned data | +|headers |Response headers as a JSON object | + +The following variables are passed to the progress handler: + +|!Name |!Description | +|lengthComputable |Whether the progress loaded and total figures are valid - "yes" or "no" | +|loaded |Number of bytes loaded so far | +|total |Total number bytes to be loaded | + +Note that the state tiddler $:/state/http-requests contains a number representing the number of outstanding HTTP requests in progress. + +!! Examples + +* [[Zotero's|https://www.zotero.org/]] API for retrieving reference items: [[WidgetMessage: tm-http-request Example - Zotero]] diff --git a/editions/tw5.com/tiddlers/messages/config-zotero-group.tid b/editions/tw5.com/tiddlers/messages/config-zotero-group.tid new file mode 100644 index 000000000..2215c496a --- /dev/null +++ b/editions/tw5.com/tiddlers/messages/config-zotero-group.tid @@ -0,0 +1,2 @@ +title: $:/config/zotero-group +text: 4813312 \ No newline at end of file diff --git a/themes/tiddlywiki/vanilla/base.tid b/themes/tiddlywiki/vanilla/base.tid index 53943f994..a8df11bb3 100644 --- a/themes/tiddlywiki/vanilla/base.tid +++ b/themes/tiddlywiki/vanilla/base.tid @@ -3185,6 +3185,10 @@ span.tc-translink > a:first-child { fill: <>; } +.tc-network-activity-background { + fill: <>; +} + /* ** Flexbox utility classes */