diff --git a/core/language/en-GB/ControlPanel.multids b/core/language/en-GB/ControlPanel.multids index 6c8d989fa..4ad04142d 100644 --- a/core/language/en-GB/ControlPanel.multids +++ b/core/language/en-GB/ControlPanel.multids @@ -38,6 +38,8 @@ Palette/Editor/Reset/Caption: reset Palette/HideEditor/Caption: hide editor Palette/Prompt: Current palette: Palette/ShowEditor/Caption: show editor +Plugins/Add/Hint: Install new plugins +Plugins/Add/Caption: Add Plugins/Caption: Plugins Plugins/Disable/Caption: disable Plugins/Disable/Hint: Disable this plugin when reloading page @@ -45,6 +47,8 @@ Plugins/Disabled/Status: (disabled) Plugins/Empty/Hint: None Plugins/Enable/Caption: enable Plugins/Enable/Hint: Enable this plugin when reloading page +Plugins/Installed/Hint: Currently installed plugins +Plugins/Installed/Caption: Installed Plugins/Language/Prompt: Languages Plugins/Plugin/Prompt: Plugins Plugins/Theme/Prompt: Themes diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids index 449aa1230..d165ce99d 100644 --- a/core/language/en-GB/Misc.multids +++ b/core/language/en-GB/Misc.multids @@ -14,6 +14,7 @@ Encryption/ConfirmClearPassword: Do you wish to clear the password? This will re Encryption/PromptSetPassword: Set a new password for this TiddlyWiki InvalidFieldName: Illegal characters in field name "<$text text=<>/>". Fields can only contain lowercase letters, digits and the characters underscore (`_`), hyphen (`-`) and period (`.`) MissingTiddler/Hint: Missing tiddler "<$text text=<>/>" - click {{$:/core/images/edit-button}} to create +OfficialPluginLibrary: Official ~TiddlyWiki Plugin Library PluginReloadWarning: Please save {{$:/core/ui/Buttons/save-wiki}} and reload {{$:/core/ui/Buttons/refresh}} to allow changes to plugins to take effect RecentChanges/DateFormat: DDth MMM YYYY SystemTiddler/Tooltip: This is a system tiddler diff --git a/core/modules/commands/savelibrarytiddlers.js b/core/modules/commands/savelibrarytiddlers.js new file mode 100644 index 000000000..925be358a --- /dev/null +++ b/core/modules/commands/savelibrarytiddlers.js @@ -0,0 +1,66 @@ +/*\ +title: $:/core/modules/commands/savelibrarytiddlers.js +type: application/javascript +module-type: command + +Command to save the subtiddlers of a bundle tiddler as a series of JSON files + +--savelibrarytiddlers + +The tiddler identifies the bundle tiddler that contains the subtiddlers. + +The pathname specifies the pathname to the folder in which the JSON files should be saved. The filename is the URL encoded title of the subtiddler. + +The skinnylisting specifies the title of the tiddler to which a JSON catalogue of the subtiddlers will be saved. The JSON file contains the same data as the bundle tiddler but with the `text` field removed. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.info = { + name: "savelibrarytiddlers", + synchronous: true +}; + +var Command = function(params,commander,callback) { + this.params = params; + this.commander = commander; + this.callback = callback; +}; + +Command.prototype.execute = function() { + if(this.params.length < 2) { + return "Missing filename"; + } + var self = this, + fs = require("fs"), + path = require("path"), + containerTitle = this.params[0], + basepath = this.params[1], + skinnyListTitle = this.params[2]; + // Get the container tiddler as data + var containerData = self.commander.wiki.getTiddlerData(containerTitle,undefined); + if(!containerData) { + return "'" + containerTitle + "' is not a tiddler bundle"; + } + // Save each JSON file and collect the skinny data + var skinnyList = []; + $tw.utils.each(containerData.tiddlers,function(tiddler,title) { + var pathname = path.resolve(self.commander.outputPath,basepath + encodeURIComponent(title) + ".json"); + $tw.utils.createFileDirectories(pathname); + fs.writeFileSync(pathname,JSON.stringify(tiddler,null,$tw.config.preferences.jsonSpaces),"utf8"); + skinnyList.push($tw.utils.extend({},tiddler,{text: undefined})); + }); + // Save the catalogue tiddler + if(skinnyListTitle) { + self.commander.wiki.setTiddlerData(skinnyListTitle,skinnyList); + } + return null; +}; + +exports.Command = Command; + +})(); diff --git a/core/modules/startup/browser-messaging.js b/core/modules/startup/browser-messaging.js new file mode 100644 index 000000000..0b8fb9eb7 --- /dev/null +++ b/core/modules/startup/browser-messaging.js @@ -0,0 +1,155 @@ +/*\ +title: $:/core/modules/browser-messaging.js +type: application/javascript +module-type: startup + +Browser message handling + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +// Export name and synchronous status +exports.name = "browser-messaging"; +exports.platforms = ["browser"]; +exports.after = ["startup"]; +exports.synchronous = true; + +/* +Load a specified url as an iframe and call the callback when it is loaded. If the url is already loaded then the existing iframe instance is used +*/ +function loadIFrame(url,callback) { + // Check if iframe already exists + var iframeInfo = $tw.browserMessaging.iframeInfoMap[url]; + if(iframeInfo) { + // We've already got the iframe + callback(null,iframeInfo); + } else { + // Create the iframe and save it in the list + var iframe = document.createElement("iframe"), + iframeInfo = { + url: url, + status: "loading", + domNode: iframe + }; + $tw.browserMessaging.iframeInfoMap[url] = iframeInfo; + saveIFrameInfoTiddler(iframeInfo); + // Add the iframe to the DOM and hide it + iframe.style.display = "none"; + document.body.appendChild(iframe); + // Set up onload + iframe.onload = function() { + iframeInfo.status = "loaded"; + saveIFrameInfoTiddler(iframeInfo); + callback(null,iframeInfo); + }; + iframe.onerror = function() { + callback("Cannot load iframe"); + }; + try { + iframe.src = url; + } catch(ex) { + callback(ex); + } + } +} + +function saveIFrameInfoTiddler(iframeInfo) { + $tw.wiki.addTiddler(new $tw.Tiddler($tw.wiki.getCreationFields(),{ + title: "$:/temp/ServerConnection/" + iframeInfo.url, + text: iframeInfo.status, + tags: ["$:/tags/ServerConnection"], + url: iframeInfo.url + },$tw.wiki.getModificationFields())); +} + +exports.startup = function() { + // Initialise the store of iframes we've created + $tw.browserMessaging = { + iframeInfoMap: {} // Hashmap by URL of {url:,status:"loading/loaded",domNode:} + }; + // Listen for widget messages to control loading the plugin library + $tw.rootWidget.addEventListener("tm-load-plugin-library",function(event) { + var paramObject = event.paramObject || {}, + url = paramObject.url; + if(url) { + loadIFrame(url,function(err,iframeInfo) { + if(err) { + alert("Error loading plugin library: " + url); + } else { + iframeInfo.domNode.contentWindow.postMessage({ + verb: "GET", + url: "recipes/default/tiddlers.json", + cookies: { + type: "save-info", + infoTitlePrefix: paramObject.infoTitlePrefix || "$:/temp/RemoteAssetInfo/", + url: url + } + },"*"); + } + }); + } + }); + $tw.rootWidget.addEventListener("tm-load-plugin-from-library",function(event) { + var paramObject = event.paramObject || {}, + url = paramObject.url, + title = paramObject.title; + if(url && title) { + loadIFrame(url,function(err,iframeInfo) { + if(err) { + alert("Error loading plugin library: " + url); + } else { + iframeInfo.domNode.contentWindow.postMessage({ + verb: "GET", + url: "recipes/default/tiddlers/" + encodeURIComponent(title) + ".json", + cookies: { + type: "save-tiddler", + url: url + } + },"*"); + } + }); + } + }); + // Listen for window messages from other windows + window.addEventListener("message",function listener(event){ + console.log("browser-messaging: ",document.location.toString()) + console.log("browser-messaging: Received message from",event.origin); + console.log("browser-messaging: Message content",event.data); + switch(event.data.verb) { + case "GET-RESPONSE": + if(event.data.status.charAt(0) === "2") { + if(event.data.cookies) { + if(event.data.cookies.type === "save-info") { + var tiddlers = JSON.parse(event.data.body); + $tw.utils.each(tiddlers,function(tiddler) { + $tw.wiki.addTiddler(new $tw.Tiddler($tw.wiki.getCreationFields(),tiddler,{ + title: event.data.cookies.infoTitlePrefix + event.data.cookies.url + "/" + tiddler.title, + "original-title": tiddler.title, + text: "", + type: "text/vnd.tiddlywiki", + "original-type": tiddler.type, + "plugin-type": undefined, + "original-plugin-type": tiddler["plugin-type"], + "module-type": undefined, + "original-module-type": tiddler["module-type"], + tags: ["$:/tags/RemoteAssetInfo"], + "original-tags": $tw.utils.stringifyList(tiddler.tags || []), + "server-url": event.data.cookies.url + },$tw.wiki.getModificationFields())); + }); + } else if(event.data.cookies.type === "save-tiddler") { + var tiddler = JSON.parse(event.data.body); + $tw.wiki.addTiddler(new $tw.Tiddler(tiddler)); + } + } + } + break; + } + },false); +}; + +})(); diff --git a/core/ui/ControlPanel/AddPlugins.tid b/core/ui/ControlPanel/AddPlugins.tid new file mode 100644 index 000000000..93a9bc1bb --- /dev/null +++ b/core/ui/ControlPanel/AddPlugins.tid @@ -0,0 +1,85 @@ +title: $:/core/ui/ControlPanel/Plugins/Add +tags: $:/tags/ControlPanel/Plugins +caption: {{$:/language/ControlPanel/Plugins/Add/Caption}} + +\define lingo-base() $:/language/ControlPanel/Plugins/ + +\define install-plugin-button() +<$button> +<$action-sendmessage $message="tm-load-plugin-from-library" url={{!!url}} title={{$(assetInfo)$!!original-title}}/> +<$list filter="[get[original-title]get[version]]" variable="installedVersion" emptyMessage="""install"""> +reinstall + + +\end + +\define display-plugin-info() + + +<> +
+ + +''<$view tiddler=<> field="description"/>'' +
+<$view tiddler=<> field="original-title"/> + + +<$view tiddler=<> field="version"/> +<$list filter="[get[original-title]get[version]]" variable="installedVersion"> +
+ +Installed: +
+<$text text=<>/> +
+ + + +\end + +\define load-plugin-library-button() +<$button> +<$action-sendmessage $message="tm-load-plugin-library" url={{!!url}} infoTitlePrefix="$:/temp/RemoteAssetInfo/"/> +open plugin library + +\end + +\define display-server-connection() +<$list filter="[all[tiddlers+shadows]tag[$:/tags/ServerConnection]suffix{!!url}]" variable="connectionTiddler" emptyMessage=<>> + +Search: <$edit-text tiddler="""$:/temp/RemoteAssetSearch/$(currentTiddler)$""" default="" type="search" tag="input" focus="true"/> +<$select tiddler="$:/temp/RemoteAssetCategory/$(currentTiddler)$" default="plugin"> + + + + + +<$set name="pluginType" filter="[[$:/temp/RemoteAssetCategory/$(currentTiddler)$]is[tiddler]]" value={{$:/temp/RemoteAssetCategory/$(currentTiddler)$}} emptyValue="plugin"> + + +<$list filter="[all[tiddlers+shadows]tag[$:/tags/RemoteAssetInfo]server-url{!!url}original-plugin-typesearch{$:/temp/RemoteAssetSearch/$(currentTiddler)$}sort[description]]" variable="assetInfo"> +<> + + +
+ + +\end + +\define plugin-library-listing() +<$list filter="[all[tiddlers+shadows]tag[$:/tags/PluginLibrary]]"> +
+ +!! <$transclude field="caption"><$view field="title"/> + +<$view field="url"/> +<> +
+ +\end + +
+<> +
+ diff --git a/core/ui/ControlPanel/Plugins.tid b/core/ui/ControlPanel/InstalledPlugins.tid similarity index 95% rename from core/ui/ControlPanel/Plugins.tid rename to core/ui/ControlPanel/InstalledPlugins.tid index 2ed51058c..288e839c3 100644 --- a/core/ui/ControlPanel/Plugins.tid +++ b/core/ui/ControlPanel/InstalledPlugins.tid @@ -1,6 +1,6 @@ -title: $:/core/ui/ControlPanel/Plugins -tags: $:/tags/ControlPanel -caption: {{$:/language/ControlPanel/Plugins/Caption}} +title: $:/core/ui/ControlPanel/Plugins/Installed +tags: $:/tags/ControlPanel/Plugins +caption: {{$:/language/ControlPanel/Plugins/Installed/Caption}} \define lingo-base() $:/language/ControlPanel/Plugins/ \define popup-state-macro() diff --git a/core/wiki/config/OfficialPluginLibrary.tid b/core/wiki/config/OfficialPluginLibrary.tid new file mode 100644 index 000000000..13e740fa7 --- /dev/null +++ b/core/wiki/config/OfficialPluginLibrary.tid @@ -0,0 +1,6 @@ +title: $:/config/OfficialPluginLibrary +tags: $:/tags/PluginLibrary +url: http://tiddlywiki.com/library/index.html +caption: {{$:/language/OfficialPluginLibrary}} + + diff --git a/core/wiki/tags/ControlPanelPlugins.tid b/core/wiki/tags/ControlPanelPlugins.tid new file mode 100644 index 000000000..32851ce74 --- /dev/null +++ b/core/wiki/tags/ControlPanelPlugins.tid @@ -0,0 +1,2 @@ +title: $:/tags/ControlPanel/Plugins +list: [[$:/core/ui/ControlPanel/Plugins/Installed]] [[$:/core/ui/ControlPanel/Plugins/Add]] diff --git a/editions/pluginlibrary/tiddlywiki.info b/editions/pluginlibrary/tiddlywiki.info new file mode 100644 index 000000000..d461050ad --- /dev/null +++ b/editions/pluginlibrary/tiddlywiki.info @@ -0,0 +1,17 @@ +{ + "description": "TiddlyWiki Plugin Library", + "plugins": [ + "tiddlywiki/pluginlibrary" + ], + "themes": [ + ], + "includeWikis": [ + ], + "build": { + "library": [ + "--makelibrary","$:/UpgradeLibrary", + "--savelibrarytiddlers","$:/UpgradeLibrary","library/recipes/default/tiddlers/","$:/UpgradeLibrary/List", + "--savetiddler","$:/UpgradeLibrary/List","library/recipes/default/tiddlers.json", + "--rendertiddler","$:/plugins/tiddlywiki/pluginlibrary/library.template.html","library/index.html","text/plain"] + } +} diff --git a/editions/prerelease/tiddlers/system/PrereleaseOfficialPluginLibrary.tid b/editions/prerelease/tiddlers/system/PrereleaseOfficialPluginLibrary.tid new file mode 100644 index 000000000..efc4de3ad --- /dev/null +++ b/editions/prerelease/tiddlers/system/PrereleaseOfficialPluginLibrary.tid @@ -0,0 +1,6 @@ +title: $:/config/OfficialPluginLibrary +tags: $:/tags/PluginLibrary +url: http://tiddlywiki.com/prerelease/library/index.html +url: file:///Users/jeremyruston/git/Jermolene/jermolene.github.io/prerelease/library/index.html +url: http://0.0.0.0:8080/prerelease/library/index.html +caption: {{$:/language/OfficialPluginLibrary}} (Prerelease) diff --git a/editions/prerelease/tiddlywiki.info b/editions/prerelease/tiddlywiki.info index 84b817cb3..5b85f6fd5 100644 --- a/editions/prerelease/tiddlywiki.info +++ b/editions/prerelease/tiddlywiki.info @@ -16,23 +16,6 @@ "tiddlywiki/readonly" ], "languages": [ - "da-DK", - "el-GR", - "en-US", - "en-GB", - "de-AT", - "de-DE", - "es-ES", - "fr-FR", - "nl-NL", - "zh-Hans", - "zh-Hant", - "hi-IN", - "pa-IN", - "it-IT", - "ja-JP", - "cs-CZ", - "ru-RU" ], "includeWikis": [ "../tw5.com" diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index 7fc0760b5..1a7ef42c7 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -17,25 +17,6 @@ "tiddlywiki/readonly" ], "languages": [ - "da-DK", - "el-GR", - "en-US", - "en-GB", - "de-AT", - "de-DE", - "es-ES", - "fr-FR", - "ia-IA", - "nl-NL", - "zh-Hans", - "zh-Hant", - "hi-IN", - "pa-IN", - "pt-PT", - "it-IT", - "ja-JP", - "cs-CZ", - "ru-RU" ], "build": { "index": [ diff --git a/plugins/tiddlywiki/pluginlibrary/asset-list-json.tid b/plugins/tiddlywiki/pluginlibrary/asset-list-json.tid new file mode 100644 index 000000000..a54f375cf --- /dev/null +++ b/plugins/tiddlywiki/pluginlibrary/asset-list-json.tid @@ -0,0 +1,4 @@ +title: $:/plugins/tiddlywiki/pluginlibrary/asset-list-json + +`var assetList = `<$view tiddler="$:/UpgradeLibrary/List"/>`; +` \ No newline at end of file diff --git a/plugins/tiddlywiki/pluginlibrary/library.template.html.tid b/plugins/tiddlywiki/pluginlibrary/library.template.html.tid new file mode 100644 index 000000000..c83ce2575 --- /dev/null +++ b/plugins/tiddlywiki/pluginlibrary/library.template.html.tid @@ -0,0 +1,27 @@ +title: $:/plugins/tiddlywiki/pluginlibrary/library.template.html + +\rules only filteredtranscludeinline transcludeinline + + + + + + + + +Plugin Library + + + + +

HelloThere

+ +

This is the TiddlyWiki plugin library. It is not intended to be opened directly in the browser.

+ +

See http://tiddlywiki.com/ for details of how to install plugins.

+ + + \ No newline at end of file diff --git a/plugins/tiddlywiki/pluginlibrary/libraryserver.js b/plugins/tiddlywiki/pluginlibrary/libraryserver.js new file mode 100644 index 000000000..6ad7fcb11 --- /dev/null +++ b/plugins/tiddlywiki/pluginlibrary/libraryserver.js @@ -0,0 +1,90 @@ +/*\ +title: $:/plugins/tiddlywiki/pluginlibrary/libraryserver.js +type: application/javascript +module-type: library + +A simple HTTP-over-window.postMessage implementation of a standard TiddlyWeb-compatible server. It uses real HTTP to load the individual tiddler JSON files. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +// Listen for window messages +window.addEventListener("message",function listener(event){ + console.log("plugin library: Received message from",event.origin); + console.log("plugin library: Message content",event.data); + switch(event.data.verb) { + case "GET": + if(event.data.url === "recipes/default/tiddlers.json") { + // Route for recipes/default/tiddlers.json + event.source.postMessage({ + verb: "GET-RESPONSE", + status: "200", + cookies: event.data.cookies, + url: event.data.url, + type: "application/json", + body: JSON.stringify(assetList,null,4) + },"*"); + } else if(event.data.url.indexOf("recipes/default/tiddlers/") === 0) { + var url = "recipes/default/tiddlers/" + encodeURIComponent(removePrefix(event.data.url,"recipes/default/tiddlers/")); + // Route for recipes/default/tiddlers/.json + httpGet(url,function(err,responseText) { + if(err) { + event.source.postMessage({ + verb: "GET-RESPONSE", + status: "404", + cookies: event.data.cookies, + url: event.data.url, + type: "text/plain", + body: "Not found" + },"*"); + } else { + event.source.postMessage({ + verb: "GET-RESPONSE", + status: "200", + cookies: event.data.cookies, + url: event.data.url, + type: "application/json", + body: responseText + },"*"); + } + }); + } else { + event.source.postMessage({ + verb: "GET-RESPONSE", + status: "404", + cookies: event.data.cookies, + url: event.data.url, + type: "text/plain", + body: "Not found" + },"*"); + } + break; + } +},false); + +// Helper to remove string prefixes +function removePrefix(string,prefix) { + if(string.indexOf(prefix) === 0) { + return string.substr(prefix.length); + } else { + return string; + } +} + +// Helper for HTTP GET +function httpGet(url,callback) { + var http = new XMLHttpRequest(); + http.open("GET",url,true); + http.onreadystatechange = function() { + if(http.readyState == 4 && http.status == 200) { + callback(null,http.responseText); + } + }; + http.send(); +} + +})(); diff --git a/plugins/tiddlywiki/pluginlibrary/plugin.info b/plugins/tiddlywiki/pluginlibrary/plugin.info new file mode 100644 index 000000000..f2de212c0 --- /dev/null +++ b/plugins/tiddlywiki/pluginlibrary/plugin.info @@ -0,0 +1,6 @@ +{ + "title": "$:/plugins/tiddlywiki/pluginlibrary", + "description": "Plugin for building the TiddlyWiki Plugin Library", + "author": "JeremyRuston", + "core-version": ">=5.0.0" +}