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