public
Last active

Sample caveatPatchor.js file for use in Propane 1.1.2 and above

  • Download Gist
caveatPatchor.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
/*
As of version 1.1.2, Propane will load and execute the contents of
~Library/Application Support/Propane/unsupported/caveatPatchor.js
immediately following the execution of its own enhancer.js file.
 
You can use this mechanism to add your own customizations to Campfire
in Propane.
 
Below you'll find two customization examples.
 
1 - A responder that adds avatars to your chat view
2 - A responder that displays CloudApp images inline
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
 
var displayAvatars = true;
var displayCloudAppImages = true;
 
 
/*
Display avatars in the chat view - based on code originally by @tmm1
*/
 
if (displayAvatars) {
 
Object.extend(Campfire.Message.prototype, {
addAvatar: function() {
if (this.actsLikeTextMessage()) {
var author = this.authorElement();
var avatar = '';
 
if (author.visible()) {
author.hide();
if (this.bodyCell.select('strong').length === 0) {
this.bodyCell.insert({top: '<strong style="color:#333;">'+author.textContent+'</strong><br>'});
avatar = author.getAttribute('data-avatar') || 'http://asset1.37img.com/global/missing/avatar.png?r=3';
author.insert({after: '<img alt="'+this.author()+'" width="32" height="32" align="top" style="opacity: 1.0; margin-left: 5px; border-radius:3px" src="'+avatar+'">'});
}
}
}
}
});
 
/* if you can wrap rather than rewrite, use swizzle like this: */
swizzle(Campfire.Message, {
setAuthorVisibilityInRelationTo: function($super, message) {
$super(message);
this.addAvatar();
}
});
 
 
/* defining a new responder is probably the best way to insulate your hacks from Campfire and Propane */
Campfire.AvatarMangler = Class.create({
initialize: function(chat) {
this.chat = chat;
 
var messages = this.chat.transcript.messages;
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
message.addAvatar();
}
 
this.chat.layoutmanager.layout();
this.chat.windowmanager.scrollToBottom();
},
 
onMessagesInserted: function(messages) {
var scrolledToBottom = this.chat.windowmanager.isScrolledToBottom();
 
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
message.addAvatar();
}
 
if (scrolledToBottom) {
this.chat.windowmanager.scrollToBottom();
}
}
});
 
/* Here is how to install your responder into the running chat */
Campfire.Responders.push("AvatarMangler");
window.chat.installPropaneResponder("AvatarMangler", "avatarmangler");
}
 
 
/*
Display CloudApp images inline.
 
This responder illustrates using Propane's requestJSON service to request
JSON from remote (non-authenticated) servers and have the results passed
to a callback of your choosing.
*/
 
if (displayCloudAppImages) {
 
Campfire.CloudAppExpander = Class.create({
initialize: function(chat) {
this.chat = chat;
var messages = this.chat.transcript.messages;
for (var i = 0; i < messages.length; i++) {
this.detectCloudAppURL(messages[i]);
}
},
 
detectCloudAppURL: function(message) {
/* we are going to use the messageID to uniquely identify our requestJSON request
so we don't check pending messages */
if (!message.pending() && message.kind === 'text') {
var links = message.bodyElement().select('a:not(image)');
if (links.length != 1) {
return;
}
var href = links[0].getAttribute('href');
var match = href.match(/^https?:\/\/cl.ly\/image\/[A-Za-z0-9]+\/?$/);
if (!match) return;
window.propane.requestJSON(message.id(), href, 'window.chat.cloudappexpander', 'onEmbedDataLoaded', 'onEmbedDataFailed');
}
},
 
onEmbedDataLoaded: function(messageID, data) {
var message = window.chat.transcript.getMessageById(messageID);
if (!message) return;
 
if (data['item_type'] === 'image') {
var imageURL = data['content_url'];
message.resize((function() {
message.bodyCell.insert({bottom: '<div style="width:100%; margin-top:5px; padding-top: 5px; border-top:1px dotted #ccc;"><a href="'+imageURL+'" class="image loading" target="_blank">' + '<img src="'+imageURL+'" onload="$dispatch(&quot;inlineImageLoaded&quot;, this)" onerror="$dispatch(&quot;inlineImageLoadFailed&quot;, this)" /></a></div>'});
}).bind(this));
}
},
 
onEmbedDataFailed: function(messageID) {
/* No cleanup required, we only alter the HTML after we get back a succesful load from the data */
},
 
onMessagesInsertedBeforeDisplay: function(messages) {
for (var i = 0; i < messages.length; i++) {
this.detectCloudAppURL(messages[i]);
}
},
 
onMessageAccepted: function(message, messageID) {
this.detectCloudAppURL(message);
}
});
 
Campfire.Responders.push("CloudAppExpander");
window.chat.installPropaneResponder("CloudAppExpander", "cloudappexpander");
}

Does propane have a requestXML that works like requestJSON? Is there any sort of documentation on the API at all?

With requestJSON I parse the remote site's JSON into Cocoa data structures and then serialize it out again as a method invocation string. Doing it this way sanitizes the JSON (avoiding eval risks), eliminates potential memory leaks and avoids any nasty crashes that might happen by letting javascript poke back into Propane too much...

As for the API, it's just:

window.propane.requestJSON(unique_id, href, qualified_object_name, success_method_name, failure_method_name)

From that I will request the JSON from the specified href and if I get a 200 response with a valid json payload I'll call:

qualified_object_name.success_method_name(unique_id, SanitizedPayload);

If the response I got was non-200 or the json parse failed then I call:

qualified_object_name.failure_method_name(unique_id)

Unfortunately I can't do the same sort of thing with a requestXML because the payload is probably much more than just dictionaries, arrays and key/value pairs. About the best thing I could do would be to pass in the response body as a string and let your callback method figure it out. Given that most people will just do something.innerHTML = payload, it kind of scares the heck out of me security-wise.

Ultimately I think the best solution is coming in Propane2 - a fully native interface (no webkit) and a scripting environment that's a first-class citizen, able to do pretty much anything that the compiled code can.

Interesting. So, I was hoping to make the cl.ly thing for droplr. They don't have a JSON api just yet, so maybe i'll wait until they do. Or maybe i'll write an intermediary XML to JSON parser service and throw it up on Heroku.

The intermediary may be your best bet right now.

I'm actually waiting to see how long it will be before someone goes hog-wild and hooks up a responder to a public intermediary like http://oohembed.com/

And before anyone reading this asks: no, you can't embed flash videos. Propane disables flash, which is one of the reasons you can leave it running for longer than two hours.

Thanks for the help. I'll see what I can do for the intermediary service. Should be pretty dang easy. I use droplr all the time, so it would definitely be worth it to have support for that.

Alrighty, http://droplr-json.heroku.com is up, and my fork includes a hack for Droplr image embeds :D

@protocool: like this? https://gist.github.com/1268036

Looks like oohembed got bought by Embedly. Someone please improve my hack. :-)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.