| 1 | let PLUGIN_INFO = |
|---|
| 2 | <VimperatorPlugin> |
|---|
| 3 | <name>{NAME}</name> |
|---|
| 4 | <description>search DeliciousBookmark and that completer</description> |
|---|
| 5 | <require type="extension" id="{2fa4ed95-0317-4c6a-a74c-5f3e3912c1f9}">Delicious Bookmarks</require> |
|---|
| 6 | <author mail="teramako@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/teramako/">teramako</author> |
|---|
| 7 | <version>0.2</version> |
|---|
| 8 | <minVersion>2.0pre</minVersion> |
|---|
| 9 | <maxVersion>2.0</maxVersion> |
|---|
| 10 | <detail><![CDATA[ |
|---|
| 11 | == Command == |
|---|
| 12 | :ds[earch] -tags tag, ...: |
|---|
| 13 | :delicious[search] -tags tag, ...: |
|---|
| 14 | search bookmark contains all tags |
|---|
| 15 | |
|---|
| 16 | :ds[earch] -query term: |
|---|
| 17 | :delicious[search] -query term: |
|---|
| 18 | search bookmark contains term in the titile or the URL or the note |
|---|
| 19 | |
|---|
| 20 | == Completion == |
|---|
| 21 | :open or :tabopen command completion |
|---|
| 22 | |
|---|
| 23 | >|| |
|---|
| 24 | set complete+=D |
|---|
| 25 | ||< |
|---|
| 26 | |
|---|
| 27 | or write in RC file |
|---|
| 28 | |
|---|
| 29 | >|| |
|---|
| 30 | autocmd VimperatorEnter ".*" :set complete+=D |
|---|
| 31 | ||< |
|---|
| 32 | |
|---|
| 33 | ]]></detail> |
|---|
| 34 | </VimperatorPlugin>; |
|---|
| 35 | |
|---|
| 36 | liberator.plugins.delicious = (function(){ |
|---|
| 37 | |
|---|
| 38 | let uuid = PLUGIN_INFO.require[0].@id.toString(); |
|---|
| 39 | if (Application.extensions.has(uuid) && Application.extensions.get(uuid).enabled){ |
|---|
| 40 | const ydls = Cc["@yahoo.com/nsYDelLocalStore;1"].getService(Ci.nsIYDelLocalStore); |
|---|
| 41 | } else { |
|---|
| 42 | return null; |
|---|
| 43 | } |
|---|
| 44 | const ss = Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService); |
|---|
| 45 | |
|---|
| 46 | // dabase connection object |
|---|
| 47 | let dbc = null; |
|---|
| 48 | |
|---|
| 49 | // sql statements |
|---|
| 50 | let statements = { |
|---|
| 51 | allTags: null, |
|---|
| 52 | simpleQuery: null, |
|---|
| 53 | }; |
|---|
| 54 | |
|---|
| 55 | /** |
|---|
| 56 | * return Delicious bookmark batabase file |
|---|
| 57 | * @return {nsIFile} |
|---|
| 58 | */ |
|---|
| 59 | function getBookmarkFile(){ |
|---|
| 60 | let file = services.get("directory").get("ProfD",Ci.nsIFile) |
|---|
| 61 | file.append("ybookmarks.sqlite"); |
|---|
| 62 | if (!file.exists() || !file.isReadable()){ |
|---|
| 63 | return null; |
|---|
| 64 | } |
|---|
| 65 | return file; |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | /** |
|---|
| 69 | * get all tag names from Delicious Bookmark |
|---|
| 70 | * @return {String[]} |
|---|
| 71 | */ |
|---|
| 72 | function getAllTags(){ |
|---|
| 73 | let list = []; |
|---|
| 74 | let st = statements.allTags; |
|---|
| 75 | try { |
|---|
| 76 | while (st.executeStep()){ |
|---|
| 77 | list.push(st.getString(0)); |
|---|
| 78 | } |
|---|
| 79 | } finally { |
|---|
| 80 | st.reset(); |
|---|
| 81 | } |
|---|
| 82 | return list; |
|---|
| 83 | } |
|---|
| 84 | /** |
|---|
| 85 | * @param {CompetionContext} context |
|---|
| 86 | * @param {Array} args |
|---|
| 87 | * @return {Array} |
|---|
| 88 | */ |
|---|
| 89 | function tagCompletion(context, args){ |
|---|
| 90 | let filter = context.filter; |
|---|
| 91 | let have = filter.split(","); |
|---|
| 92 | args.completeFilter = have.pop(); |
|---|
| 93 | let prefix = filter.substr(0, filter.length - args.completeFilter.length); |
|---|
| 94 | let tags = getAllTags(); |
|---|
| 95 | return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag)<0)]; |
|---|
| 96 | } |
|---|
| 97 | /** |
|---|
| 98 | * search Delicious Bookmarks |
|---|
| 99 | * contains all tags and query in title or URL or Note |
|---|
| 100 | * @param {String[]} tags |
|---|
| 101 | * @param {String} query |
|---|
| 102 | * @return {Array[]} [[url, title, note], ...] |
|---|
| 103 | * if both tags and query is none, returns [] |
|---|
| 104 | */ |
|---|
| 105 | function bookmarkSearch(tags, query){ |
|---|
| 106 | if (!query && (!tags || tags.length == 0)) |
|---|
| 107 | return []; |
|---|
| 108 | |
|---|
| 109 | let sql; |
|---|
| 110 | let list = []; |
|---|
| 111 | let st; |
|---|
| 112 | let finalize = true; |
|---|
| 113 | try { |
|---|
| 114 | if (!tags || tags.length == 0){ |
|---|
| 115 | st = statements.simpleQuery; |
|---|
| 116 | st.bindUTF8StringParameter(0, '%' + query + '%'); |
|---|
| 117 | finalize = false; |
|---|
| 118 | } else { |
|---|
| 119 | let sqlList = [ |
|---|
| 120 | 'SELECT b.name,b.url,b.description', |
|---|
| 121 | 'FROM bookmarks b, bookmarks_tags bt, tags t', |
|---|
| 122 | 'WHERE bt.tag_id = t.rowid', |
|---|
| 123 | 'AND b.rowid = bt.bookmark_id', |
|---|
| 124 | 'AND t.name in (', |
|---|
| 125 | ['?' + (parseInt(i)+1) for (i in tags)].join(","), |
|---|
| 126 | ')']; |
|---|
| 127 | if (query){ |
|---|
| 128 | let num = tags.length + 1; |
|---|
| 129 | sqlList.push([ |
|---|
| 130 | 'AND (', |
|---|
| 131 | 'b.name like', '?' + num, |
|---|
| 132 | 'OR b.url like', '?' + num, |
|---|
| 133 | 'OR b.description like', '?' + num, |
|---|
| 134 | ')', |
|---|
| 135 | 'GROUP BY b.rowid HAVING COUNT (b.rowid) = ?' + (num + 1), |
|---|
| 136 | 'ORDER BY b.added_date DESC' |
|---|
| 137 | ].join(" ")); |
|---|
| 138 | sql = sqlList.join(" "); |
|---|
| 139 | st = dbc.createStatement(sql); |
|---|
| 140 | st.bindUTF8StringParameter(tags.length, '%'+query+'%'); |
|---|
| 141 | st.bindInt32Parameter(tags.length+1, tags.length); |
|---|
| 142 | } else { |
|---|
| 143 | sqlList.push([ |
|---|
| 144 | 'GROUP BY b.rowid HAVING COUNT (b.rowid) = ?' + (tags.length + 1), |
|---|
| 145 | 'ORDER BY b.added_date DESC' |
|---|
| 146 | ].join(" ")); |
|---|
| 147 | sql = sqlList.join(" "); |
|---|
| 148 | st = dbc.createStatement(sql); |
|---|
| 149 | st.bindInt32Parameter(tags.length, tags.length); |
|---|
| 150 | } |
|---|
| 151 | for (let i in tags){ |
|---|
| 152 | st.bindUTF8StringParameter(i, tags[i]); |
|---|
| 153 | } |
|---|
| 154 | } |
|---|
| 155 | while (st.executeStep()){ |
|---|
| 156 | let url = st.getString(1); |
|---|
| 157 | list.push({ |
|---|
| 158 | url: url, |
|---|
| 159 | name: st.getString(0), |
|---|
| 160 | note: st.getString(2), |
|---|
| 161 | icon: bookmarks.getFavicon(url), |
|---|
| 162 | tags: ydls.getTags(url, {}) |
|---|
| 163 | }); |
|---|
| 164 | } |
|---|
| 165 | } finally { |
|---|
| 166 | st.reset(); |
|---|
| 167 | if (finalize) st.finalize(); |
|---|
| 168 | } |
|---|
| 169 | return list; |
|---|
| 170 | } |
|---|
| 171 | function templateDescription(item){ |
|---|
| 172 | return (item.tags && item.tags.length > 0 ? "[" + item.tags.join(",") + "]" : "") + item.note; |
|---|
| 173 | } |
|---|
| 174 | function templateTitleAndIcon(item){ |
|---|
| 175 | let simpleURL = item.text.replace(/^https?:\/\//, ''); |
|---|
| 176 | return <> |
|---|
| 177 | <span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{item.name}<a href={item.text} highlight="simpleURL"> |
|---|
| 178 | <span class="extra-info">{simpleURL}</span> |
|---|
| 179 | </a> |
|---|
| 180 | </>; |
|---|
| 181 | } |
|---|
| 182 | |
|---|
| 183 | commands.addUserCommand(["delicious[search]","ds[earch]"], "Delicious Bookmark Search", |
|---|
| 184 | function(args){ |
|---|
| 185 | if (args.length > 0){ |
|---|
| 186 | liberator.open(args[0], liberator.CURRENT_TAB); |
|---|
| 187 | return; |
|---|
| 188 | } |
|---|
| 189 | let list = bookmarkSearch(args["-tags"], args["-query"]); |
|---|
| 190 | let xml = template.tabular(["Title","Tags and Note"], [], list.map(function(item){ |
|---|
| 191 | return [ |
|---|
| 192 | <><img src={item.icon}/><a highlight="URL" href={item.url}>{item.name}</a></>, |
|---|
| 193 | "[" + item.tags.join(",") + "] " + item.note |
|---|
| 194 | ]; |
|---|
| 195 | })); |
|---|
| 196 | liberator.echo(xml, true); |
|---|
| 197 | },{ |
|---|
| 198 | options: [ |
|---|
| 199 | [["-tags","-t"], commands.OPTION_LIST, null, tagCompletion], |
|---|
| 200 | [["-query","-q"], commands.OPTION_STRING] |
|---|
| 201 | ], |
|---|
| 202 | completer: function(context, args){ |
|---|
| 203 | context.format = { |
|---|
| 204 | anchored: true, |
|---|
| 205 | title: ["Title and URL", "Tags and Note"], |
|---|
| 206 | keys: { text: "url", name: "name", icon: "icon", tags: "tags", note: "note"}, |
|---|
| 207 | process: [templateTitleAndIcon, templateDescription], |
|---|
| 208 | }; |
|---|
| 209 | context.filterFunc = null; |
|---|
| 210 | context.regenerate = true; |
|---|
| 211 | context.generate = function() bookmarkSearch(args["-tags"], args["-query"]); |
|---|
| 212 | }, |
|---|
| 213 | },true); |
|---|
| 214 | |
|---|
| 215 | let self = { |
|---|
| 216 | init: function(){ |
|---|
| 217 | if (dbc){ |
|---|
| 218 | try { |
|---|
| 219 | this.close(); |
|---|
| 220 | } catch(e) {} |
|---|
| 221 | } |
|---|
| 222 | let file = getBookmarkFile(); |
|---|
| 223 | if (!file) return; |
|---|
| 224 | dbc = ss.openDatabase(file); |
|---|
| 225 | |
|---|
| 226 | statements.allTags = dbc.createStatement("SELECT name FROM tags"); |
|---|
| 227 | statements.simpleQuery = dbc.createStatement([ |
|---|
| 228 | 'SELECT name,url,description FROM bookmarks', |
|---|
| 229 | 'WHERE name like ?1 OR', |
|---|
| 230 | 'url like ?1 OR', |
|---|
| 231 | 'description like ?1', |
|---|
| 232 | 'ORDER BY added_date DESC' |
|---|
| 233 | ].join(" ")); |
|---|
| 234 | }, |
|---|
| 235 | get tags(){ |
|---|
| 236 | return getAllTags(); |
|---|
| 237 | }, |
|---|
| 238 | /** |
|---|
| 239 | * @see bookmarkSearch |
|---|
| 240 | */ |
|---|
| 241 | search: function(tags, query){ |
|---|
| 242 | return bookmarkSearch(tags, query); |
|---|
| 243 | }, |
|---|
| 244 | /** |
|---|
| 245 | * used by completion |
|---|
| 246 | * :set complete+=D |
|---|
| 247 | * @param {CompletionContext} context |
|---|
| 248 | */ |
|---|
| 249 | urlCompleter: function(context){ |
|---|
| 250 | context.format = { |
|---|
| 251 | anchored: true, |
|---|
| 252 | title: ["Delicious Bookmarks"], |
|---|
| 253 | keys: { text: "url", name: "name", icon: "icon", tags: "tags", note: "note"}, |
|---|
| 254 | process: [templateTitleAndIcon, templateDescription], |
|---|
| 255 | }; |
|---|
| 256 | context.filterFunc = null; |
|---|
| 257 | context.regenerate = true; |
|---|
| 258 | context.generate = function() bookmarkSearch([], context.filter); |
|---|
| 259 | }, |
|---|
| 260 | close: function(){ |
|---|
| 261 | for each(let st in statements){ |
|---|
| 262 | if (st.state > 0) |
|---|
| 263 | statements[key].finalize(); |
|---|
| 264 | } |
|---|
| 265 | if (dbc.connectionReady) |
|---|
| 266 | dbc.close(); |
|---|
| 267 | }, |
|---|
| 268 | }; |
|---|
| 269 | self.init(); |
|---|
| 270 | liberator.registerObserver("shutdown", self.close); |
|---|
| 271 | completion.addUrlCompleter("D", "Delicious Bookmarks", self.urlCompleter); |
|---|
| 272 | return self; |
|---|
| 273 | })(); |
|---|
| 274 | function onUnload(){ |
|---|
| 275 | liberator.plugins.delicious.close(); |
|---|
| 276 | } |
|---|
| 277 | // vim: sw=2 ts=2 et: |
|---|