diff options
-rw-r--r-- | etherpad/src/etherpad/utils.js | 23 | ||||
-rw-r--r-- | etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js | 206 | ||||
-rw-r--r-- | etherpad/src/plugins/twitterStyleTags/models/tagQuery.js | 227 | ||||
-rw-r--r-- | etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs | 58 | ||||
-rw-r--r-- | etherpad/src/plugins/urlIndexer/controllers/urlBrowser.js | 132 | ||||
-rw-r--r-- | etherpad/src/plugins/urlIndexer/hooks.js | 5 | ||||
-rw-r--r-- | etherpad/src/plugins/urlIndexer/main.js | 3 | ||||
-rw-r--r-- | etherpad/src/plugins/urlIndexer/templates/urlBrowser.ejs | 53 |
8 files changed, 477 insertions, 230 deletions
diff --git a/etherpad/src/etherpad/utils.js b/etherpad/src/etherpad/utils.js index 1d139ed..65ebe1f 100644 --- a/etherpad/src/etherpad/utils.js +++ b/etherpad/src/etherpad/utils.js @@ -70,11 +70,21 @@ function findExistsingFile(files) { function findTemplate(filename, plugin) { var files = []; - if (plugin != undefined) { - files.push('/plugins/' + plugin + '/templates/' + filename); - files.push('/themes/' + appjet.config.theme + '/plugins/' + plugin + '/templates/' + filename); - files.push('/themes/default/plugins/' + plugin + '/templates/' + filename); - } + var pluginList = [plugin]; + try { + if (plugin.forEach !== undefined) + pluginList = plugin; + else + pluginList = [plugin]; + } catch (e) {} + + pluginList.forEach(function (plugin) { + if (plugin != undefined) { + files.push('/plugins/' + plugin + '/templates/' + filename); + files.push('/themes/' + appjet.config.theme + '/plugins/' + plugin + '/templates/' + filename); + files.push('/themes/default/plugins/' + plugin + '/templates/' + filename); + } + }); files.push('/themes/' + appjet.config.theme + '/templates/' + filename); files.push('/themes/default/templates/' + filename); @@ -110,7 +120,8 @@ function renderTemplateAsString(filename, data, plugin) { data = data || {}; data.helpers = helpers; // global helpers data.plugins = plugins; // Access callHook and the like... - var template = new Template(data, plugin); + if (data.template == undefined) + new Template(data, plugin); var f = findTemplate(filename, plugin); //"/templates/"+filename; if (! appjet.scopeCache.ejs) { diff --git a/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js b/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js index 7071306..f0b7470 100644 --- a/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js +++ b/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js @@ -15,6 +15,8 @@ * limitations under the License. */ +import("plugins.twitterStyleTags.models.tagQuery"); + import("faststatic"); import("dispatch.{Dispatcher,PrefixMatcher,forward}"); @@ -31,206 +33,19 @@ import("sqlbase.sqlcommon"); import("sqlbase.sqlobj"); import("etherpad.pad.padutils"); -function tagsToQuery(tags, antiTags) { - var prefixed = []; - for (i = 0; i < antiTags.length; i++) - prefixed[i] = '!' + antiTags[i]; - return tags.concat(prefixed).join(','); -} - -function stringFormat(text, obj) { - var name; - for (name in obj) { - //iterate through the params and replace their placeholders from the original text - text = text.replace(new RegExp('%\\(' + name + '\\)s', 'gi' ), obj[name]); - } - return text; -} - -/* All these sql query functions both takes a querySql object as - * parameter and returns one. This object has two members - sql and - * params. Sql is a string of an sql table name or a subqyery in - * parens. The table pr subquery should have an ID column containing a - * PAD_ID. - */ - -/* Filters pads by tags and anti-tags */ -function getQueryToSql(tags, antiTags, querySql) { - var queryTable; - var queryParams; - - if (querySql == null) { - queryTable = 'PAD_META'; - queryParams = []; - } else { - queryTable = querySql.sql; - queryParams = querySql.params; - } - - var exceptArray = []; - var joinArray = []; - var whereArray = []; - var exceptParamArray = []; - var joinParamArray = []; - - var info = new Object(); - info.queryTable = queryTable; - info.n = 0; - var i; - - for (i = 0; i < antiTags.length; i++) { - tag = antiTags[i]; - exceptArray.push( - stringFormat( - 'left join (PAD_TAG as pt%(n)s ' + - ' join TAG AS t%(n)s on ' + - ' t%(n)s.NAME = ? ' + - ' and t%(n)s.ID = pt%(n)s.TAG_ID) on ' + - ' pt%(n)s.PAD_ID = p.ID ', - info)); - whereArray.push(stringFormat('pt%(n)s.TAG_ID is null', info)); - exceptParamArray.push(tag); - info.n += 1; - } - for (i = 0; i < tags.length; i++) { - tag = tags[i]; - joinArray.push( - stringFormat( - 'join PAD_TAG as pt%(n)s on ' + - ' pt%(n)s.PAD_ID = p.ID ' + - 'join TAG as t%(n)s on ' + - ' t%(n)s.ID = pt%(n)s.TAG_ID ' + - ' and t%(n)s.NAME = ? ', - info)); - joinParamArray.push(tag); - info.n += 1; - } - - info["joins"] = joinArray.join(' '); - info["excepts"] = exceptArray.join(' '); - info["wheres"] = whereArray.length > 0 ? ' where ' + whereArray.join(' and ') : ''; - - /* Create a subselect from all the joins */ - return { - sql: stringFormat( - '(select distinct ' + - ' p.ID ' + - ' from ' + - ' %(queryTable)s as p ' + - ' %(joins)s ' + - ' %(excepts)s ' + - ' %(wheres)s ' + - ') ', - info), - params: queryParams.concat(joinParamArray).concat(exceptParamArray)}; -} - -/* Returns the sql to count the number of results from some other - * query. */ -function nrSql(querySql) { - var queryTable; - var queryParams; - - if (querySql == null) { - queryTable = 'PAD_META'; - queryParams = []; - } else { - queryTable = querySql.sql; - queryParams = querySql.params; - } - - var info = []; - info['query_sql'] = queryTable - return { - sql: stringFormat('(select count(*) as total from %(query_sql)s as q)', info), - params: queryParams}; -} - -/* Returns the sql to select the 10 best new tags to tack on to a - * query, that is, the tags that are closest to halving the result-set - * if tacked on. */ -function newTagsSql(querySql) { - var queryTable; - var queryParams; - - if (querySql == null) { - queryTable = 'PAD_META'; - queryParams = []; - } else { - queryTable = querySql.sql; - queryParams = querySql.params; - } - - var info = []; - info["query_post_table"] = queryTable; - var queryNrSql = nrSql(querySql); - info["query_nr_sql"] = queryNrSql.sql; - queryNrParams = queryNrSql.params; - - return { - sql: stringFormat('' + - 'select ' + - ' t.NAME tagname, ' + - ' count(tp.PAD_ID) as matches, ' + - ' tn.total - count(tp.PAD_ID) as antimatches, ' + - ' abs(count(tp.PAD_ID) - (tn.total / 2)) as weight ' + - 'from ' + - ' TAG as t, ' + - ' PAD_TAG as tp, ' + - ' %(query_nr_sql)s as tn ' + - 'where ' + - ' tp.TAG_ID = t.ID ' + - ' and tp.PAD_ID in %(query_post_table)s ' + - ' and tp.PAD_ID NOT LIKE \'%$%\'' + - 'group by t.NAME, tn.total ' + - 'having ' + - ' count(tp.PAD_ID) > 0 and count(tp.PAD_ID) < tn.total ' + - 'order by ' + - ' abs(count(tp.PAD_ID) - (tn.total / 2)) asc ' + - 'limit 10 ' + - '', info), - params: queryNrParams.concat(queryParams)}; -} - function onRequest() { - var tags = new Array(); - var antiTags = new Array(); - - if (request.params.query != undefined && request.params.query != '') { - var query = request.params.query.split(','); - for (i = 0; i < query.length; i++) - if (query[i][0] == '!') - antiTags.push(query[i].substring(1)); - else - tags.push(query[i]); - } + var tags = tagQuery.queryToTags(request.params.query); /* Create the pad filter sql */ - var querySql = getQueryToSql(tags.concat(['public']), antiTags); + var querySql = tagQuery.getQueryToSql(tags.tags.concat(['public']), tags.antiTags); /* Use the pad filter sql to figure out which tags to show in the tag browser this time. */ - var queryNewTagsSql = newTagsSql(querySql); + var queryNewTagsSql = tagQuery.newTagsSql(querySql); var newTags = sqlobj.executeRaw(queryNewTagsSql.sql, queryNewTagsSql.params); - /* Select the 10 last changed matching pads and some extra information on them. Except the Pro Pads*/ - var sql = '' + - 'select ' + - ' m.id as ID, ' + - ' DATE_FORMAT(m.lastWriteTime, \'%a, %d %b %Y %H:%i:%s GMT\') as lastWriteTime, ' + - ' c.TAGS ' + - 'from ' + - querySql.sql + ' as q ' + - ' join PAD_SQLMETA as m on ' + - ' m.id = q.ID ' + - ' join PAD_TAG_CACHE as c on ' + - ' c.PAD_ID = q.ID ' + - 'where ' + - ' m.id NOT LIKE \'%$%\'' + - 'order by ' + - ' m.lastWriteTime desc ' + - 'limit 10'; - var matchingPads = sqlobj.executeRaw(sql, querySql.params); + padSql = tagQuery.padInfoSql(querySql, 10); + var matchingPads = sqlobj.executeRaw(padSql.sql, padSql.params); for (i = 0; i < matchingPads.length; i++) { matchingPads[i].TAGS = matchingPads[i].TAGS.split('#'); @@ -252,7 +67,6 @@ function onRequest() { var isProUser = (isPro && ! padusers.isGuest(userId)); - padutils.setOptsAndCookiePrefs(request); var prefs = helpers.getClientVar('cookiePrefsToSet'); var bodyClass = (prefs.isFullWidth ? "fullwidth" : "limwidth") @@ -260,10 +74,10 @@ function onRequest() { var info = { prefs: prefs, config: appjet.config, - tagsToQuery: tagsToQuery, + tagQuery: tagQuery, padIdToReadonly: server_utils.padIdToReadonly, - tags: tags, - antiTags: antiTags, + tags: tags.tags, + antiTags: tags.antiTags, newTags: newTags, matchingPads: matchingPads, bodyClass: 'nonpropad', diff --git a/etherpad/src/plugins/twitterStyleTags/models/tagQuery.js b/etherpad/src/plugins/twitterStyleTags/models/tagQuery.js new file mode 100644 index 0000000..8a32ef7 --- /dev/null +++ b/etherpad/src/plugins/twitterStyleTags/models/tagQuery.js @@ -0,0 +1,227 @@ +/** + * Copyright 2010 RedHog, Egil Möller <egil.moller@piratpartiet.se> + * Copyright 2010 Pita, Peter Martischka <petermartischka@googlemail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import("sqlbase.sqlbase"); +import("sqlbase.sqlcommon"); +import("sqlbase.sqlobj"); +import("etherpad.log"); + +function tagsToQuery(tags, antiTags) { + var prefixed = []; + for (i = 0; i < antiTags.length; i++) + prefixed[i] = '!' + antiTags[i]; + return tags.concat(prefixed).join(','); +} + +function queryToTags(query) { + var tags = { + tags: new Array(), + antiTags: new Array() + }; + + if (query != undefined && query != '') { + var query = query.split(','); + for (i = 0; i < query.length; i++) + if (query[i][0] == '!') + tags.antiTags.push(query[i].substring(1)); + else + tags.tags.push(query[i]); + } + return tags; +} + +function stringFormat(text, obj) { + var name; + for (name in obj) { + //iterate through the params and replace their placeholders from the original text + text = text.replace(new RegExp('%\\(' + name + '\\)s', 'gi' ), obj[name]); + } + return text; +} + +/* All these sql query functions both takes a querySql object as + * parameter and returns one. This object has two members - sql and + * params. Sql is a string of an sql table name or a subqyery in + * parens. The table pr subquery should have an ID column containing a + * PAD_ID. + */ + +/* Filters pads by tags and anti-tags */ +function getQueryToSql(tags, antiTags, querySql) { + var queryTable; + var queryParams; + + if (querySql == null) { + queryTable = 'PAD_META'; + queryParams = []; + } else { + queryTable = querySql.sql; + queryParams = querySql.params; + } + + var exceptArray = []; + var joinArray = []; + var whereArray = []; + var exceptParamArray = []; + var joinParamArray = []; + + var info = new Object(); + info.queryTable = queryTable; + info.n = 0; + var i; + + for (i = 0; i < antiTags.length; i++) { + tag = antiTags[i]; + exceptArray.push( + stringFormat( + 'left join (PAD_TAG as pt%(n)s ' + + ' join TAG AS t%(n)s on ' + + ' t%(n)s.NAME = ? ' + + ' and t%(n)s.ID = pt%(n)s.TAG_ID) on ' + + ' pt%(n)s.PAD_ID = p.ID ', + info)); + whereArray.push(stringFormat('pt%(n)s.TAG_ID is null', info)); + exceptParamArray.push(tag); + info.n += 1; + } + for (i = 0; i < tags.length; i++) { + tag = tags[i]; + joinArray.push( + stringFormat( + 'join PAD_TAG as pt%(n)s on ' + + ' pt%(n)s.PAD_ID = p.ID ' + + 'join TAG as t%(n)s on ' + + ' t%(n)s.ID = pt%(n)s.TAG_ID ' + + ' and t%(n)s.NAME = ? ', + info)); + joinParamArray.push(tag); + info.n += 1; + } + + info["joins"] = joinArray.join(' '); + info["excepts"] = exceptArray.join(' '); + info["wheres"] = whereArray.length > 0 ? ' where ' + whereArray.join(' and ') : ''; + + /* Create a subselect from all the joins */ + return { + sql: stringFormat( + '(select distinct ' + + ' p.ID ' + + ' from ' + + ' %(queryTable)s as p ' + + ' %(joins)s ' + + ' %(excepts)s ' + + ' %(wheres)s ' + + ') ', + info), + params: queryParams.concat(joinParamArray).concat(exceptParamArray)}; +} + +/* Returns the sql to count the number of results from some other + * query. */ +function nrSql(querySql) { + var queryTable; + var queryParams; + + if (querySql == null) { + queryTable = 'PAD_META'; + queryParams = []; + } else { + queryTable = querySql.sql; + queryParams = querySql.params; + } + + var info = []; + info['query_sql'] = queryTable + return { + sql: stringFormat('(select count(*) as total from %(query_sql)s as q)', info), + params: queryParams}; +} + +/* Returns the sql to select the 10 best new tags to tack on to a + * query, that is, the tags that are closest to halving the result-set + * if tacked on. */ +function newTagsSql(querySql) { + var queryTable; + var queryParams; + + if (querySql == null) { + queryTable = 'PAD_META'; + queryParams = []; + } else { + queryTable = querySql.sql; + queryParams = querySql.params; + } + + var info = []; + info["query_post_table"] = queryTable; + var queryNrSql = nrSql(querySql); + info["query_nr_sql"] = queryNrSql.sql; + queryNrParams = queryNrSql.params; + + return { + sql: stringFormat('' + + 'select ' + + ' t.NAME tagname, ' + + ' count(tp.PAD_ID) as matches, ' + + ' tn.total - count(tp.PAD_ID) as antimatches, ' + + ' abs(count(tp.PAD_ID) - (tn.total / 2)) as weight ' + + 'from ' + + ' TAG as t, ' + + ' PAD_TAG as tp, ' + + ' %(query_nr_sql)s as tn ' + + 'where ' + + ' tp.TAG_ID = t.ID ' + + ' and tp.PAD_ID in %(query_post_table)s ' + + ' and tp.PAD_ID NOT LIKE \'%$%\'' + + 'group by t.NAME, tn.total ' + + 'having ' + + ' count(tp.PAD_ID) > 0 and count(tp.PAD_ID) < tn.total ' + + 'order by ' + + ' abs(count(tp.PAD_ID) - (tn.total / 2)) asc ' + + 'limit 10 ' + + '', info), + params: queryNrParams.concat(queryParams)}; +} + +/* Select the X last changed matching pads and some extra information + * on them. Except the Pro Pads*/ +function padInfoSql(querySql, limit, offset) { + var sql = '' + + 'select ' + + ' m.id as ID, ' + + ' DATE_FORMAT(m.lastWriteTime, \'%a, %d %b %Y %H:%i:%s GMT\') as lastWriteTime, ' + + ' c.TAGS ' + + 'from ' + + querySql.sql + ' as q ' + + ' join PAD_SQLMETA as m on ' + + ' m.id = q.ID ' + + ' join PAD_TAG_CACHE as c on ' + + ' c.PAD_ID = q.ID ' + + 'where ' + + ' m.id NOT LIKE \'%$%\'' + + 'order by ' + + ' m.lastWriteTime desc '; + if (limit != undefined) + sql += 'limit ' + limit + " "; + if (offset != undefined) + sql += 'offset ' + offset + " "; + return { + sql: sql, + params: querySql.params + }; +} diff --git a/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs b/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs index 955d2e6..c5b6215 100644 --- a/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs +++ b/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs @@ -18,7 +18,7 @@ limitations under the License. */ %> helpers.setHtmlTitle("EtherPad: Browse tags"); helpers.includeCss("plugins/twitterStyleTags/tagBrowser.css"); helpers.includeCss("plugins/twitterStyleTags/pad.css"); - helpers.addToHead('\n<link rel="alternate" href="/ep/tag/?query=' + tagsToQuery(tags, antiTags) + '&format=rss" type="application/rss+xml" title="Query results as RSS" />\n'); + helpers.addToHead('\n<link rel="alternate" href="/ep/tag/?query=' + tagQuery.tagsToQuery(tags, antiTags) + '&format=rss" type="application/rss+xml" title="Query results as RSS" />\n'); function inArray(item, arr) { for (var i = 0; i < arr.length; i++) @@ -59,10 +59,10 @@ limitations under the License. */ %> Latest changed pads <% } else { %> <% for (i = 0; i < tags.length; i++) { %> - <a href="/ep/tag/?query=<%= tagsToQuery(tags.filter(function (tag) { return tag != tags[i]}), antiTags) %>" class="padtag" title="<%= tags[i] %> matches">#<%= tags[i] %></a> + <a href="/ep/tag/?query=<%= tagQuery.tagsToQuery(tags.filter(function (tag) { return tag != tags[i]}), antiTags) %>" class="padtag" title="<%= tags[i] %> matches">#<%= tags[i] %></a> <% } %> <% for (i = 0; i < antiTags.length; i++) { %> - <a href="/ep/tag/?query=<%= tagsToQuery(tags, antiTags.filter(function (tag) { return tag != antiTags[i]})) %>" class="anti_padtag" title="<%= antiTags[i] %> matches">!#<%= antiTags[i] %></a> + <a href="/ep/tag/?query=<%= tagQuery.tagsToQuery(tags, antiTags.filter(function (tag) { return tag != antiTags[i]})) %>" class="anti_padtag" title="<%= antiTags[i] %> matches">!#<%= antiTags[i] %></a> <% } %> <% } %> </td> @@ -71,34 +71,38 @@ limitations under the License. */ %> <% template.define('contentArea', function() { var ejs_data=''; %> <div id="editorcontainer"> <div class="query-refiner"> - <h1>Search for pads that have the tag</h1> - <% for (i = 0; i < newTags.length; i++) { %> - <a href="/ep/tag/?query=<%= tagsToQuery(tags.concat([newTags[i].tagname]),antiTags) %>" class="padtag" title="<%= newTags[i].matches %> matches">#<%= newTags[i].tagname %></a> - <% } %> + <%: template.use('queryRefiner', function() { var ejs_data=''; %> + <h1>Search for pads that have the tag</h1> + <% for (i = 0; i < newTags.length; i++) { %> + <a href="/ep/tag/?query=<%= tagQuery.tagsToQuery(tags.concat([newTags[i].tagname]),antiTags) %>" class="padtag" title="<%= newTags[i].matches %> matches">#<%= newTags[i].tagname %></a> + <% } %> - <h1>Search for pads that <em>don't</em> have the tag</h1> - <% for (i = 0; i < newTags.length; i++) { %> - <a href="/ep/tag/?query=<%= tagsToQuery(tags,antiTags.concat([newTags[i].tagname])) %>" class="anti_padtag" title="<%= newTags[i].antimatches %> matches">!#<%= newTags[i].tagname %></a> - <% } %> + <h1>Search for pads that <em>don't</em> have the tag</h1> + <% for (i = 0; i < newTags.length; i++) { %> + <a href="/ep/tag/?query=<%= tagQuery.tagsToQuery(tags,antiTags.concat([newTags[i].tagname])) %>" class="anti_padtag" title="<%= newTags[i].antimatches %> matches">!#<%= newTags[i].tagname %></a> + <% } %> + <% return ejs_data; }); %> </div> <dl> - <% for (i = 0; i < matchingPads.length; i++) { %> - <% - var matchingPadId = matchingPads[i].ID; - var matchingPadUrl = matchingPadId; - if (!inArray('writable', matchingPads[i].TAGS)) { - matchingPadId = padIdToReadonly(matchingPads[i].ID); - matchingPadUrl = 'ep/pad/view/' + matchingPadId + '/latest'; - } - %> - <dt><a href="/<%= matchingPadUrl %>"><%= matchingPadId %></a><dt> - <dd> - <% for (j = 0; j < matchingPads[i].TAGS.length; j++) { %> - <a href="/ep/tag/?query=<%= tagsToQuery(tags.concat([matchingPads[i].TAGS[j]]), antiTags) %>" class="padtag" title="<%= matchingPads[i].TAGS[j] %> matches">#<%= matchingPads[i].TAGS[j] %></a> - <% } %> - </dd> - <% } %> + <%: template.use('queryResult', function() { var ejs_data=''; %> + <% for (i = 0; i < matchingPads.length; i++) { %> + <% + var matchingPadId = matchingPads[i].ID; + var matchingPadUrl = matchingPadId; + if (!inArray('writable', matchingPads[i].TAGS)) { + matchingPadId = padIdToReadonly(matchingPads[i].ID); + matchingPadUrl = 'ep/pad/view/' + matchingPadId + '/latest'; + } + %> + <dt><a href="/<%= matchingPadUrl %>"><%= matchingPadId %></a><dt> + <dd> + <% for (j = 0; j < matchingPads[i].TAGS.length; j++) { %> + <a href="/ep/tag/?query=<%= tagQuery.tagsToQuery(tags.concat([matchingPads[i].TAGS[j]]), antiTags) %>" class="padtag" title="<%= matchingPads[i].TAGS[j] %> matches">#<%= matchingPads[i].TAGS[j] %></a> + <% } %> + </dd> + <% } %> + <% return ejs_data; }); %> </dl> </div> <% return ejs_data; }); %> diff --git a/etherpad/src/plugins/urlIndexer/controllers/urlBrowser.js b/etherpad/src/plugins/urlIndexer/controllers/urlBrowser.js new file mode 100644 index 0000000..ebd39bd --- /dev/null +++ b/etherpad/src/plugins/urlIndexer/controllers/urlBrowser.js @@ -0,0 +1,132 @@ +/** + * Copyright 2009 RedHog, Egil Möller <egil.moller@piratpartiet.se> + * Copyright 2010 Pita, Peter Martischka <petermartischka@googlemail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import("plugins.twitterStyleTags.models.tagQuery"); + +import("faststatic"); +import("dispatch.{Dispatcher,PrefixMatcher,forward}"); + +import("etherpad.utils.*"); +import("etherpad.collab.server_utils"); +import("etherpad.globals.*"); +import("etherpad.log"); +import("etherpad.pad.padusers"); +import("etherpad.pro.pro_utils"); +import("etherpad.helpers"); +import("etherpad.pro.pro_accounts.getSessionProAccount"); +import("sqlbase.sqlbase"); +import("sqlbase.sqlcommon"); +import("sqlbase.sqlobj"); +import("etherpad.pad.padutils"); + +function urlSql(querySql, limit, offset) { + var sql = '' + + 'select ' + + ' u.URL, ' + + ' m.id as ID, ' + + ' DATE_FORMAT(m.lastWriteTime, \'%a, %d %b %Y %H:%i:%s GMT\') as lastWriteTime, ' + + ' c.TAGS ' + + 'from ' + + querySql.sql + ' as q ' + + ' join PAD_SQLMETA as m on ' + + ' m.id = q.ID ' + + ' join PAD_TAG_CACHE as c on ' + + ' c.PAD_ID = q.ID ' + + ' join PAD_URL as u on ' + + ' u.PAD_ID = q.ID ' + + 'where ' + + ' m.id NOT LIKE \'%$%\'' + + 'order by ' + + ' u.URL asc '; + if (limit != undefined) + sql += 'limit ' + limit + " "; + if (offset != undefined) + sql += 'offset ' + offset + " "; + return { + sql: sql, + params: querySql.params + }; +} + +function onRequest() { + var tags = tagQuery.queryToTags(request.params.query); + + /* Create the pad filter sql */ + var querySql = tagQuery.getQueryToSql(tags.tags.concat(['public']), tags.antiTags); + + /* Use the pad filter sql to figure out which tags to show in the tag browser this time. */ + var queryNewTagsSql = tagQuery.newTagsSql(querySql); + var newTags = sqlobj.executeRaw(queryNewTagsSql.sql, queryNewTagsSql.params); + + urlSql = urlSql(querySql, 10); + var matchingUrls = sqlobj.executeRaw(urlSql.sql, urlSql.params); + + for (i = 0; i < matchingUrls.length; i++) { + matchingUrls[i].TAGS = matchingUrls[i].TAGS.split('#'); + } + + var isPro = pro_utils.isProDomainRequest(); + var userId = padusers.getUserId(); + + helpers.addClientVars({ + userAgent: request.headers["User-Agent"], + debugEnabled: request.params.djs, + clientIp: request.clientAddr, + colorPalette: COLOR_PALETTE, + serverTimestamp: +(new Date), + isProPad: isPro, + userIsGuest: padusers.isGuest(userId), + userId: userId, + }); + + var isProUser = (isPro && ! padusers.isGuest(userId)); + + padutils.setOptsAndCookiePrefs(request); + var prefs = helpers.getClientVar('cookiePrefsToSet'); + var bodyClass = (prefs.isFullWidth ? "fullwidth" : "limwidth") + + var info = { + prefs: prefs, + config: appjet.config, + tagQuery: tagQuery, + padIdToReadonly: server_utils.padIdToReadonly, + tags: tags.tags, + antiTags: tags.antiTags, + newTags: newTags, + matchingPads: [], + matchingUrls: matchingUrls, + bodyClass: 'nonpropad', + isPro: isPro, + isProAccountHolder: isProUser, + account: getSessionProAccount(), // may be falsy + }; + + var format = "html"; + if (request.params.format != undefined) + format = request.params.format; + + if (format == "html") + renderHtml("urlBrowser.ejs", info, ['urlIndexer', 'twitterStyleTags']); + else if (format == "rss") { + response.setContentType("application/xml; charset=utf-8"); + response.write(renderTemplateAsString("tagRss.ejs", info, 'urlIndexer')); + if (request.acceptsGzip) { + response.setGzip(true); + } + } + return true; +} diff --git a/etherpad/src/plugins/urlIndexer/hooks.js b/etherpad/src/plugins/urlIndexer/hooks.js index 1429895..45d37f1 100644 --- a/etherpad/src/plugins/urlIndexer/hooks.js +++ b/etherpad/src/plugins/urlIndexer/hooks.js @@ -1,6 +1,11 @@ import("etherpad.log"); import("dispatch.{Dispatcher,PrefixMatcher,forward}"); import("sqlbase.sqlobj"); +import("plugins.urlIndexer.controllers.urlBrowser"); + +function handlePath() { + return [[PrefixMatcher('/ep/url'), forward(urlBrowser)]]; +} REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; REGEX_URLCHAR = new RegExp('('+/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source+'|'+REGEX_WORDCHAR.source+')'); diff --git a/etherpad/src/plugins/urlIndexer/main.js b/etherpad/src/plugins/urlIndexer/main.js index 79bb019..0b2847f 100644 --- a/etherpad/src/plugins/urlIndexer/main.js +++ b/etherpad/src/plugins/urlIndexer/main.js @@ -4,9 +4,10 @@ import("sqlbase.sqlobj"); import("sqlbase.sqlcommon"); function init() { - this.hooks = ['padModelWriteToDB']; + this.hooks = ['padModelWriteToDB', 'handlePath']; this.description = 'Indexes URLs linked to in pads so that they can be displayed outside pads, searched for etc.'; this.padModelWriteToDB = hooks.padModelWriteToDB; + this.handlePath = hooks.handlePath; this.install = install; this.uninstall = uninstall; diff --git a/etherpad/src/plugins/urlIndexer/templates/urlBrowser.ejs b/etherpad/src/plugins/urlIndexer/templates/urlBrowser.ejs new file mode 100644 index 0000000..a254dc1 --- /dev/null +++ b/etherpad/src/plugins/urlIndexer/templates/urlBrowser.ejs @@ -0,0 +1,53 @@ +<% /* +Copyright 2009 Google Inc. +Copyright 2010 Pita, Peter Martischka <petermartischka@googlemail.com> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ %> +<% + template.inherit('tagBrowser.ejs'); + helpers.setHtmlTitle("EtherPad: Browse URLs by tags"); + helpers.includeCss("plugins/twitterStyleTags/tagBrowser.css"); + helpers.includeCss("plugins/twitterStyleTags/pad.css"); + helpers.addToHead('\n<link rel="alternate" href="/ep/url/?query=' + tagQuery.tagsToQuery(tags, antiTags) + '&format=rss" type="application/rss+xml" title="Query results as RSS" />\n'); + + function inArray(item, arr) { + for (var i = 0; i < arr.length; i++) + if (arr[i] == item) + return true; + return false; + } +%> + +<% template.define('docBarTitle', function() { var ejs_data=''; %> + <td id="docbarpadtitle"><span>Browse URLs by tags</span></td> +<% return ejs_data; }); %> + +<% template.define('queryResult', function() { var ejs_data=''; %> + <% for (i = 0; i < matchingUrls.length; i++) { %> + <% + var matchingPadId = matchingUrls[i].ID; + var matchingPadUrl = matchingPadId; + if (!inArray('writable', matchingUrls[i].TAGS)) { + matchingPadId = padIdToReadonly(matchingUrls[i].ID); + matchingPadUrl = 'ep/pad/view/' + matchingPadId + '/latest'; + } + %> + <dt><a href="<%= matchingUrls[i].URL %>"><%= matchingUrls[i].URL %></a><dt> + <dd> + <a href="<%= matchingPadUrl %>"><%= matchingPadId %></a>: + <% for (j = 0; j < matchingUrls[i].TAGS.length; j++) { %> + <a href="/ep/url/?query=<%= tagQuery.tagsToQuery(tags.concat([matchingUrls[i].TAGS[j]]), antiTags) %>" class="padtag" title="<%= matchingUrls[i].TAGS[j] %> matches">#<%= matchingUrls[i].TAGS[j] %></a> + <% } %> + </dd> + <% } %> +<% return ejs_data; }); %> |