diff options
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&&pad.editbarClick('bold'));" class="editbarbutton bold" title="Bold (ctrl-B)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('italic'));" class="editbarbutton italic" title="Italics (ctrl-I)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('underline'));" class="editbarbutton underline" title="Underline (ctrl-U)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('strikethrough'));" class="editbarbutton strikethrough" title="Strikethrough"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('clearauthorship'));" class="editbarbutton clearauthorship" title="Clear Authorship Colors"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('undo'));" class="editbarbutton undo" title="Undo (ctrl-Z)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('redo'));" class="editbarbutton redo" title="Redo (ctrl-Y)"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('insertunorderedlist'));" class="editbarbutton insertunorderedlist" title="Toggle Bullet List"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('indent'));" class="editbarbutton indent" title="Indent List"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('outdent'));" class="editbarbutton outdent" title="Unindent List"> </a> + <a unselectable="on" href="javascript:void (window.pad&&pad.editbarClick('save'));" class="editbarbutton save" title="Save Revision"> </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) { %> + < No current query; please select some tags below to search for 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="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> |