comparison booket.js @ 7:a1a934adff8d version-2

Add support for favicons
author Guido Berhoerster <guido+booket@berhoerster.name>
date Sun, 14 Sep 2014 23:12:37 +0200
parents e9ad4c625b7a
children 20902b548d9f
comparison
equal deleted inserted replaced
6:e9ad4c625b7a 7:a1a934adff8d
23 23
24 (function () { 24 (function () {
25 'use strict'; 25 'use strict';
26 26
27 var BOOKMARKLET_URI = 27 var BOOKMARKLET_URI =
28 'javascript:(function () {' + 28 'javascript:(function() {' +
29 '\'use strict\';' + 29 '\'use strict\';' +
30 '' +
31 'function displayBookmarkData(bookmarkData) {' +
32 'window.alert(\'Copy the following data and paste it into \' +' +
33 '\'Booket:\\n\\n\' + JSON.stringify(bookmarkData));' +
34 '}' +
30 '' + 35 '' +
31 'window.alert(\'Copy the following data and paste it into \' +' + 36 'var bookmarkData = {' +
32 '\'Booket:\\n\\n\' + JSON.stringify({' + 37 '\'url\': document.URL,' +
33 '\'url\': document.URL,' + 38 '\'title\': document.title,' +
34 '\'title\': document.title' + 39 '\'favicon\': undefined' +
35 '}));' + 40 '};' +
36 '}) ();'; 41 'var faviconLinkElement;' +
42 'var faviconUrls = [];' +
43 'var aElement;' +
44 'var canvasElement;' +
45 'var canvasCtx;' +
46 'var imgElement;' +
47 '' +
48 'aElement = document.createElement(\'a\');' +
49 'aElement.href = document.URL;' +
50 '' +
51 'faviconUrls.push(aElement.protocol + \'//\' + aElement.host + ' +
52 '\'/favicon.ico\');' +
53 '' +
54 'faviconLinkElement = document.querySelector(' +
55 '\'link[rel~=\\\'icon\\\']\');' +
56 'if (faviconLinkElement !== null) {' +
57 'faviconUrls.push(faviconLinkElement.href);' +
58 '}' +
59 '' +
60 'canvasElement = document.createElement(\'canvas\');' +
61 'canvasCtx = canvasElement.getContext(\'2d\');' +
62 '' +
63 'imgElement = new Image();' +
64 'imgElement.addEventListener(\'load\', function(e) {' +
65 'var faviconUrl;' +
66 '' +
67 'canvasElement.width = 16;' +
68 'canvasElement.height = 16;' +
69 'canvasCtx.clearRect(0, 0, 16, 16);' +
70 'try {' +
71 'canvasCtx.drawImage(this, 0, 0, 16, 16);' +
72 'bookmarkData.favicon = canvasElement.toDataURL();' +
73 '} catch (exception) {' +
74 'faviconUrl = faviconUrls.pop();' +
75 '}' +
76 'if (bookmarkData.favicon !== undefined || ' +
77 'faviconUrl === undefined) {' +
78 'displayBookmarkData(bookmarkData);' +
79 '} else {' +
80 'imgElement.src = faviconUrl;' +
81 '}' +
82 '});' +
83 'imgElement.addEventListener(\'error\', function(e) {' +
84 'var faviconUrl;' +
85 '' +
86 'faviconUrl = faviconUrls.pop();' +
87 'if (faviconUrl !== undefined) {' +
88 'imgElement.src = faviconUrl;' +
89 '} else {' +
90 'displayBookmarkData(bookmarkData);' +
91 '}' +
92 '});' +
93 'imgElement.src = faviconUrls.pop();' +
94 '})();';
37 95
38 96
39 /* 97 /*
40 * utility stuff 98 * utility stuff
41 */ 99 */
346 404
347 /* 405 /*
348 * model 406 * model
349 */ 407 */
350 408
351 var Bookmark = function (url, title, tags, ctime, mtime) { 409 var Bookmark = function (url, title, favicon, tags, ctime, mtime) {
352 var parsedTime; 410 var parsedTime;
353 411
354 if (!isString(url)) { 412 if (!isString(url)) {
355 throw new TypeError(typeof url + ' is not a string'); 413 throw new TypeError(typeof url + ' is not a string');
356 } 414 }
357 this.url = url; 415 this.url = url;
358 416
359 this.title = (isString(title) && title !== '') ? title : url; 417 this.title = (isString(title) && title !== '') ? title : url;
418
419 if (isString(favicon) && favicon.match(/^data:image\/png;base64,/)) {
420 this.favicon = favicon;
421 } else {
422 this.favicon = undefined;
423 }
360 424
361 if (Array.isArray(tags)) { 425 if (Array.isArray(tags)) {
362 // remove duplicates, non-string or empty tags and tags containing 426 // remove duplicates, non-string or empty tags and tags containing
363 // commas 427 // commas
364 this.tags = new StringSet(tags.filter(function (tag) { 428 this.tags = new StringSet(tags.filter(function (tag) {
645 709
646 // create a temporary list of valid bookmarks 710 // create a temporary list of valid bookmarks
647 parsedData.bookmarks.forEach(function (bookmark) { 711 parsedData.bookmarks.forEach(function (bookmark) {
648 if (isString(bookmark.url) && bookmark.url !== '') { 712 if (isString(bookmark.url) && bookmark.url !== '') {
649 bookmarks.push(new Bookmark(bookmark.url, bookmark.title, 713 bookmarks.push(new Bookmark(bookmark.url, bookmark.title,
650 bookmark.tags, bookmark.ctime, bookmark.mtime)); 714 bookmark.favicon, bookmark.tags, bookmark.ctime,
715 bookmark.mtime));
651 } 716 }
652 }, this); 717 }, this);
653 718
654 // add each bookmark to the model ordered by the last modification time 719 // add each bookmark to the model ordered by the last modification time
655 this.add(bookmarks.sort(function (bookmark1, bookmark2) { 720 this.add(bookmarks.sort(function (bookmark1, bookmark2) {
820 885
821 var ActionsView = function () { 886 var ActionsView = function () {
822 var saveFormElement; 887 var saveFormElement;
823 var loadFormElement; 888 var loadFormElement;
824 var newNode; 889 var newNode;
825 var editorFormElement;
826 890
827 ObservableMixin.call(this); 891 ObservableMixin.call(this);
828 892
829 this.tagInputTemplate = document.querySelector('#tag-input-template'); 893 this.tagInputTemplate = document.querySelector('#tag-input-template');
830 saveFormElement = document.querySelector('form#save-form'); 894 saveFormElement = document.querySelector('form#save-form');
837 901
838 // create new editor form from template 902 // create new editor form from template
839 newNode = document.importNode( 903 newNode = document.importNode(
840 document.querySelector('#bookmark-editor-template').content, true); 904 document.querySelector('#bookmark-editor-template').content, true);
841 905
842 editorFormElement = newNode.querySelector('form.bookmark-editor-form'); 906 this.editorFormElement = newNode.querySelector('form.bookmark-editor-form');
843 editorFormElement.querySelector('legend').textContent = 'Add Bookmark'; 907 this.editorFormElement.querySelector('legend').textContent = 'Add Bookmark';
844 editorFormElement.querySelector('input:not([type="hidden"])').accessKey = 908 this.editorFormElement.querySelector(
845 'a'; 909 'input:not([type="hidden"])').accessKey = 'a';
846 editorFormElement.addEventListener('input', this); 910 this.editorFormElement.addEventListener('input', this);
847 editorFormElement.addEventListener('click', this); 911 this.editorFormElement.addEventListener('click', this);
848 editorFormElement.addEventListener('submit', this); 912 this.editorFormElement.addEventListener('submit', this);
849 editorFormElement.addEventListener('reset', this); 913 this.editorFormElement.addEventListener('reset', this);
914
915 this.faviconImageElement =
916 this.editorFormElement.querySelector('img.bookmark-favicon');
917 this.faviconImageElement.addEventListener('load', this);
918 this.faviconImageElement.addEventListener('error', this);
919
920 this.missingFaviconUri = this.faviconImageElement.src;
850 921
851 this.editTagListElement = 922 this.editTagListElement =
852 editorFormElement.querySelector('ul.tag-input-list'); 923 this.editorFormElement.querySelector('ul.tag-input-list');
853 this.editTagListElement.appendChild(this.createTagInputElement('')); 924 this.editTagListElement.appendChild(this.createTagInputElement(''));
854 925
855 saveFormElement.parentNode.insertBefore(newNode, 926 saveFormElement.parentNode.insertBefore(newNode,
856 saveFormElement.nextSibling); 927 saveFormElement.nextSibling);
857 928
874 var parsedData; 945 var parsedData;
875 var tags = []; 946 var tags = [];
876 var i; 947 var i;
877 948
878 switch (e.type) { 949 switch (e.type) {
950 case 'error':
951 if (e.target.classList.contains('bookmark-favicon')) {
952 if (e.target.src !== this.missingFaviconUri) {
953 e.target.src = this.missingFaviconUri;
954 }
955 }
956 break;
957 case 'load':
958 if (e.target.classList.contains('bookmark-favicon')) {
959 this.editorFormElement.favicon.value =
960 (e.target.src !== this.missingFaviconUri) ? e.target.src : '';
961 }
962 break;
879 case 'input': 963 case 'input':
880 if (e.target.name === 'bookmarklet-import') { 964 if (e.target.name === 'bookmarklet-import') {
881 // get rid of any preceding text 965 // get rid of any preceding text
882 bookmarkletData = e.target.value.replace(/^[^{]*/, ''); 966 bookmarkletData = e.target.value.replace(/^[^{]*/, '');
883 967
890 if (isString(parsedData.url) && parsedData.url !== '') { 974 if (isString(parsedData.url) && parsedData.url !== '') {
891 e.target.form.elements.url.value = parsedData.url; 975 e.target.form.elements.url.value = parsedData.url;
892 } 976 }
893 if (isString(parsedData.title) && parsedData.title !== '') { 977 if (isString(parsedData.title) && parsedData.title !== '') {
894 e.target.form.elements.title.value = parsedData.title; 978 e.target.form.elements.title.value = parsedData.title;
979 }
980 if (isString(parsedData.favicon) &&
981 parsedData.favicon.match(/^data:image\/png;base64,/)) {
982 this.faviconImageElement.src = parsedData.favicon;
895 } 983 }
896 } 984 }
897 break; 985 break;
898 case 'click': 986 case 'click':
899 if (e.target.name === 'more-tags') { 987 if (e.target.name === 'more-tags') {
926 } else { 1014 } else {
927 tags.push(e.target.tag.value.trim()); 1015 tags.push(e.target.tag.value.trim());
928 } 1016 }
929 1017
930 this.notify('save-bookmark', e.target.url.value, 1018 this.notify('save-bookmark', e.target.url.value,
931 e.target.title.value, tags); 1019 e.target.title.value, e.target.favicon.value, tags);
932 1020
933 e.target.reset(); 1021 e.target.reset();
934 } 1022 }
935 break; 1023 break;
936 case 'reset': 1024 case 'reset':
937 if (e.target.classList.contains('bookmark-editor-form')) { 1025 if (e.target.classList.contains('bookmark-editor-form')) {
938 e.target.blur(); 1026 e.target.blur();
1027
1028 e.target.querySelector('img.bookmark-favicon').src =
1029 this.missingFaviconUri;
939 1030
940 // remove all but one tag input element 1031 // remove all but one tag input element
941 while (this.editTagListElement.firstChild !== null) { 1032 while (this.editTagListElement.firstChild !== null) {
942 this.editTagListElement.removeChild( 1033 this.editTagListElement.removeChild(
943 this.editTagListElement.firstChild); 1034 this.editTagListElement.firstChild);
977 this.bookmarkEditorTemplate = 1068 this.bookmarkEditorTemplate =
978 document.querySelector('#bookmark-editor-template'); 1069 document.querySelector('#bookmark-editor-template');
979 this.tagInputTemplate = document.querySelector('#tag-input-template'); 1070 this.tagInputTemplate = document.querySelector('#tag-input-template');
980 1071
981 this.bookmarkListElement = document.querySelector('ul#bookmark-list'); 1072 this.bookmarkListElement = document.querySelector('ul#bookmark-list');
1073 this.bookmarkListElement.addEventListener('input', this);
982 this.bookmarkListElement.addEventListener('click', this); 1074 this.bookmarkListElement.addEventListener('click', this);
983 this.bookmarkListElement.addEventListener('submit', this); 1075 this.bookmarkListElement.addEventListener('submit', this);
984 this.bookmarkListElement.addEventListener('reset', this); 1076 this.bookmarkListElement.addEventListener('reset', this);
985 1077
986 searchFormElement = document.querySelector('#search-form'); 1078 searchFormElement = document.querySelector('#search-form');
989 1081
990 this.searchTermInputElement = searchFormElement['search-term']; 1082 this.searchTermInputElement = searchFormElement['search-term'];
991 1083
992 this.bookmarkMessageElement = document.querySelector('#bookmark-message'); 1084 this.bookmarkMessageElement = document.querySelector('#bookmark-message');
993 1085
1086 this.missingFaviconUri = '';
1087
994 this.updateBookmarkMessage(); 1088 this.updateBookmarkMessage();
995 }; 1089 };
996 1090
997 extend(BookmarkView, ObservableMixin); 1091 extend(BookmarkView, ObservableMixin);
998 1092
999 BookmarkView.prototype.handleEvent = function (e) { 1093 BookmarkView.prototype.handleEvent = function (e) {
1094 var bookmarkletData;
1095 var parsedData;
1000 var i; 1096 var i;
1001 var tags = []; 1097 var tags = [];
1002 var node; 1098 var node;
1003 1099
1004 switch (e.type) { 1100 switch (e.type) {
1101 case 'error':
1102 if (e.target.classList.contains('bookmark-favicon')) {
1103 if (e.target.src !== this.missingFaviconUri) {
1104 e.target.src = this.missingFaviconUri;
1105 }
1106 }
1107 break;
1108 case 'load':
1109 if (e.target.classList.contains('bookmark-favicon')) {
1110 node = e.target;
1111 while ((node = node.parentNode) !== null) {
1112 if (node.classList.contains('bookmark-editor-form')) {
1113 node.favicon.value =
1114 (e.target.src !== this.missingFaviconUri) ?
1115 e.target.src : '';
1116 break;
1117 }
1118 }
1119 }
1120 break;
1121 case 'input':
1122 if (e.target.name === 'bookmarklet-import') {
1123 // get rid of any preceding text
1124 bookmarkletData = e.target.value.replace(/^[^{]*/, '');
1125
1126 try {
1127 parsedData = JSON.parse(bookmarkletData);
1128 } catch (exception) {
1129 return;
1130 }
1131
1132 if (isString(parsedData.url) && parsedData.url !== '') {
1133 e.target.form.elements.url.value = parsedData.url;
1134 }
1135 if (isString(parsedData.title) && parsedData.title !== '') {
1136 e.target.form.elements.title.value = parsedData.title;
1137 }
1138 if (isString(parsedData.favicon) &&
1139 parsedData.favicon.match(/^data:image\/png;base64,/)) {
1140 e.target.form.querySelector('img.bookmark-favicon').src =
1141 parsedData.favicon;
1142 }
1143 }
1144 break;
1005 case 'click': 1145 case 'click':
1006 switch (e.target.name) { 1146 switch (e.target.name) {
1007 case 'edit-bookmark': 1147 case 'edit-bookmark':
1008 e.target.blur(); 1148 e.target.blur();
1009 // fallthrough 1149 // fallthrough
1038 } else { 1178 } else {
1039 tags.push(e.target.tag.value.trim()); 1179 tags.push(e.target.tag.value.trim());
1040 } 1180 }
1041 1181
1042 this.notify('save-bookmark', e.target.url.value, 1182 this.notify('save-bookmark', e.target.url.value,
1043 e.target.title.value, tags, e.target['original-url'].value); 1183 e.target.title.value, e.target.favicon.value, tags,
1184 e.target['original-url'].value);
1044 } else if (e.target.id === 'search-form') { 1185 } else if (e.target.id === 'search-form') {
1045 // search 1186 // search
1046 e.preventDefault(); 1187 e.preventDefault();
1047 e.target.blur(); 1188 e.target.blur();
1048 1189
1081 }; 1222 };
1082 1223
1083 BookmarkView.prototype.onBookmarkAdded = function (bookmark) { 1224 BookmarkView.prototype.onBookmarkAdded = function (bookmark) {
1084 var newNode; 1225 var newNode;
1085 var bookmarkElement; 1226 var bookmarkElement;
1227 var faviconElement;
1086 var linkElement; 1228 var linkElement;
1087 var hostnameElement; 1229 var hostnameElement;
1088 var urlElement; 1230 var urlElement;
1089 var ctimeElement; 1231 var ctimeElement;
1090 var mtimeElement; 1232 var mtimeElement;
1092 1234
1093 newNode = document.importNode(this.bookmarkTemplate.content, true); 1235 newNode = document.importNode(this.bookmarkTemplate.content, true);
1094 1236
1095 bookmarkElement = newNode.querySelector('li'); 1237 bookmarkElement = newNode.querySelector('li');
1096 bookmarkElement.dataset.bookmarkUrl = bookmark.url; 1238 bookmarkElement.dataset.bookmarkUrl = bookmark.url;
1239
1240 faviconElement = bookmarkElement.querySelector('img.bookmark-favicon');
1241 faviconElement.src = (bookmark.favicon) ? bookmark.favicon :
1242 this.missingFaviconUri;
1243 faviconElement.alt = '';
1097 1244
1098 linkElement = bookmarkElement.querySelector('a.bookmark-link'); 1245 linkElement = bookmarkElement.querySelector('a.bookmark-link');
1099 linkElement.textContent = linkElement.title = bookmark.title; 1246 linkElement.textContent = linkElement.title = bookmark.title;
1100 linkElement.href = bookmark.url; 1247 linkElement.href = bookmark.url;
1101 1248
1209 1356
1210 BookmarkView.prototype.displayBookmarkEditor = function (bookmark) { 1357 BookmarkView.prototype.displayBookmarkEditor = function (bookmark) {
1211 var bookmarkElement; 1358 var bookmarkElement;
1212 var newNode; 1359 var newNode;
1213 var formElement; 1360 var formElement;
1361 var faviconImageElement;
1214 var editTagListElement; 1362 var editTagListElement;
1215 1363
1216 bookmarkElement = 1364 bookmarkElement =
1217 this.bookmarkListElement.querySelector('ul#bookmark-list > li' + 1365 this.bookmarkListElement.querySelector('ul#bookmark-list > li' +
1218 createDatasetSelector('bookmark-url', bookmark.url)); 1366 createDatasetSelector('bookmark-url', bookmark.url));
1228 formElement = newNode.querySelector('form.bookmark-editor-form'); 1376 formElement = newNode.querySelector('form.bookmark-editor-form');
1229 formElement.querySelector('legend').textContent = 'Edit Bookmark'; 1377 formElement.querySelector('legend').textContent = 'Edit Bookmark';
1230 formElement['original-url'].value = bookmark.url; 1378 formElement['original-url'].value = bookmark.url;
1231 formElement.url.value = bookmark.url; 1379 formElement.url.value = bookmark.url;
1232 formElement.title.value = bookmark.title; 1380 formElement.title.value = bookmark.title;
1381
1382 faviconImageElement = formElement.querySelector('img.bookmark-favicon');
1383 faviconImageElement.addEventListener('load', this);
1384 faviconImageElement.addEventListener('error', this);
1385 this.missingFaviconUri = faviconImageElement.src;
1386 if (bookmark.favicon) {
1387 faviconImageElement.src = bookmark.favicon;
1388 }
1233 1389
1234 editTagListElement = formElement.querySelector('ul.tag-input-list'); 1390 editTagListElement = formElement.querySelector('ul.tag-input-list');
1235 bookmark.tags.forEach(function (tag) { 1391 bookmark.tags.forEach(function (tag) {
1236 editTagListElement.appendChild(this.createTagInputElement(tag)); 1392 editTagListElement.appendChild(this.createTagInputElement(tag));
1237 }, this); 1393 }, this);
1377 BooketController.prototype.onEditBookmark = function (bookmarkUrl) { 1533 BooketController.prototype.onEditBookmark = function (bookmarkUrl) {
1378 this.bookmarkView.displayBookmarkEditor( 1534 this.bookmarkView.displayBookmarkEditor(
1379 this.bookmarkModel.get(bookmarkUrl)); 1535 this.bookmarkModel.get(bookmarkUrl));
1380 }; 1536 };
1381 1537
1382 BooketController.prototype.onSaveBookmark = function (url, title, tags, 1538 BooketController.prototype.onSaveBookmark = function (url, title,
1383 originalUrl) { 1539 favicon, tags, originalUrl) {
1384 var ctime; 1540 var ctime;
1385 1541
1386 if (originalUrl === undefined) { 1542 if (originalUrl === undefined) {
1387 // saving new bookmark, get confirmation before replacing existing one 1543 // saving new bookmark, get confirmation before replacing existing one
1388 if (this.bookmarkModel.has(url)) { 1544 if (this.bookmarkModel.has(url)) {
1401 ctime = (this.bookmarkModel.has(url)) ? 1557 ctime = (this.bookmarkModel.has(url)) ?
1402 this.bookmarkModel.get(url).ctime : new Date(); 1558 this.bookmarkModel.get(url).ctime : new Date();
1403 1559
1404 this.bookmarkModel.delete(originalUrl); 1560 this.bookmarkModel.delete(originalUrl);
1405 } 1561 }
1406 this.bookmarkModel.add(new Bookmark(url, title, tags, ctime)); 1562 this.bookmarkModel.add(new Bookmark(url, title, favicon, tags, ctime));
1407 }; 1563 };
1408 1564
1409 BooketController.prototype.onDeleteBookmark = function (bookmarkUrl) { 1565 BooketController.prototype.onDeleteBookmark = function (bookmarkUrl) {
1410 if (this.bookmarkView.confirmDeleteBookmark( 1566 if (this.bookmarkView.confirmDeleteBookmark(
1411 this.bookmarkModel.get(bookmarkUrl))) { 1567 this.bookmarkModel.get(bookmarkUrl))) {