This commit is contained in:
Jeremy Ruston 2024-04-25 22:51:43 +08:00 committed by GitHub
commit 90c7c96131
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 209 additions and 10 deletions

View File

@ -0,0 +1,66 @@
/*\
title: $:/core/modules/savers/postmessage.js
type: application/javascript
module-type: saver
Handles saving changes via window.postMessage() to the window.parent
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Select the appropriate saver module and set it up
*/
var PostMessageSaver = function(wiki) {
this.publisher = new $tw.utils.BrowserMessagingPublisher({type: "SAVE"});
};
PostMessageSaver.prototype.save = function(text,method,callback,options) {
// Fail if the publisher hasn't been fully initialised
if(!this.publisher.canSend()) {
return false;
}
// Send the save request
this.publisher.send({
verb: "SAVE",
body: text
},function(err) {
if(err) {
callback("PostMessageSaver Error: " + err);
} else {
callback(null);
}
});
// Indicate that we handled the save
return true;
};
/*
Information about this saver
*/
PostMessageSaver.prototype.info = {
name: "postmessage",
capabilities: ["save", "autosave"],
priority: 100
};
/*
Static method that returns true if this saver is capable of working
*/
exports.canSave = function(wiki) {
// Provisionally say that we can save
return true;
};
/*
Create an instance of this saver
*/
exports.create = function(wiki) {
return new PostMessageSaver(wiki);
};
})();

View File

@ -22,6 +22,16 @@ exports.synchronous = true;
var FAVICON_TITLE = "$:/favicon.ico";
exports.startup = function() {
var setFavicon = function() {
var tiddler = $tw.wiki.getTiddler(FAVICON_TITLE);
if(tiddler) {
var faviconLink = document.getElementById("faviconLink"),
dataURI = $tw.utils.makeDataUri(tiddler.fields.text,tiddler.fields.type,tiddler.fields._canonical_uri);
faviconLink.setAttribute("href",dataURI);
$tw.faviconPublisher.send({verb: "FAVICON",body: dataURI});
}
}
$tw.faviconPublisher = new $tw.utils.BrowserMessagingPublisher({type: "FAVICON", onsubscribe: setFavicon});
// Set up the favicon
setFavicon();
// Reset the favicon when the tiddler changes
@ -32,12 +42,4 @@ exports.startup = function() {
});
};
function setFavicon() {
var tiddler = $tw.wiki.getTiddler(FAVICON_TITLE);
if(tiddler) {
var faviconLink = document.getElementById("faviconLink");
faviconLink.setAttribute("href",$tw.utils.makeDataUri(tiddler.fields.text,tiddler.fields.type,tiddler.fields._canonical_uri));
}
}
})();

View File

@ -32,10 +32,15 @@ exports.startup = function() {
$tw.titleWidgetNode = $tw.wiki.makeTranscludeWidget(PAGE_TITLE_TITLE,{document: $tw.fakeDocument, parseAsInline: true});
$tw.titleContainer = $tw.fakeDocument.createElement("div");
$tw.titleWidgetNode.render($tw.titleContainer,null);
document.title = $tw.titleContainer.textContent;
var publishTitle = function() {
$tw.titlePublisher.send({verb: "PAGETITLE",body: document.title});
document.title = $tw.titleContainer.textContent;
};
$tw.titlePublisher = new $tw.utils.BrowserMessagingPublisher({type: "PAGETITLE", onsubscribe: publishTitle});
publishTitle();
$tw.wiki.addEventListener("change",function(changes) {
if($tw.titleWidgetNode.refresh(changes,$tw.titleContainer,null)) {
document.title = $tw.titleContainer.textContent;
publishTitle();
}
});
// Set up the styles

View File

@ -0,0 +1,126 @@
/*\
title: $:/core/modules/utils/messaging.js
type: application/javascript
module-type: utils-browser
Messaging utilities for use with window.postMessage() etc.
This module intentionally has no dependencies so that it can be included in non-TiddlyWiki projects
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var RESPONSE_TIMEOUT = 2 * 1000;
/*
Class to handle subscribing to publishers
target: Target window (eg iframe.contentWindow)
type: String indicating type of item for which subscriptions are being provided (eg "SAVING")
onsubscribe: Function to be invoked with err parameter when the subscription is established, or there is a timeout
onmessage: Function to be invoked when a new message arrives, invoked with (data,callback). The callback is invoked with the argument (response)
*/
function BrowserMessagingSubscriber(options) {
var self = this;
this.target = options.target;
this.type = options.type;
this.onsubscribe = options.onsubscribe || function() {};
this.onmessage = options.onmessage;
this.hasConfirmed = false;
this.channel = new MessageChannel();
this.channel.port1.addEventListener("message",function(event) {
if(this.timerID) {
clearTimeout(this.timerID);
this.timerID = null;
}
if(event.data) {
if(event.data.verb === "SUBSCRIBED") {
self.hasConfirmed = true;
self.onsubscribe(null);
} else if(event.data.verb === self.type) {
self.onmessage(event.data,function(response) {
// Send the response back on the supplied port, and then close it
event.ports[0].postMessage(response);
event.ports[0].close();
});
}
}
});
// Set a timer so that if we don't hear from the iframe before a timeout we alert the user
this.timerID = setTimeout(function() {
if(!self.hasConfirmed) {
self.onsubscribe("NO_RESPONSE");
}
},RESPONSE_TIMEOUT);
this.channel.port1.start();
this.target.postMessage({verb: "SUBSCRIBE",to: self.type},"*",[this.channel.port2]);
}
exports.BrowserMessagingSubscriber = BrowserMessagingSubscriber;
/*
Class to handle publishing subscriptions
type: String indicating type of item for which subscriptions are being provided (eg "SAVING")
onsubscribe: Function to be invoked when a subscription occurs
*/
function BrowserMessagingPublisher(options) {
var self = this;
this.type = options.type;
this.hostIsListening = false;
this.port = null;
// Listen to connection requests from the host
window.addEventListener("message",function(event) {
if(event.data && event.data.verb === "SUBSCRIBE" && event.data.to === self.type) {
self.hostIsListening = true;
// Acknowledge
self.port = event.ports[0];
self.port.postMessage({verb: "SUBSCRIBED", to: self.type});
if(options.onsubscribe) {
options.onsubscribe(event.data);
}
}
});
}
BrowserMessagingPublisher.prototype.canSend = function() {
return !!this.hostIsListening && !!this.port;
};
BrowserMessagingPublisher.prototype.send = function(data,callback) {
var self = this;
callback = callback || function() {};
// Check that we've been initialised by the host
if(!this.hostIsListening || !this.port) {
return false;
}
// Create a channel for the confirmation
var channel = new MessageChannel();
channel.port1.addEventListener("message",function(event) {
if(event.data && event.data.verb === "OK") {
callback(null);
} else {
callback("BrowserMessagingPublisher for " + self.type + " error: " + (event.data || {}).verb);
}
channel.port1.close();
});
channel.port1.start();
// Send the save request with the port for the response
this.port.postMessage(data,[channel.port2]);
};
BrowserMessagingPublisher.prototype.close = function() {
if(this.port) {
this.port.close();
this.hostIsListening = false;
this.port = null;
}
};
exports.BrowserMessagingPublisher = BrowserMessagingPublisher;
})();