cobalt.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. (function($) {
  2. $(document).ready(function(){
  3. var match_count = 0, match_page_size=5, match_offset = 0,
  4. cobalt_visible = false, cobalt_out_visible = false,
  5. matches = [], match_idx = 0, handler_idx = 0,
  6. current_text, current_action_text='', handler, item, actions, match_count=0,
  7. plugins = {}, plugin_names=[], catalogs = {}, global_handlers = [], handlers = {},
  8. update_queue = [], required_updates = [];
  9. var log_error = function(message, err) {
  10. if (typeof(window.console) != 'undefined') {
  11. console.error(message);
  12. if(err) {
  13. console.log(err);
  14. }
  15. }
  16. };
  17. var log_msg = function(message) {
  18. if (typeof(window.console) != 'undefined') {
  19. console.log(message);
  20. }
  21. };
  22. var db = null;
  23. // Initialize database
  24. if (!db && typeof(openDatabase)=='function') {
  25. try {
  26. db = openDatabase('cobalt', '1.0', 'Cobalt Database', 204800);
  27. }
  28. catch (err) {
  29. if (err.name === 'SECURITY_ERR') {
  30. log_msg(Drupal.t('Failed to open database using the Web SQL Database API due to a security-related error.'));
  31. }
  32. else {
  33. log_msg(Drupal.t('Failed to open database using the Web SQL Database API.'));
  34. }
  35. }
  36. }
  37. if (!db) {
  38. log_error(Drupal.t('Could not open a client-side database. Cobalt requires a browser that implements the Web SQL Database API.'));
  39. return;
  40. }
  41. var nullDataHandler = function(transaction, results) { };
  42. var current_state = function() {
  43. if (typeof(Drupal.settings.cobalt) != 'undefined' &&
  44. typeof(Drupal.settings.cobalt.state) != 'undefined') {
  45. return Drupal.settings.cobalt.state;
  46. }
  47. else {
  48. return 0;
  49. }
  50. };
  51. // Initialize Cobalt
  52. var cobalt = {
  53. 'dbErrorHandler': function(transaction, error)
  54. {
  55. $(document).trigger('cobalt-db-error');
  56. if (window.console) {
  57. console.error(error.message+' (Code: '+error.code+')');
  58. }
  59. return false;
  60. },
  61. 'registerPlugin': function(name, plugin) {
  62. plugin_names.push(name);
  63. plugins[name] = plugin;
  64. if(typeof(plugin['catalogs'])!='undefined') {
  65. for(var c_name in plugin.catalogs) {
  66. catalogs[c_name] = plugin.catalogs[c_name];
  67. }
  68. }
  69. if(typeof(plugin['handlers'])!='undefined') {
  70. var h_len = plugin.handlers.length;
  71. for(var i=0; i<h_len; i++) {
  72. register_handler(plugin.handlers[i]);
  73. }
  74. }
  75. },
  76. 'catalogUpdated': function(catalog, updated) {
  77. if (typeof(updated)=='undefined') {
  78. updated = new Date().getTime();
  79. }
  80. db.transaction(function (transaction) {
  81. transaction.executeSql("UPDATE catalogs SET updated=?, state=? WHERE name = ?;", [ updated, Drupal.settings.cobalt.state, catalog ], nullDataHandler,cobalt.dbErrorHandler);
  82. });
  83. },
  84. 'emptyCatalog': function(name) {
  85. db.transaction(function (transaction) {
  86. transaction.executeSql("DELETE FROM entries WHERE catalog=?;", [ name ], nullDataHandler,cobalt.dbErrorHandler);
  87. });
  88. },
  89. 'addKeyBinding': function(binding, catalog, id, handler, active, state) {
  90. if (typeof(state)=='undefined') {
  91. state = current_state();
  92. }
  93. if (typeof(active)=='undefined') {
  94. active = 1;
  95. }
  96. bind_key(binding, catalog, id, handler);
  97. db.transaction(function (transaction) {
  98. transaction.executeSql("INSERT OR REPLACE INTO key_bindings(binding, catalog, id, handler, active, state) VALUES(?,?,?,?,?,?);", [ binding, catalog, id, handler, active, state ], nullDataHandler,cobalt.dbErrorHandler);
  99. });
  100. },
  101. 'loadEntry': function(catalog, id, callback) {
  102. var state = current_state();
  103. db.transaction(function (transaction) {
  104. transaction.executeSql("SELECT * FROM entries WHERE catalog=? AND id=? AND state=?;", [ catalog, id, state ], function(transaction, results) {
  105. var item = null;
  106. if (results.rows.length) {
  107. item = results.rows.item(0);
  108. item.information = JSON.parse(item.data);
  109. }
  110. callback(item);
  111. },cobalt.dbErrorHandler);
  112. });
  113. },
  114. 'deleteEntry': function(catalog, id) {
  115. db.transaction(function (transaction) {
  116. transaction.executeSql("DELETE FROM entries WHERE catalog=? AND id=?;", [ catalog, id ], nullDataHandler,cobalt.dbErrorHandler);
  117. });
  118. },
  119. 'addTemporaryEntry': function(id, name, information, classname) {
  120. cobalt.addEntry({id:id, name:name, information:information, catalog:'*temporary*', classname:classname, active:1, state:current_state()});
  121. },
  122. 'addEntry': function(data) {
  123. if (typeof(data.state)=='undefined') {
  124. data.state = current_state();
  125. }
  126. if (typeof(data.active)=='undefined') {
  127. data.active = 1;
  128. }
  129. if (typeof(data.extra)=='undefined') {
  130. data.extra = '';
  131. }
  132. db.transaction(function (transaction) {
  133. transaction.executeSql("INSERT OR REPLACE INTO entries(id, name, extra, data, catalog, data_class, state, active) VALUES(?,?,?,?,?,?,?,?);", [data['id'], data['name'], data.extra, JSON.stringify(data.information), data.catalog, data.classname, data.state, data.active], nullDataHandler,cobalt.dbErrorHandler);
  134. });
  135. },
  136. 'actionCandidates': function(item, callback) {
  137. action_candidates(item, callback);
  138. },
  139. 'showHtml': function(html) {
  140. if (typeof(html) == 'string') {
  141. cobalt_output.html(html);
  142. }
  143. else {
  144. cobalt_output.empty().append(html);
  145. }
  146. if (!cobalt_out_visible) {
  147. toggle_output();
  148. }
  149. }
  150. };
  151. if (typeof(Drupal.settings.cobalt) != 'undefined' && typeof(Drupal.settings.cobalt.update) != 'undefined') {
  152. cobalt.updateVersion = function(transaction, name, version) {
  153. transaction.executeSql('UPDATE versions SET version=? WHERE name=?', [version, name], nullDataHandler,cobalt.dbErrorHandler);
  154. };
  155. $(document).trigger('cobalt-update', [cobalt, db, Drupal.settings.cobalt.update]);
  156. return;
  157. }
  158. var register_handler_use = function(handler, item, transaction) {
  159. transaction.executeSql("INSERT OR IGNORE INTO handler_usage_data(catalog, id, handler, weight) VALUES(?,?,?,?)",
  160. [item.catalog, item.id, handler.id, 0], nullDataHandler, cobalt.dbErrorHandler);
  161. transaction.executeSql("UPDATE handler_usage_data SET weight=weight+1 WHERE catalog=? AND id=? AND handler=?",
  162. [item.catalog, item.id, handler.id], nullDataHandler, cobalt.dbErrorHandler);
  163. if (item.id) {
  164. handler.weight++;
  165. register_handler_use(handler, {id:0, catalog: ''}, transaction);
  166. }
  167. };
  168. var register_use = function(text, item, handler) {
  169. db.transaction(function (transaction) {
  170. register_handler_use(handler, item, transaction);
  171. transaction.executeSql("UPDATE usage_data SET last=null WHERE last = 1", [], nullDataHandler, cobalt.dbErrorHandler);
  172. if (item.weight == null) {
  173. // IGNORE in the rare event a handler that don't refresh the page
  174. // (e.g "show") is used multiple times and the weight initially was null.
  175. transaction.executeSql("INSERT OR IGNORE INTO usage_data(catalog, id, weight, abbreviation, last) VALUES(?,?,?,?,?)",
  176. [item.catalog, item.id, 1, text, 1], nullDataHandler,cobalt.dbErrorHandler);
  177. }
  178. else {
  179. transaction.executeSql("UPDATE usage_data SET weight=weight+1, abbreviation=?, last=1 WHERE catalog=? AND id=?",
  180. [text, item.catalog, item.id], nullDataHandler,cobalt.dbErrorHandler);
  181. }
  182. });
  183. };
  184. var register_handler = function(handler) {
  185. if (typeof(handler['data_class'])=='undefined') {
  186. handler['data_class'] = null;
  187. }
  188. handler.weight = 0;
  189. handler.context_weight = 0;
  190. // Get the weight of the handler
  191. db.transaction(function (transaction) {
  192. transaction.executeSql("SELECT weight FROM handler_usage_data WHERE catalog='' AND handler=?",
  193. [handler.id], function(transaction, results) {
  194. if (results.rows.length) {
  195. handler.weight = results.rows.item(0).weight;
  196. }
  197. });
  198. });
  199. if (!handler['data_class']) {
  200. global_handlers.push(handler);
  201. }
  202. else {
  203. if (!handlers[handler.data_class]) {
  204. handlers[handler.data_class] = [];
  205. }
  206. handlers[handler.data_class].push(handler);
  207. }
  208. };
  209. $(document).trigger('cobalt-load', cobalt);
  210. cobalt.registerPlugin('cobalt', {
  211. 'version': 1,
  212. 'handlers': [
  213. {
  214. 'id': 'cobalt_show',
  215. 'name': Drupal.t('Show'),
  216. 'handler': function(text, item) {
  217. cobalt.showHtml('<h2>' + item.name + '</h2>' +
  218. Drupal.t('text') + ': <i>' + text + '</i><br/>' +
  219. Drupal.t('id') + ': ' + item.id + '<br/>' +
  220. Drupal.t('catalog') + ': ' + item.catalog + '<br/>' +
  221. Drupal.t('class') + ': ' + item.data_class + '<br/>' +
  222. Drupal.t('data') + ': ' + item.data + '<br/>' +
  223. Drupal.t('weight') + ': ' + item.weight);
  224. }
  225. },
  226. {
  227. 'id': 'cobalt_abbrev',
  228. 'name': Drupal.t('Assign shortcut'),
  229. 'handler': function(text, item) {
  230. cobalt.actionCandidates(item, function(cand) {
  231. var out = $('<div class="shortcut-add"><h2>' + item.name + '</h2>' +
  232. Drupal.t('The keys !input should trigger the action:', {'!input': '<input class="key-combo" type="text" value="Ctrl+"/>'}) +
  233. '<br/> <select class="action-select"></select>' +
  234. '<p><button class="ok">' + Drupal.t('Ok') + '</button></p></div>');
  235. var actions = $(out).find('.action-select');
  236. var key_combo = $(out).find('.key-combo').css('width',50);
  237. var cand_count = cand.length;
  238. for(var i=0; i<cand_count; i++) {
  239. actions.append('<option value="' + cand[i].id + '">' + cand[i].name + '</option>');
  240. }
  241. $(out).find('button.ok').bind('click',function(){
  242. toggle_output('hide');
  243. cobalt.addKeyBinding(key_combo.val(), item.catalog, item.id, actions.val());
  244. });
  245. cobalt.showHtml(out);
  246. key_combo.focus();
  247. });
  248. }
  249. }
  250. ]
  251. });
  252. db.transaction(function (transaction) {
  253. transaction.executeSql('CREATE TABLE IF NOT EXISTS versions(' +
  254. 'name TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 0, ' +
  255. 'CONSTRAINT pk_versions PRIMARY KEY(name));', [], nullDataHandler,cobalt.dbErrorHandler);
  256. transaction.executeSql('CREATE TABLE IF NOT EXISTS entries(' +
  257. 'catalog TEXT NOT NULL, id TEXT NOT NULL, name TEXT NOT NULL, extra TEXT DEFAULT "", data TEXT NOT NULL DEFAULT "", data_class TEXT NOT NULL, ' +
  258. 'state INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 1, ' +
  259. 'CONSTRAINT pk_entries PRIMARY KEY(catalog, id));', [], nullDataHandler,cobalt.dbErrorHandler);
  260. transaction.executeSql('DELETE FROM entries WHERE catalog=?', ['*temporary*'], nullDataHandler, cobalt.dbErrorHandler);
  261. transaction.executeSql('CREATE TABLE IF NOT EXISTS key_bindings(' +
  262. 'binding TEXT NOT NULL, catalog TEXT NOT NULL, id TEXT NOT NULL, handler TEXT NOT NULL, ' +
  263. 'state INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 1, ' +
  264. 'CONSTRAINT pk_bindings PRIMARY KEY(binding, state));', [], nullDataHandler,cobalt.dbErrorHandler);
  265. transaction.executeSql('CREATE TABLE IF NOT EXISTS usage_data(catalog TEXT NOT NULL, id TEXT NOT NULL, ' +
  266. 'weight INTEGER NOT NULL DEFAULT 0, abbreviation TEXT NOT NULL DEFAULT "", last INTEGER, ' +
  267. 'CONSTRAINT pk_usage_data PRIMARY KEY(catalog, id));', [], nullDataHandler,cobalt.dbErrorHandler);
  268. transaction.executeSql('CREATE TABLE IF NOT EXISTS handler_usage_data(catalog TEXT NOT NULL, id TEXT NOT NULL, ' +
  269. 'handler TEXT NOT NULL, weight INTEGER NOT NULL DEFAULT 0,' +
  270. 'CONSTRAINT pk_handler_usage_data PRIMARY KEY(catalog, id, handler));', [], nullDataHandler,cobalt.dbErrorHandler);
  271. transaction.executeSql('CREATE INDEX IF NOT EXISTS idx_entries_catalog ON entries(catalog);', [], nullDataHandler,cobalt.dbErrorHandler);
  272. transaction.executeSql('CREATE INDEX IF NOT EXISTS idx_entries_abbreviation ON usage_data(abbreviation);', [], nullDataHandler,cobalt.dbErrorHandler);
  273. transaction.executeSql('CREATE INDEX IF NOT EXISTS idx_entries_active ON entries(active DESC);', [], nullDataHandler,cobalt.dbErrorHandler);
  274. transaction.executeSql('CREATE INDEX IF NOT EXISTS idx_entries_weight ON usage_data(weight DESC);', [], nullDataHandler,cobalt.dbErrorHandler);
  275. transaction.executeSql('CREATE TABLE IF NOT EXISTS catalogs(' +
  276. 'name TEXT NOT NULL PRIMARY KEY, updated INTEGER NOT NULL DEFAULT 0, state INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 1, uninstall INTEGER NOT NULL DEFAULT 0);', [], nullDataHandler,cobalt.dbErrorHandler);
  277. });
  278. var cobalt_output = $('<div id="cobalt-out"></div>').appendTo('body').hide();
  279. var bind_key = function (binding, catalog, id, handler) {
  280. $(document).bind('keydown', binding, function(){
  281. cobalt.loadEntry(catalog, id, function(item) {
  282. if (item) {
  283. cobalt.actionCandidates(item, function(cand){
  284. var cand_count = cand.length;
  285. for(var i=0; i<cand_count; i++) {
  286. if (cand[i].id == handler) {
  287. cand[i].handler(item.name, item);
  288. }
  289. }
  290. });
  291. }
  292. });
  293. });
  294. };
  295. var toggle_output = function(arg) {
  296. if (cobalt_out_visible) {
  297. cobalt_output.hide();
  298. cobalt_out_visible = false;
  299. }
  300. else if (arg!='hide') {
  301. cobalt_output.show();
  302. cobalt_out_visible = true;
  303. }
  304. };
  305. var keypress_reaction = function() {
  306. if(cobalt_input.val()==current_text) {
  307. return;
  308. }
  309. current_text = cobalt_input.val();
  310. if($.trim(current_text)!='') {
  311. lookup();
  312. }
  313. };
  314. var action_keypress_reaction = function() {
  315. if(cobalt_h_input.val()==current_action_text) {
  316. return;
  317. }
  318. current_action_text = cobalt_h_input.val();
  319. action_candidates(item, function(candidates) {
  320. actions = candidates;
  321. set_handler(0, true);
  322. });
  323. };
  324. var clear_ac = function() {
  325. match_idx = 0;
  326. matches = null;
  327. item = 0;
  328. $('#cobalt .inner').attr('class','inner');
  329. $('#cobalt .inner label').hide();
  330. cobalt_ac.hide().empty();
  331. cobalt_paging.hide().empty();
  332. };
  333. var lookup = function(preserve_offset) {
  334. if (!preserve_offset) {
  335. match_offset = 0;
  336. }
  337. var like_expr = '%' + current_text + '%';
  338. db.transaction(function (transaction) {
  339. transaction.executeSql("SELECT e.*, u.weight FROM entries AS e " +
  340. "LEFT OUTER JOIN usage_data AS u ON (e.catalog=u.catalog AND e.id=u.id) " +
  341. "WHERE e.active=1 AND (u.abbreviation = ? OR e.name LIKE ? OR e.extra LIKE ?) " +
  342. "ORDER BY nullif(?,u.abbreviation), nullif(?,e.name), u.weight DESC LIMIT ?,?;", [
  343. current_text, like_expr, like_expr, current_text, current_text, match_offset, match_page_size ], lookup_finished,cobalt.dbErrorHandler);
  344. });
  345. };
  346. var lookup_finished = function(transaction, results) {
  347. if (results.rows.length) {
  348. match_idx = 0;
  349. $('#cobalt .left label').hide();
  350. cobalt_ac.empty().hide();
  351. for (var i=0; i<results.rows.length; i++) {
  352. var item = results.rows.item(i);
  353. item.information = JSON.parse(item.data);
  354. if (typeof(catalogs[item.catalog]) !='undefined' && typeof(catalogs[item.catalog].item_formatter) == 'function'){
  355. var title = catalogs[item.catalog].item_formatter(item);
  356. }
  357. else {
  358. var title = item.name;
  359. }
  360. $('<li class="ac-opt-' + i + '"></li>').html(title).appendTo(cobalt_ac);
  361. }
  362. matches = results.rows;
  363. //Only count for paging when we're not already offsetting the result and
  364. //have a match count equal to the page size
  365. if (match_offset) {
  366. update_pager(match_count);
  367. }
  368. else if (matches.length==match_page_size) {
  369. var like_expr = '%' + current_text + '%';
  370. db.transaction(function (transaction) {
  371. transaction.executeSql("SELECT COUNT(*) as match_count FROM entries AS e " +
  372. "LEFT OUTER JOIN usage_data AS u ON (e.catalog=u.catalog AND e.id=u.id) " +
  373. "WHERE e.active=1 AND (u.abbreviation = ? OR e.name LIKE ? OR e.extra LIKE ?);", [
  374. current_text, like_expr, like_expr], function(transaction, results) {
  375. if (results.rows.length) {
  376. match_count = results.rows.item(0).match_count;
  377. update_pager(match_count);
  378. }
  379. },cobalt.dbErrorHandler);
  380. });
  381. }
  382. else {
  383. update_pager(matches.length);
  384. }
  385. cobalt_ac.show();
  386. }
  387. ac_select(0);
  388. };
  389. var update_pager = function(new_match_count) {
  390. match_count = new_match_count;
  391. cobalt_paging.hide().empty();
  392. var page_count = Math.min(Math.ceil(match_count/match_page_size),25);
  393. for (var i=0; i<page_count; i++ ) {
  394. var p = $('<li>&nbsp;</li>').appendTo(cobalt_paging);
  395. if (i==match_offset/match_page_size) {
  396. p.attr('class','current');
  397. }
  398. }
  399. cobalt_paging.show();
  400. };
  401. var ac_page = function(new_offset) {
  402. if (new_offset>=match_count || new_offset<0) {
  403. return;
  404. }
  405. match_offset = new_offset;
  406. lookup(true);
  407. };
  408. var ac_select = function(idx) {
  409. if (idx<0 || idx >= matches.length) {
  410. item = null;
  411. return;
  412. }
  413. item = matches.item(idx);
  414. item.information = JSON.parse(item.data);
  415. var old_item = matches.item(match_idx);
  416. $('#cobalt .ac-opt-' + match_idx).removeClass('active');
  417. match_idx = idx;
  418. // Generate path specific classes that can be used for icons.
  419. // Matches the classes used in the Rubik admin theme.
  420. var classes = '';
  421. if (item.data_class === 'url_data') {
  422. var path = JSON.parse(item.data);
  423. if (typeof(path) == 'object') {
  424. path = path.path;
  425. }
  426. classes = [];
  427. var args = path.split('/');
  428. while(args.length) {
  429. classes[args.length - 1] = 'path-' + args.join('-');
  430. args.pop();
  431. }
  432. classes = classes.join(' ');
  433. };
  434. $('#cobalt .left .inner').attr('class','inner cobalt-item-' + item.data_class + ' ' + classes);
  435. $('#cobalt .left label').text(item.name).show();
  436. $('#cobalt .ac-opt-' + match_idx).addClass('active');
  437. action_candidates(item, function(candidates){
  438. actions = candidates;
  439. set_handler(0, true);
  440. });
  441. };
  442. var action_candidates = function(item, callback) {
  443. var candidates = [], gcount, i, j, cls_count, rc, cc, item,
  444. add_applicable = function(handler) {
  445. // Check if the handler matches the current_action_text (from closure),
  446. // or run the handlers own applicability check if it has one.
  447. if ((current_action_text=='' || handler['name'].toLowerCase().indexOf(current_action_text)!=-1) &&
  448. (!handler['applicable'] || handler.applicable(current_text, item))) {
  449. candidates.push(handler);
  450. }
  451. };
  452. // Add handlers for the items data class that match the current_action_text
  453. if (typeof(handlers[item.data_class])!='undefined') {
  454. cls_count = handlers[item.data_class].length;
  455. for (i=0; i<cls_count; i++) {
  456. add_applicable(handlers[item.data_class][i]);
  457. }
  458. }
  459. // Add global handlers that match the current_action_text
  460. g_count = global_handlers.length;
  461. for (i=0; i<g_count; i++) {
  462. add_applicable(global_handlers[i]);
  463. }
  464. // Load the usage data for the candidates so that we may sort them
  465. db.transaction(function (transaction) {
  466. transaction.executeSql("SELECT handler, weight FROM handler_usage_data WHERE catalog=? AND id=?",
  467. [item.catalog, item.id], function(transaction, results) {
  468. rc = results.rows.length;
  469. cc = candidates.length;
  470. for (j=0; j<cc; j++) {
  471. candidates[j].context_weight = 0;
  472. for (i=0; i<rc; i++) {
  473. r = results.rows.item(i);
  474. if (r.handler == candidates[j].id) {
  475. candidates[j].context_weight = r.weight;
  476. }
  477. }
  478. }
  479. // Sort the candidates after context_weight and weight
  480. candidates.sort(function(a, b){
  481. var cwd = b.context_weight-a.context_weight;
  482. if (cwd==0) {
  483. return b.weight-a.weight;
  484. }
  485. return cwd;
  486. });
  487. callback(candidates);
  488. });
  489. });
  490. };
  491. var handler_class = function() {
  492. if (handler) {
  493. return handler['class'] ? handler['class'] : 'default';
  494. }
  495. };
  496. var set_handler = function(idx, update) {
  497. if (update) {
  498. cobalt_actions.empty();
  499. var a_count = actions.length;
  500. for(var i=0; i<a_count; i++) {
  501. $('<li class="action-opt-' + i + '"></l i>').html(actions[i]['name']).appendTo(cobalt_actions);
  502. }
  503. }
  504. if (idx<0 || idx >= actions.length) {
  505. return;
  506. }
  507. $('#cobalt .action-opt-' + handler_idx).removeClass('active');
  508. $('#cobalt .action-opt-' + idx).addClass('active');
  509. handler_idx = idx;
  510. var new_handler = actions[idx];
  511. handler = new_handler;
  512. if (handler) {
  513. var newClass = handler_class();
  514. $('#cobalt .right .inner').attr('class','inner cobalt-action-' + newClass);
  515. $('#cobalt .right label').text(handler.name).show();
  516. }
  517. };
  518. var run_handler = function() {
  519. if (item && typeof(handler['handler']) == 'function') {
  520. var text = $.trim(cobalt_input.val());
  521. register_use(text, item, handler);
  522. try {
  523. handler.handler(text, item);
  524. }
  525. catch(err) {
  526. log_error(Drupal.t("The handler threw a exception"), err);
  527. }
  528. }
  529. hide();
  530. };
  531. var get_last_entry = function() {
  532. db.transaction(function (transaction) {
  533. transaction.executeSql("SELECT e.*, u.weight FROM entries AS e INNER JOIN usage_data AS u ON u.catalog = e.catalog AND u.id = e.id WHERE last = 1;", [], function(transaction, results) {
  534. lookup_finished(transaction, results);
  535. }, cobalt.dbErrorHandler);
  536. });
  537. };
  538. var toggle = function(arg) {
  539. if (cobalt_visible) {
  540. cb.hide();
  541. cobalt_visible = false;
  542. }
  543. else if (arg!='hide') {
  544. toggle_output('hide');
  545. cobalt_input.val($.trim(cobalt_input.val()));
  546. cb.show();
  547. cobalt_visible = true;
  548. setTimeout(function(){ cobalt_input.focus(); cobalt_input.select(); }, 100);
  549. }
  550. };
  551. var hide = function() {
  552. toggle('hide');
  553. };
  554. var init = function() {
  555. $(document).trigger('cobalt-init', cobalt);
  556. // Load key bindings
  557. db.transaction(function (transaction) {
  558. transaction.executeSql('SELECT binding, catalog, id, handler FROM key_bindings', [], function(transaction, results) {
  559. for (var i=0; i<results.rows.length; i++) {
  560. var b = results.rows.item(i);
  561. bind_key(b.binding, b.catalog, b.id, b.handler);
  562. }
  563. },cobalt.dbErrorHandler);
  564. });
  565. get_last_entry();
  566. db.transaction(function (transaction) {
  567. transaction.executeSql("SELECT * FROM catalogs ORDER BY updated;", [ ], function(transaction, results) {
  568. var info = {};
  569. var state = current_state();
  570. // Queue catalogs for update
  571. for (var i = 0; i < results.rows.length; i++) {
  572. var item = results.rows.item(i);
  573. info[item.name] = item;
  574. update_queue.push({'name': item.name, 'updated': (item.state==state?item.updated:0) });
  575. }
  576. // Install new catalogs and queue them for updates
  577. for (var key in catalogs) {
  578. (function(key) {
  579. if (!info[key]) {
  580. if (catalogs[key]['install']) {
  581. catalogs[key].install();
  582. }
  583. db.transaction(function (transaction) {
  584. transaction.executeSql("INSERT INTO catalogs(name) VALUES(?)", [key], nullDataHandler,cobalt.dbErrorHandler);
  585. });
  586. update_queue.unshift({'name': key, 'updated': 0 });
  587. }
  588. })(key);
  589. }
  590. // Update loop
  591. var update_counter = 0;
  592. var update_loop = function() {
  593. setTimeout(function() {
  594. if(update_queue.length) {
  595. var inf = update_queue.shift();
  596. var now = new Date().getTime();
  597. var catalog = catalogs[inf.name];
  598. if (typeof(catalog['update_rate'])!='undefined' && inf.updated + catalog.update_rate > now) {
  599. update_queue.push(inf);
  600. update_loop();
  601. }
  602. else {
  603. if (catalog['update']) {
  604. catalog.update(inf.updated, function(enqueue){
  605. var now = new Date().getTime();
  606. cobalt.catalogUpdated(inf.name);
  607. if (enqueue) {
  608. update_queue.push({'name': inf.name, 'updated': now });
  609. }
  610. update_counter++;
  611. update_loop();
  612. });
  613. }
  614. else {
  615. update_loop();
  616. }
  617. }
  618. }
  619. },update_counter?1000:100);
  620. };
  621. update_loop();
  622. },cobalt.dbErrorHandler);
  623. });
  624. $(document).trigger('cobalt-post-init', cobalt);
  625. cb.bind('click', function(e){ return false; });
  626. $([cobalt_input[0], cobalt_h_input[0]]).bind('keydown', 'esc', function(){ toggle('hide'); toggle_output('hide'); return false; }).
  627. bind('keydown', 'return', function(){ run_handler(); return false; });
  628. cobalt_input.bind('keydown', 'up', function(){ ac_select(match_idx-1); return false; }).
  629. bind('keydown', 'down', function(){ ac_select(match_idx+1); return false; }).
  630. bind('keydown', 'Alt+left', function(){ ac_page(match_offset-match_page_size); return false; }).
  631. bind('keydown', 'Alt+right', function(){ ac_page(match_offset+match_page_size); return false; }).
  632. bind('keyup', function(){ keypress_reaction(); return false; }).
  633. bind('focus', function() {
  634. if (matches && matches.length) {
  635. cobalt_ac.show();
  636. cobalt_paging.show();
  637. }
  638. cobalt_actions.hide();
  639. });
  640. cobalt_h_input.bind('keydown', 'up', function(){ set_handler(handler_idx-1); return false; }).
  641. bind('keydown', 'down', function(){ set_handler(handler_idx+1); return false; }).
  642. bind('keyup', function(){ action_keypress_reaction(); return false; }).
  643. bind('focus', function(){ cobalt_paging.hide(); cobalt_ac.hide(); cobalt_actions.show(); });
  644. cobalt_output.bind('click', function(e){ return false; });
  645. $('.cell.left', cb).bind('click', function(){ cobalt_input[0].focus(); });
  646. $('.cell.right', cb).bind('click', function(){ cobalt_h_input[0].focus(); });
  647. $(document).bind('click', function(){ toggle('hide'); toggle_output('hide'); });
  648. if (Drupal && Drupal.settings && Drupal.settings.cobalt) {
  649. var bindings = Drupal.settings.cobalt.bindings,
  650. bind_count = bindings.length, i;
  651. for (i=0; i<bind_count; i++) {
  652. $(document).bind('keydown', bindings[i], toggle);
  653. }
  654. }
  655. else {
  656. $(document).bind('keydown', 'Alt+space', toggle)
  657. .bind('keydown', 'Ctrl+space', toggle);
  658. }
  659. };
  660. // Initialize GUI
  661. var cb = $('<div id="cobalt">'+
  662. '<div class ="cells"><div class="cell left"><div class="inner"><span class="icon"></span><input type="text" id="cobalt-input" /><label></label></div></div>'+
  663. '<div class="cell right"><div class="inner"><input type="text" id="cobalt-handler-input" /><label></label></div></div></div>'+
  664. '<ol class="cobalt-paging"></ol><ul class="cobalt-autocomplete"></ul><ul class="cobalt-actions"></ul></div>').appendTo('body').hide();
  665. var cobalt_input = $('#cobalt-input');
  666. var cobalt_h_input = $('#cobalt-handler-input');
  667. var cobalt_ac = $('#cobalt .cobalt-autocomplete');
  668. var cobalt_actions = $('#cobalt .cobalt-actions');
  669. var cobalt_paging = $('#cobalt .cobalt-paging');
  670. $('#cobalt .right label').hide();
  671. // Check if we need to update anything before initializing
  672. db.transaction(function (transaction) {
  673. transaction.executeSql('SELECT name, version FROM versions', [], function(transaction, results) {
  674. var v = {};
  675. for (var i=0; i<results.rows.length; i++) {
  676. var vi = results.rows.item(i);
  677. v[vi.name] = vi.version;
  678. }
  679. db.transaction(function (transaction) {
  680. var plugin_count = plugin_names.length;
  681. for (var i=0; i<plugin_count; i++) {
  682. var n=plugin_names[i];
  683. if (typeof(v[n])=='undefined') {
  684. transaction.executeSql('INSERT INTO versions(name, version) VALUES(?,?)', [ n, plugins[n].version ], nullDataHandler,cobalt.dbErrorHandler);
  685. }
  686. else if (v[n]<plugins[n].version) {
  687. required_updates.push([n, v[n], plugins[n].version]);
  688. }
  689. }
  690. if (required_updates.length) {
  691. var update_notice = function () {
  692. var update_url = Drupal.settings.basePath + 'cobalt/update';
  693. for (var i=0; i<required_updates.length; i++) {
  694. var u = required_updates[i];
  695. update_url += '/' + u[0] + '/' + u[1] + '/' + u[2];
  696. }
  697. cobalt.showHtml('<h1>' + Drupal.t('Update required') + '</h1>' +
  698. '<p>' + Drupal.t('Cobalt must be updated before it can be used') + '</p>' +
  699. '<p><a class="cobalt-update-link" href="' + update_url + '">' + Drupal.t('Click here to update') + '</a></p>');
  700. };
  701. $(document).bind('click', function(){ toggle_output('hide'); });
  702. if (Drupal && Drupal.settings && Drupal.settings.cobalt) {
  703. var bindings = Drupal.settings.cobalt.bindings,
  704. bind_count = bindings.length, i;
  705. for (i=0; i<bind_count; i++) {
  706. $(document).bind('keydown', bindings[i], update_notice);
  707. }
  708. }
  709. else {
  710. $(document).bind('keydown', 'Alt+space', update_notice)
  711. .bind('keydown', 'Ctrl+space', update_notice);
  712. }
  713. }
  714. else {
  715. init();
  716. }
  717. });
  718. },cobalt.dbErrorHandler);
  719. });
  720. });
  721. })(jQuery);