aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js184
-rw-r--r--etherpad/src/plugins/twitterStyleTags/hooks.js2
-rw-r--r--etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs118
3 files changed, 301 insertions, 3 deletions
diff --git a/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js b/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js
index bf7c5b7..f5e63f7 100644
--- a/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js
+++ b/etherpad/src/plugins/twitterStyleTags/controllers/tagBrowser.js
@@ -29,8 +29,184 @@ import("sqlbase.sqlbase");
import("sqlbase.sqlcommon");
import("sqlbase.sqlobj");
+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;
+}
+
+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 ') : '';
+
+ 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)};
+}
+
+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};
+}
+
+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 ' +
+ '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 querySql = getQueryToSql(tags.concat(['public']), antiTags);
+ //log.info(querySql.sql);
+
+ var queryNewTagsSql = newTagsSql(querySql);
+ var newTags = sqlobj.executeRaw(queryNewTagsSql.sql, queryNewTagsSql.params);
+
+ var matchingPads;
+ if (tags.length > 0 || antiTags.length > 0) {
+ var sql = "select p.PAD_ID as ID, p.TAGS from PAD_TAG_CACHE as p, " + querySql.sql + " as q where p.PAD_ID = q.ID limit 10"
+ matchingPads = sqlobj.executeRaw(sql, querySql.params);
+ } else {
+ matchingPads = [];
+ }
+
+ for (i = 0; i < matchingPads.length; i++) {
+ matchingPads[i].TAGS = matchingPads[i].TAGS.split('#');
+ }
+
var isPro = pro_utils.isProDomainRequest();
var userId = padusers.getUserId();
@@ -49,6 +225,14 @@ function onRequest() {
renderHtml("tagBrowser.ejs",
{
+ config: appjet.config,
+ tagsToQuery: tagsToQuery,
+ padIdToReadonly: server_utils.padIdToReadonly,
+ tags: tags,
+ antiTags: antiTags,
+ newTags: newTags,
+ matchingPads: matchingPads,
+ bodyClass: 'nonpropad',
isPro: isPro,
isProAccountHolder: isProUser,
account: getSessionProAccount(), // may be falsy
diff --git a/etherpad/src/plugins/twitterStyleTags/hooks.js b/etherpad/src/plugins/twitterStyleTags/hooks.js
index 86875fc..a5513f0 100644
--- a/etherpad/src/plugins/twitterStyleTags/hooks.js
+++ b/etherpad/src/plugins/twitterStyleTags/hooks.js
@@ -4,7 +4,7 @@ import("plugins.twitterStyleTags.controllers.tagBrowser");
import("sqlbase.sqlobj");
function handlePath() {
- return [[PrefixMatcher('/ep/tags/'), forward(tagBrowser)]];
+ return [[PrefixMatcher('/ep/tag/'), forward(tagBrowser)]];
}
function padModelWriteToDB(args) {
diff --git a/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs b/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs
index aed5d9b..ba00aa2 100644
--- a/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs
+++ b/etherpad/src/plugins/twitterStyleTags/templates/tagBrowser.ejs
@@ -12,7 +12,7 @@ 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. */ %>
<%
- helpers.setHtmlTitle("Test plugin");
+ helpers.setHtmlTitle("Browse tags");
helpers.setBodyId("padbody");
helpers.addBodyClass("limwidth nonpropad nonprouser");
helpers.includeCss("pad2_ejs.css");
@@ -21,8 +21,122 @@ limitations under the License. */ %>
helpers.includeCometJs();
helpers.includeJs("json2.js");
helpers.addToHead('\n<style type="text/css" title="dynamicsyntax"></style>\n');
+
+ function inArray(item, arr) {
+ for (var i = 0; i < arr.length; i++)
+ if (arr[i] == item)
+ return true;
+ return false;
+ }
%>
<div id="padpage">
- Welcome to the tag browser plugin
+ <div id="padtop">
+ <div id="topbar" style="margin: 7px; margin-top: 0px;">
+ <div id="topbarleft"><!-- --></div>
+ <div id="topbarright"><!-- --></div>
+ <div id="topbarcenter"><a href="/" id="topbaretherpad">EtherPad</a></div>
+ <% if (isProAccountHolder) { %>
+ <div id="accountnav"><%= toHTML(account.email) %><a href="/ep/account/sign-out">(sign out)</a></div>
+ <% } else if (isPro) { %>
+ <div id="accountnav"><a href="<%= signinUrl %>">sign in</a></div>
+ <% } %>
+ </div>
+ </div>
+ <div id="docbar" class="docbar-public">
+ <div id="docbarleft"><!-- --></div>
+ <div title="Browse pads by tag" id="docbarpadtitle"><span>Browse tags</span></div>
+
+ <div id="docbaroptions-outer"><a href="javascript:void(0)" id="docbaroptions">Pad Options</a></div>
+ <div id="docbarsavedrevs-outer"><a href="javascript:void(0)" id="docbarsavedrevs">Saved revisions</a></div>
+ <div id="docbarimpexp-outer"><a href="javascript:void(0)" id="docbarimpexp">Import/Export</a></div>
+ <div id="docbarslider-outer"><a target="_blank" href="/ep/pad/view/xx/latest" id="docbarslider">Time Slider</a></div>
+
+ </div>
+ </div>
+ <div id="padmain">
+
+ <div id="padsidebar">
+ <div id="padusers">
+ <a href="/ep/pad/newpad" id="home-newpad">
+ Create new pad
+ </a>
+ </div>
+
+ <div id="hdraggie"><!-- --></div>
+
+ <div id="padchat"><iframe src="<%= config['motdPage'] %>" width="100%" height="100%"></iframe></div>
+ </div> <!-- /padsidebar -->
+
+ <div id="padeditor">
+ <div id="editbar" class="enabledtoolbar">
+ <div id="editbarleft"><!-- --></div>
+ <div id="editbarright"><!-- --></div>
+
+ <div id="editbarinner">
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('bold'));" class="editbarbutton bold" title="Bold (ctrl-B)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('italic'));" class="editbarbutton italic" title="Italics (ctrl-I)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('underline'));" class="editbarbutton underline" title="Underline (ctrl-U)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('strikethrough'));" class="editbarbutton strikethrough" title="Strikethrough">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('clearauthorship'));" class="editbarbutton clearauthorship" title="Clear Authorship Colors">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('undo'));" class="editbarbutton undo" title="Undo (ctrl-Z)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('redo'));" class="editbarbutton redo" title="Redo (ctrl-Y)">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('insertunorderedlist'));" class="editbarbutton insertunorderedlist" title="Toggle Bullet List">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('indent'));" class="editbarbutton indent" title="Indent List">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('outdent'));" class="editbarbutton outdent" title="Unindent List">&nbsp;</a>
+ <a unselectable="on" href="javascript:void (window.pad&amp;&amp;pad.editbarClick('save'));" class="editbarbutton save" title="Save Revision">&nbsp;</a>
+ </div>
+ </div>
+ <div style="height: 268px;" id="editorcontainerbox">
+ <div id="editorcontainer" style="padding:5pt; height: 600pt;">
+ <h1>Current query</h1>
+ <% if (tags.length == 0 && antiTags.length == 0) { %>
+ &lt; No current query; please select some tags below to search for pads &gt;
+ <% } else { %>
+ <% for (i = 0; i < tags.length; i++) { %>
+ <a href="/ep/tag/?query=<%= tagsToQuery(tags.filter(function (tag) { return tag != tags[i]}), antiTags) %>" class="tag" 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="tag" title="<%= antiTags[i] %> matches">!<%= antiTags[i] %></a>
+ <% } %>
+ <% } %>
+ <h1>Refine your query</h1>
+ <h2>Search for pads that have a tag</h2>
+ <% for (i = 0; i < newTags.length; i++) { %>
+ <a href="/ep/tag/?query=<%= tagsToQuery(tags.concat([newTags[i].tagname]),antiTags) %>" class="tag" title="<%= newTags[i].matches %> matches"><%= newTags[i].tagname %></a>
+ <% } %>
+
+ <h2>Search for pads that <em>doesn't</em> have a tag</h2>
+ <% for (i = 0; i < newTags.length; i++) { %>
+ <a href="/ep/tag/?query=<%= tagsToQuery(tags,antiTags.concat([newTags[i].tagname])) %>" class="anti_tag" title="<%= newTags[i].antimatches %> matches"><%= newTags[i].tagname %></a>
+ <% } %>
+
+ <h1>Matching pads</h1>
+ <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.filter(function (tag) { return tag != matchingPads[i].TAGS[j]}), antiTags) %>" class="tag" title="<%= matchingPads[i].TAGS[j] %> matches"><%= matchingPads[i].TAGS[j] %></a>
+ <% } %>
+ </dd>
+ <% } %>
+ </dl>
+ </div>
+ </div>
+ </div><!-- /padeditor -->
+
+ <div id="bottomarea">
+ <div id="widthprefcheck" class="widthprefunchecked"><!-- --></div>
+ <div id="sidebarcheck" class="sidebarchecked"><!-- --></div>
+ </div>
+ </div>
</div>