diff 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
line wrap: on
line diff
--- a/booket.js	Wed Sep 10 19:45:23 2014 +0200
+++ b/booket.js	Sun Sep 14 23:12:37 2014 +0200
@@ -25,15 +25,73 @@
 'use strict';
 
 var BOOKMARKLET_URI =
-    'javascript:(function () {' +
-        '\'use strict\';' +
+    'javascript:(function() {' +
+         '\'use strict\';' +
+     '' +
+        'function displayBookmarkData(bookmarkData) {' +
+            'window.alert(\'Copy the following data and paste it into \' +' +
+                '\'Booket:\\n\\n\' + JSON.stringify(bookmarkData));' +
+        '}' +
+    '' +
+        'var bookmarkData = {' +
+            '\'url\': document.URL,' +
+            '\'title\': document.title,' +
+            '\'favicon\': undefined' +
+        '};' +
+        'var faviconLinkElement;' +
+        'var faviconUrls = [];' +
+        'var aElement;' +
+        'var canvasElement;' +
+        'var canvasCtx;' +
+        'var imgElement;' +
+    '' +
+        'aElement = document.createElement(\'a\');' +
+        'aElement.href = document.URL;' +
+    '' +
+        'faviconUrls.push(aElement.protocol + \'//\' + aElement.host + ' +
+            '\'/favicon.ico\');' +
+    '' +
+        'faviconLinkElement = document.querySelector(' +
+            '\'link[rel~=\\\'icon\\\']\');' +
+        'if (faviconLinkElement !== null) {' +
+            'faviconUrls.push(faviconLinkElement.href);' +
+        '}' +
     '' +
-        'window.alert(\'Copy the following data and paste it into \' +' +
-            '\'Booket:\\n\\n\' + JSON.stringify({' +
-                '\'url\': document.URL,' +
-                '\'title\': document.title' +
-            '}));' +
-    '}) ();';
+        'canvasElement = document.createElement(\'canvas\');' +
+        'canvasCtx = canvasElement.getContext(\'2d\');' +
+    '' +
+        'imgElement = new Image();' +
+        'imgElement.addEventListener(\'load\', function(e) {' +
+            'var faviconUrl;' +
+    '' +
+            'canvasElement.width = 16;' +
+            'canvasElement.height = 16;' +
+            'canvasCtx.clearRect(0, 0, 16, 16);' +
+            'try {' +
+                'canvasCtx.drawImage(this, 0, 0, 16, 16);' +
+                'bookmarkData.favicon = canvasElement.toDataURL();' +
+            '} catch (exception) {' +
+                'faviconUrl = faviconUrls.pop();' +
+            '}' +
+            'if (bookmarkData.favicon !== undefined || ' +
+                    'faviconUrl === undefined) {' +
+                'displayBookmarkData(bookmarkData);' +
+            '} else {' +
+                'imgElement.src = faviconUrl;' +
+            '}' +
+        '});' +
+        'imgElement.addEventListener(\'error\', function(e) {' +
+            'var faviconUrl;' +
+    '' +
+            'faviconUrl = faviconUrls.pop();' +
+            'if (faviconUrl !== undefined) {' +
+                'imgElement.src = faviconUrl;' +
+            '} else {' +
+                'displayBookmarkData(bookmarkData);' +
+            '}' +
+        '});' +
+        'imgElement.src = faviconUrls.pop();' +
+    '})();';
 
 
 /*
@@ -348,7 +406,7 @@
  * model
  */
 
-var Bookmark = function (url, title, tags, ctime, mtime) {
+var Bookmark = function (url, title, favicon, tags, ctime, mtime) {
     var parsedTime;
 
     if (!isString(url)) {
@@ -358,6 +416,12 @@
 
     this.title = (isString(title) && title !== '') ? title : url;
 
+    if (isString(favicon) && favicon.match(/^data:image\/png;base64,/)) {
+        this.favicon = favicon;
+    } else {
+        this.favicon = undefined;
+    }
+
     if (Array.isArray(tags)) {
         // remove duplicates, non-string or empty tags and tags containing
         // commas
@@ -647,7 +711,8 @@
     parsedData.bookmarks.forEach(function (bookmark) {
         if (isString(bookmark.url) && bookmark.url !== '') {
             bookmarks.push(new Bookmark(bookmark.url, bookmark.title,
-                bookmark.tags, bookmark.ctime, bookmark.mtime));
+                bookmark.favicon, bookmark.tags, bookmark.ctime,
+                bookmark.mtime));
         }
     }, this);
 
@@ -822,7 +887,6 @@
     var saveFormElement;
     var loadFormElement;
     var newNode;
-    var editorFormElement;
 
     ObservableMixin.call(this);
 
@@ -839,17 +903,24 @@
     newNode = document.importNode(
         document.querySelector('#bookmark-editor-template').content, true);
 
-    editorFormElement = newNode.querySelector('form.bookmark-editor-form');
-    editorFormElement.querySelector('legend').textContent = 'Add Bookmark';
-    editorFormElement.querySelector('input:not([type="hidden"])').accessKey =
-        'a';
-    editorFormElement.addEventListener('input', this);
-    editorFormElement.addEventListener('click', this);
-    editorFormElement.addEventListener('submit', this);
-    editorFormElement.addEventListener('reset', this);
+    this.editorFormElement = newNode.querySelector('form.bookmark-editor-form');
+    this.editorFormElement.querySelector('legend').textContent = 'Add Bookmark';
+    this.editorFormElement.querySelector(
+        'input:not([type="hidden"])').accessKey = 'a';
+    this.editorFormElement.addEventListener('input', this);
+    this.editorFormElement.addEventListener('click', this);
+    this.editorFormElement.addEventListener('submit', this);
+    this.editorFormElement.addEventListener('reset', this);
+
+    this.faviconImageElement =
+        this.editorFormElement.querySelector('img.bookmark-favicon');
+    this.faviconImageElement.addEventListener('load', this);
+    this.faviconImageElement.addEventListener('error', this);
+
+    this.missingFaviconUri = this.faviconImageElement.src;
 
     this.editTagListElement =
-        editorFormElement.querySelector('ul.tag-input-list');
+        this.editorFormElement.querySelector('ul.tag-input-list');
     this.editTagListElement.appendChild(this.createTagInputElement(''));
 
     saveFormElement.parentNode.insertBefore(newNode,
@@ -876,6 +947,19 @@
     var i;
 
     switch (e.type) {
+    case 'error':
+        if (e.target.classList.contains('bookmark-favicon')) {
+            if (e.target.src !== this.missingFaviconUri) {
+                e.target.src = this.missingFaviconUri;
+            }
+        }
+        break;
+    case 'load':
+        if (e.target.classList.contains('bookmark-favicon')) {
+            this.editorFormElement.favicon.value =
+                (e.target.src !== this.missingFaviconUri) ?  e.target.src : '';
+        }
+        break;
     case 'input':
         if (e.target.name === 'bookmarklet-import') {
             // get rid of any preceding text
@@ -893,6 +977,10 @@
             if (isString(parsedData.title) && parsedData.title !== '') {
                 e.target.form.elements.title.value = parsedData.title;
             }
+            if (isString(parsedData.favicon) &&
+                    parsedData.favicon.match(/^data:image\/png;base64,/)) {
+                this.faviconImageElement.src = parsedData.favicon;
+            }
         }
         break;
     case 'click':
@@ -928,7 +1016,7 @@
             }
 
             this.notify('save-bookmark', e.target.url.value,
-                e.target.title.value, tags);
+                e.target.title.value, e.target.favicon.value, tags);
 
             e.target.reset();
         }
@@ -937,6 +1025,9 @@
         if (e.target.classList.contains('bookmark-editor-form')) {
             e.target.blur();
 
+            e.target.querySelector('img.bookmark-favicon').src =
+                this.missingFaviconUri;
+
             // remove all but one tag input element
             while (this.editTagListElement.firstChild !== null) {
                 this.editTagListElement.removeChild(
@@ -979,6 +1070,7 @@
     this.tagInputTemplate = document.querySelector('#tag-input-template');
 
     this.bookmarkListElement = document.querySelector('ul#bookmark-list');
+    this.bookmarkListElement.addEventListener('input', this);
     this.bookmarkListElement.addEventListener('click', this);
     this.bookmarkListElement.addEventListener('submit', this);
     this.bookmarkListElement.addEventListener('reset', this);
@@ -991,17 +1083,65 @@
 
     this.bookmarkMessageElement = document.querySelector('#bookmark-message');
 
+    this.missingFaviconUri = '';
+
     this.updateBookmarkMessage();
 };
 
 extend(BookmarkView, ObservableMixin);
 
 BookmarkView.prototype.handleEvent = function (e) {
+    var bookmarkletData;
+    var parsedData;
     var i;
     var tags = [];
     var node;
 
     switch (e.type) {
+    case 'error':
+        if (e.target.classList.contains('bookmark-favicon')) {
+            if (e.target.src !== this.missingFaviconUri) {
+                e.target.src = this.missingFaviconUri;
+            }
+        }
+        break;
+    case 'load':
+        if (e.target.classList.contains('bookmark-favicon')) {
+            node = e.target;
+            while ((node = node.parentNode) !== null) {
+                if (node.classList.contains('bookmark-editor-form')) {
+                    node.favicon.value =
+                        (e.target.src !== this.missingFaviconUri) ?
+                        e.target.src : '';
+                    break;
+                }
+            }
+        }
+        break;
+    case 'input':
+        if (e.target.name === 'bookmarklet-import') {
+            // get rid of any preceding text
+            bookmarkletData = e.target.value.replace(/^[^{]*/, '');
+
+            try {
+                parsedData = JSON.parse(bookmarkletData);
+            } catch (exception) {
+                return;
+            }
+
+            if (isString(parsedData.url) && parsedData.url !== '') {
+                e.target.form.elements.url.value = parsedData.url;
+            }
+            if (isString(parsedData.title) && parsedData.title !== '') {
+                e.target.form.elements.title.value = parsedData.title;
+            }
+            if (isString(parsedData.favicon) &&
+                    parsedData.favicon.match(/^data:image\/png;base64,/)) {
+                e.target.form.querySelector('img.bookmark-favicon').src =
+                    parsedData.favicon;
+            }
+        }
+        break;
     case 'click':
         switch (e.target.name) {
         case 'edit-bookmark':
@@ -1040,7 +1180,8 @@
             }
 
             this.notify('save-bookmark', e.target.url.value,
-                e.target.title.value, tags, e.target['original-url'].value);
+                e.target.title.value, e.target.favicon.value, tags,
+                e.target['original-url'].value);
         } else if (e.target.id === 'search-form') {
             // search
             e.preventDefault();
@@ -1083,6 +1224,7 @@
 BookmarkView.prototype.onBookmarkAdded = function (bookmark) {
     var newNode;
     var bookmarkElement;
+    var faviconElement;
     var linkElement;
     var hostnameElement;
     var urlElement;
@@ -1095,6 +1237,11 @@
     bookmarkElement = newNode.querySelector('li');
     bookmarkElement.dataset.bookmarkUrl = bookmark.url;
 
+    faviconElement = bookmarkElement.querySelector('img.bookmark-favicon');
+    faviconElement.src = (bookmark.favicon) ? bookmark.favicon :
+        this.missingFaviconUri;
+    faviconElement.alt = '';
+
     linkElement = bookmarkElement.querySelector('a.bookmark-link');
     linkElement.textContent = linkElement.title = bookmark.title;
     linkElement.href = bookmark.url;
@@ -1211,6 +1358,7 @@
     var bookmarkElement;
     var newNode;
     var formElement;
+    var faviconImageElement;
     var editTagListElement;
 
     bookmarkElement =
@@ -1231,6 +1379,14 @@
     formElement.url.value = bookmark.url;
     formElement.title.value = bookmark.title;
 
+    faviconImageElement = formElement.querySelector('img.bookmark-favicon');
+    faviconImageElement.addEventListener('load', this);
+    faviconImageElement.addEventListener('error', this);
+    this.missingFaviconUri = faviconImageElement.src;
+    if (bookmark.favicon) {
+        faviconImageElement.src = bookmark.favicon;
+    }
+
     editTagListElement = formElement.querySelector('ul.tag-input-list');
     bookmark.tags.forEach(function (tag) {
         editTagListElement.appendChild(this.createTagInputElement(tag));
@@ -1379,8 +1535,8 @@
         this.bookmarkModel.get(bookmarkUrl));
 };
 
-BooketController.prototype.onSaveBookmark = function (url, title, tags,
-        originalUrl) {
+BooketController.prototype.onSaveBookmark = function (url, title,
+        favicon, tags, originalUrl) {
     var ctime;
 
     if (originalUrl === undefined) {
@@ -1403,7 +1559,7 @@
 
         this.bookmarkModel.delete(originalUrl);
     }
-    this.bookmarkModel.add(new Bookmark(url, title, tags, ctime));
+    this.bookmarkModel.add(new Bookmark(url, title, favicon, tags, ctime));
 };
 
 BooketController.prototype.onDeleteBookmark = function (bookmarkUrl) {