diff options
Diffstat (limited to 'plugins/LinkPreview')
-rw-r--r-- | plugins/LinkPreview/LinkPreviewPlugin.php | 101 | ||||
-rw-r--r-- | plugins/LinkPreview/linkpreview.js | 194 | ||||
-rw-r--r-- | plugins/LinkPreview/oembedproxyaction.php | 84 |
3 files changed, 379 insertions, 0 deletions
diff --git a/plugins/LinkPreview/LinkPreviewPlugin.php b/plugins/LinkPreview/LinkPreviewPlugin.php new file mode 100644 index 000000000..39d2c9bf3 --- /dev/null +++ b/plugins/LinkPreview/LinkPreviewPlugin.php @@ -0,0 +1,101 @@ +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Some UI extras for now... + * + * @package LinkPreviewPlugin + * @maintainer Brion Vibber <brion@status.net> + */ +class LinkPreviewPlugin extends Plugin +{ + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'LinkPreview', + 'version' => STATUSNET_VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:LinkPreview', + 'rawdescription' => + _m('UI extensions previewing thumbnails from links.')); + + return true; + } + + /** + * Load JS at runtime if we're logged in. + * + * @param Action $action + * @return boolean hook result + */ + function onEndShowScripts($action) + { + $user = common_current_user(); + if ($user && common_config('attachments', 'process_links')) { + $action->script('plugins/LinkPreview/linkpreview.js'); + $data = json_encode(array( + 'api' => common_local_url('oembedproxy'), + 'width' => common_config('attachments', 'thumbwidth'), + 'height' => common_config('attachments', 'thumbheight'), + )); + $action->inlineScript('$(function() {SN.Init.LinkPreview && SN.Init.LinkPreview('.$data.');})'); + } + return true; + } + + /** + * Autoloader + * + * Loads our classes if they're requested. + * + * @param string $cls Class requested + * + * @return boolean hook return + */ + function onAutoload($cls) + { + $lower = strtolower($cls); + switch ($lower) + { + case 'oembedproxyaction': + require_once dirname(__FILE__) . '/' . $lower . '.php'; + return false; + default: + return true; + } + } + + /** + * Hook for RouterInitialized event. + * + * @param Net_URL_Mapper $m URL mapper + * + * @return boolean hook return + */ + function onStartInitializeRouter($m) + { + $m->connect('main/oembed/proxy', + array('action' => 'oembedproxy')); + + return true; + } +} diff --git a/plugins/LinkPreview/linkpreview.js b/plugins/LinkPreview/linkpreview.js new file mode 100644 index 000000000..0c0eb734e --- /dev/null +++ b/plugins/LinkPreview/linkpreview.js @@ -0,0 +1,194 @@ +/** + * (c) 2010 StatusNet, Inc. + */ + +(function() { + var oEmbed = { + api: 'http://oohembed.com/oohembed', + width: 100, + height: 75, + cache: {}, + callbacks: {}, + + /** + * Do a cached oEmbed lookup for the given URL. + * + * @param {String} url + * @param {function} callback + */ + lookup: function(url, callback) + { + if (typeof oEmbed.cache[url] == "object") { + // We already have a successful lookup. + callback(oEmbed.cache[url]); + } else if (typeof oEmbed.callbacks[url] == "undefined") { + // No lookup yet... Start it! + oEmbed.callbacks[url] = [callback]; + + oEmbed.rawLookup(url, function(data) { + oEmbed.cache[url] = data; + var callbacks = oEmbed.callbacks[url]; + oEmbed.callbacks[url] = undefined; + for (var i = 0; i < callbacks.length; i++) { + callbacks[i](data); + } + }); + } else { + // A lookup is in progress. + oEmbed.callbacks[url].push(callback); + } + }, + + /** + * Do an oEmbed lookup for the given URL. + * + * @fixme proxy through ourselves if possible? + * @fixme use the global thumbnail size settings + * + * @param {String} url + * @param {function} callback + */ + rawLookup: function(url, callback) + { + var params = { + url: url, + format: 'json', + maxwidth: oEmbed.width, + maxheight: oEmbed.height, + token: $('#token').val() + }; + $.get(oEmbed.api, params, function(data, xhr) { + callback(data); + }, 'json'); + } + }; + + var LinkPreview = { + links: [], + + /** + * Find URL links from the source text that may be interesting. + * + * @param {String} text + * @return {Array} list of URLs + */ + findLinks: function (text) + { + // @fixme match this to core code + var re = /(?:^| )(https?:\/\/.+?\/.+?)(?= |$)/mg; + var links = []; + var matches; + while ((matches = re.exec(text)) !== null) { + links.push(matches[1]); + } + return links; + }, + + /** + * Start looking up info for a link preview... + * May start async data loads. + * + * @param {String} id + * @param {String} url + */ + prepLinkPreview: function(id, url) + { + oEmbed.lookup(url, function(data) { + var thumb = null; + var width = 100; + if (typeof data.thumbnail_url == "string") { + thumb = data.thumbnail_url; + if (typeof data.thumbnail_width !== "undefined") { + if (data.thumbnail_width < width) { + width = data.thumbnail_width; + } + } + } else if (data.type == 'photo' && typeof data.url == "string") { + thumb = data.url; + if (typeof data.width !== "undefined") { + if (data.width < width) { + width = data.width; + } + } + } + if (thumb) { + var link = $('<span class="inline-attachment"><a><img/></a></span>'); + link.find('a') + .attr('href', url) + .attr('target', '_blank') + .last() + .find('img') + .attr('src', thumb) + .attr('width', width) + .attr('title', data.title || data.url || url); + $('#' + id).append(link); + } + }); + }, + + /** + * Update the live preview section with links found in the given text. + * May start async data loads. + * + * @param {String} text: free-form input text + */ + previewLinks: function(text) + { + var old = LinkPreview.links; + var links = LinkPreview.findLinks(text); + + // Check for existing common elements... + for (var i = 0; i < old.length && i < links.length; i++) { + if (links[i] != old[i]) { + // Change an existing entry! + var id = 'link-preview-' + i; + $('#' + id).html(''); + LinkPreview.prepLinkPreview(id, links[i]); + } + } + if (links.length > old.length) { + // Adding new entries, whee! + for (var i = old.length; i < links.length; i++) { + var id = 'link-preview-' + i; + $('#link-preview').append('<span id="' + id + '"></span>'); + LinkPreview.prepLinkPreview(id, links[i]); + } + } else if (old.length > links.length) { + // Remove preview entries for links that have been removed. + for (var i = links.length; i < old.length; i++) { + var id = 'link-preview-' + i; + $('#' + id).remove(); + } + } + + LinkPreview.links = links; + }, + + /** + * Clear out any link preview data. + */ + clear: function() { + LinkPreview.links = []; + $('#link-preview').empty(); + } + }; + + SN.Init.LinkPreview = function(params) { + if (params.api) oEmbed.api = params.api; + if (params.width) oEmbed.width = params.width; + if (params.height) oEmbed.height = params.height; + + $('#form_notice') + .append('<div id="link-preview" class="thumbnails"></div>') + .bind('reset', function() { + LinkPreview.clear(); + }); + + // Piggyback on the counter update... + var origCounter = SN.U.Counter; + SN.U.Counter = function(form) { + LinkPreview.previewLinks($('#notice_data-text').val()); + return origCounter(form); + } + } +})(); diff --git a/plugins/LinkPreview/oembedproxyaction.php b/plugins/LinkPreview/oembedproxyaction.php new file mode 100644 index 000000000..470f78073 --- /dev/null +++ b/plugins/LinkPreview/oembedproxyaction.php @@ -0,0 +1,84 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * StatusNet-only extensions to the Twitter-like API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package StatusNet + * @author Brion Vibber <brion@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Oembed proxy implementation + * + * This class provides an interface for our JS-side code to pull info on + * links from other sites, using either native oEmbed, our own custom + * handlers, or the oohEmbed.com offsite proxy service as configured. + * + * @category oEmbed + * @package StatusNet + * @author Brion Vibber <brion@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class OembedproxyAction extends OembedAction +{ + + function handle($args) + { + // We're not a general oEmbed proxy service; limit to valid sessions. + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token. '. + 'Try again, please.')); + } + + $format = $this->arg('format'); + if ($format && $format != 'json') { + throw new ClientException('Invalid format; only JSON supported.'); + } + + $url = $this->arg('url'); + if (!common_valid_http_url($url)) { + throw new ClientException('Invalid URL.'); + } + + $params = array(); + if ($this->arg('maxwidth')) { + $params['maxwidth'] = $this->arg('maxwidth'); + } + if ($this->arg('maxheight')) { + $params['maxheight'] = $this->arg('maxheight'); + } + + $data = oEmbedHelper::getObject($url, $params); + + $this->init_document('json'); + print json_encode($data); + } + +} |