PageSourceSearch

https://www.smaheya.com/js/bookmark_button.js

js smaheya.com collected 2026-10-05 07:56:18 UTC 31,067 bytes, 780 lines download raw bytes

1var Hatena = Hatena || {};
2
3/* UTF-8 でないページから読み込まれても動くように、
4 * コメントは複数行コメント (閉じる "*" の前にスペースを置く) を使い、
5 * 文字列リテラル中の ASCII 外の文字は \uXXXX とエスケープする。
6 */
7
8(function (Hatena) {
9
10var B = Hatena.Bookmark = Hatena.Bookmark || {};
11if (B.BookmarkButton) return;
12
13if (location.protocol === 'https:') {
14    B.apiOrigin = 'https://b.hatena.ne.jp';
15} else {
16    B.apiOrigin = 'http://cdn.api.b.hatena.ne.jp';
17}
18B.origin = location.protocol + 'http://b.hatena.ne.jp';
19B.staticOrigin = location.protocol + 'http://b.st-hatena.com';
20
21var match = new RegExp('^((?:local|b(?!log)\\w+)\\.hatena\\.ne\\.jp(?::3000)?)$').exec(location.host);
22var isLocal;
23if (match) {
24    B.origin = B.apiOrigin = B.staticOrigin = 'http://' + match[1];
25    isLocal = true;
26}
27
28var U = B.ButtonUtils = {};
29
30var extend = U.extend = function (dest, src) {
31    for (var i in src)
32        dest[i] = src[i];
33    return dest;
34};
35
36var E = U.createElement = function (name, props) {
37    var element = document.createElement(name);
38    for (var p in props)
39        element[p] = props[p];
40    for (var i = 2; i < arguments.length; i++) {
41        var child = arguments[i];
42        if (!child.nodeType)
43            child = document.createTextNode(child);
44        element.appendChild(child);
45    }
46    return element;
47};
48
49var getLocation = U.getLocation = function () {
50    var url = location.href;
51    /* Safari は location.href でパス中の URI エスケープを
52     * デコードしてしまうので、document.URL も調べてみる
53     */
54    if (url.length < document.URL.length)
55        url = document.URL;
56    return url;
57};
58
59var View = U.View = {
60    root: null,
61    clientLeft: 0,
62    clientTop: 0,
63    init: function () {
64        this.root = ((document.compatMode || '') === 'CSS1Compat')
65                    ? document.documentElement : document.body;
66        this.clientLeft = this.root.clientLeft || 0;
67        this.clientTop = this.root.clientTop || 0;
68    },
69    getElementRect: function (element) {
70        if (!this.root) this.init();
71        this.getElementRect = element.getBoundingClientRect
72            ? this.getElementRectByRect : this.getElementRectByOffset;
73        return this.getElementRect(element);
74    },
75    getElementRectByRect: function (element) {
76        var rect = element.getBoundingClientRect();
77        var scroll = this.getScroll();
78        return {
79            x: rect.left + scroll.x - this.clientLeft,
80            y: rect.top + scroll.y - this.clientTop,
81            width: rect.width || rect.right - rect.left,
82            height: rect.height || rect.bottom - rect.top
83        };
84    },
85    getElementRectByOffset: function (element) {
86        var x = 0, y = 0;
87        for (var node = element, base; base = node.offsetParent; node = base) {
88            x += node.offsetLeft;
89            y += node.offsetTop;
90        }
91        return { x: x, y: y, width: element.offsetWidth, height: element.offsetHeight };
92    },
93    getWindowSize: function () {
94        if (!this.root) this.init();
95        return { width: this.root.clientWidth, height: this.root.clientHeight };
96    },
97    getDocumentSize: function () {
98        if (!this.root) this.init();
99        return { width: this.root.scrollWidth, height: this.root.scrollHeight };
100    },
101    getScroll: function () {
102        if (!this.root) this.init();
103        this.getScroll = (typeof window.pageXOffset === 'number')
104                         ? this.getScrollByPage : this.getScrollByRoot;
105        return this.getScroll();
106    },
107    getScrollByPage: function () {
108        return { x: window.pageXOffset, y: window.pageYOffset };
109    },
110    getScrollByRoot: function () {
111        return { x: this.root.scrollLeft, y: this.root.scrollTop };
112    }
113};
114
115var Dispatchable = U.Dispatchable = {
116    addEventListener: function (type, listener) {
117        var listeners = this.getListeners(type);
118        for (var i = 0; i < listeners.length; i++)
119            if (listeners[i] === listener)
120                return;
121        listeners.push(listener);
122    },
123    removeEventListener: function (type, listener) {
124        var listeners = this.getListeners(type);
125        for (var i = 0; i < listeners.length; i++) {
126            if (listeners[i] === listener) {
127                listeners.splice(i, 1);
128                return;
129            }
130        }
131    },
132    dispatchEvent: function (type, data) {
133        var event = new Dispatchable.Event(type, data);
134        var listeners = this.getListeners(type);
135        for (var i = 0; i < listeners.length; i++)
136            listeners[i].call(this, event);
137        return !event.defaultPrevented;
138    },
139    getListeners: function (type) {
140        if (!this.hasOwnProperty('_listenersMap'))
141            this._listenersMap = {};
142        return this._listenersMap[type] || (this._listenersMap[type] = []);
143    }
144};
145
146Dispatchable.Event = function (type, data) {
147    this.type = type;
148    this.data = data;
149    this.defaultPrevented = false;
150};
151
152extend(Dispatchable.Event.prototype, {
153    preventDefault: function () {
154        this.defaultPrevented = true;
155    }
156});
157
158var Observer = U.Observer = function (target, type, handler, method) {
159    this.target = target;
160    this.type = type;
161    this.listener = handler;
162    if (method) {
163        this.listener = (typeof method === 'string')
164            ? function () { return handler[method].apply(handler, arguments); }
165            : function () { return handler.apply(method, arguments); };
166    }
167    if (!target.addEventListener && target.attachEvent) {
168        var listener = this.listener;
169        this.listener = function (event) {
170            return listener.call(target, Observer.WrappedEvent.create(event));
171        };
172        this.start = this.startAttach;
173        this.stop = this.stopAttach;
174    }
175    this.start();
176};
177
178extend(Observer.prototype, {
179    start: function () {
180        this.target.addEventListener(this.type, this.listener, false);
181    },
182    stop: function () {
183        this.target.removeEventListener(this.type, this.listener, false);
184    },
185    startAttach: function () {
186        this.target.attachEvent('on' + this.type, this.listener);
187    },
188    stopAttach: function () {
189        this.target.detachEvent('on' + this.type, this.listener);
190    }
191});
192
193Observer.WrappedEvent = {
194    create: function (event) {
195        /* 一部の IE8 でのクラッシュ対策
196         * cf: http://vividcode.hatenablog.com/entry/ie/ie8_posmes_crash */
197        if ( event.type === "message" ) {
198            return event;
199        }
200        var e = document.createEventObject(event);
201        e._event = event;
202        e.target = event.srcElement;
203        var scroll = View.getScroll();
204        e.pageX = event.clientX + scroll.x - View.clientLeft;
205        e.pageY = event.clientY + scroll.y - View.clientTop;
206        e.stopPropagation = this.stopPropagation;
207        e.preventDefault = this.preventDefault;
208        return e;
209    },
210    stopPropagation: function () {
211        this._event.cancelBubble = true;
212    },
213    preventDefault: function () {
214        this._event.returnValue = false;
215    }
216};
217
218var JSON = U.JSON = window.JSON || {
219    _tokenRE: /[{}\[\],:]|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b|\"(?:[^\u0000-\u001f\"\\]|\\(?:[\"\\\/bfnrt]|u[0-9A-Fa-f]{4}))*\"|\b(?:true|false|null)\b|\s+/g,
220    _escapeChar: function (c) {
221        return '\\u' + (0x10000 + c.charCodeAt(0)).toString(16).substring(1);
222    },
223
224    parse: function (json) {
225        json = String(json);
226        if (json.replace(JSON._tokenRE, '') !== '')
227            throw new Error('Invalid JSON sytax');
228        return eval('(' + json + ')');
229    },
230    stringify: function (value) {
231        switch (typeof value) {
232        case 'string':
233            return '"' + value.replace(/[\u0000-\u001f\"\\\u2028\u2029]/g, JSON._escapeChar) + '"';
234        case 'number':
235        case 'boolean':
236            return '' + value;
237        case 'object':
238            if (!value) return 'null';
239            var stringify = JSON.stringify;
240            var type = Object.prototype.toString.call(value).slice(8, -1);
241            switch (type) {
242            case 'String': case 'Number': case 'Boolean':
243                return stringify(value.valueOf());
244            case 'Array':
245                var members = [];
246                for (var i = 0; i < value.length; i++)
247                    members.push(stringify(value[i]));
248                return '[' + members.join(',') + ']';
249            case 'Object':
250                var members = [];
251                for (var i in value)
252                    if (value.hasOwnProperty(i))
253                        members.push(stringify(i) + ':' + stringify(value[i]));
254                return '{' + members.join(',') + '}';
255            }
256            return 'null';
257        }
258        return 'null';
259    }
260};
261
262var WindowMessenger = U.WindowMessenger = function (win, url) {
263    this.win = win;
264    this.origin = url.replace(/^(https?:\/\/[^\/?#]+)[\s\S]*/, '$1');
265    this.observer = new Observer(window, 'message', this, 'messageHandler');
266};
267
268extend(WindowMessenger, {
269    usePostMessage: true,
270    keySeed: (Math.random() * 0x10000) << 8,
271    createForFrame: function (frame, url) {
272        var win = frame.contentWindow;
273        /* WebKit ではフレームごとに別の名前をつける必要あり */
274        win.name = (++this.keySeed).toString(36) + '|' + getLocation();
275        win.location.replace(url);
276        return new this(win, url);
277    }
278});
279
280extend(WindowMessenger.prototype, Dispatchable);
281extend(WindowMessenger.prototype, {
282    send: function (type, data) {
283        if (typeof data === 'undefined') data = null;
284        var message = 'HBMessage@' + JSON.stringify({ type: type, data: data });
285        this.win.postMessage(message, this.origin);
286    },
287    messageHandler: function (event) {
288        var origin = event.origin || event.uri.replace(/^(https?:\/\/[^\/?#]+)[\s\S]*/, '$1');
289        if (origin !== this.origin || event.source != this.win ||
290            !/^HBMessage@/.test(event.data)) return;
291        var message = JSON.parse(event.data.substring(10));
292        this.dispatchEvent(message.type, message.data);
293    },
294    destroy: function () {
295        this.observer.stop();
296    }
297});
298
299if (!window.postMessage) {
300
301WindowMessenger = U.WindowMessenger = function (win, url, key) {
302    this.win = win;
303    this.url = url.replace(/#[\s\S]*/, '');
304    this.key = key;
305    WindowMessenger.instances[this.key] = this;
306    if (!WindowMessenger.timerId) WindowMessenger.init();
307};
308
309extend(WindowMessenger, {
310    usePostMessage: false,
311    instances: {},
312    interval: 20,
313    timerId: 0,
314    createForFrame: function (frame, url) {
315        var win = frame.contentWindow;
316        var key = this.makeFNVHash(url + '|' + Math.random()).toString(36);
317        win.name = key + '|' + getLocation();
318        win.location.replace(url);
319        return new this(win, url, key);
320    },
321    /* http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function */
322    makeFNVHash: function (string) {
323        var hash = 2166136261;
324        for (var i = 0; i < string.length; i++)
325            hash = ((hash * 16777619) ^ string.charCodeAt(i)) >>> 0;
326        return hash;
327    },
328    init: function () {
329        this.lastFragment = location.hash;
330        var WM = this;
331        this.timerId = window.setInterval(function () { WM.observe(); }, this.interval);
332    },
333    observe: function () {
334        var fragment = location.hash;
335        if (fragment === this.lastFragment) return;
336        var match = fragment.match(/^#HBMessage-(\w+)-([^\/]+)\/(.+)
336/);
337        if (!match) {
338            this.lastFragment = fragment;
339            return;
340        }
341        /* "#" だとページ最上部へのスクロールが発生するので "#_" で */
342        this.lastFragment = this.lastFragment || '#_';
343        var scroll = View.getScroll();
344        location.replace(this.lastFragment);
345        window.scrollTo(scroll.x, scroll.y);
346        var messenger = this.instances[match[1]];
347        if (!messenger) return;
348        var type = decodeURIComponent(match[2]);
349        var data = JSON.parse(decodeURIComponent(match[3]));
350        messenger.dispatchEvent(type, data);
351    }
352});
353
354extend(WindowMessenger.prototype, Dispatchable);
355extend(WindowMessenger.prototype, {
356    send: function (type, data) {
357        if (typeof data === 'undefined') data = null;
358        var url = this.url + '#HBMessage-' + this.key + '-' +
359                  encodeURIComponent(type) + '/' +
360                  encodeURIComponent(JSON.stringify(data));
361        try {
362            this.win.location.replace(url);
363        } catch (ex) {
364            this.win.location.href = url;
365        }
366    },
367    destroy: function () {
368        delete WindowMessenger.instances[this.key];
369    }
370});
371
372} /* WindowMessenger implementation with fragment identifier */
373
374B.BookmarkButton = function (link) {
375    this.url = link.getAttribute('data-hatena-bookmark-url') ||
376               B.BookmarkButton.extractURL(link.href) ||
377               B.BookmarkButton.getCurrentURL();
378    this.link = link;
379    this.mode = B.BookmarkButton.isTouchBrowser
380        ? 'goto-touch' : this.link.getAttribute('data-hatena-bookmark-mode');
381    /* IE 7 はデフォルトのセキュリティ設定によりフレーム内でのパネルの動作に
382     * 支障をきたすので、フレーム内ではポップアップを開くことを強制する。
383     */
384    if (!WindowMessenger.usePostMessage &&
385        /\bMSIE 7\b/.test(navigator.userAgent) &&
386        window.self != window.top) {
387        this.mode = 'popup';
388    }
389    this.setup();
390};
391
392extend(B.BookmarkButton, {
393    interval: 428, /* Welcome to Shibuya.js! */
394    timerId: 0,
395    setup: function () {
396        if (this.timerId) return;
397        var Button = this;
398        this.timerId = window.setInterval(function () { Button.tryCreate(); }, this.interval);
399        this.tryCreate();
400    },
401    lastLinkCount: 0,
402    tryCreate: function () {
403        var links = document.getElementsByTagName('a');
404        var linkCount = links.length;
405        if (linkCount === this.lastLinkCount) return;
406        var buttonLinks = [];
407        var classRE = /(?:^|\s)hatena-bookmark-button(?:\s|$)/;
408        for (var i = 0; i < linkCount; i++)
409            if (classRE.test(links[i].className) &&
410                !links[i].getAttribute('data-hatena-bookmark-initialized'))
411                buttonLinks.push(links[i]);
412        for (var i = 0; i < buttonLinks.length; i++)
413            new B.BookmarkButton(buttonLinks[i]);
414        this.lastLinkCount = links.length;
415    },
416    extractURL: function (entryURL) {
417        if (!entryURL) return null;
418        var match = entryURL.match(/^http:\/\/b\.hatena\.ne\.jp\/entry\/(?:add\/)?(.+)/);
419        if (!match) return null;
420        var url = match[1];
421        var parts = url.match(/^(?:https?(?:(:)|(%3A))|(s\/))?/);
422        if (parts[2]) {
423            try {
424                return decodeURIComponent(url);
425            } catch (ex) {
426                return unescape(url);
427            }
428        }
429        if (!parts[1])
430            url = parts[3] ? 'https://' + url.substring(2) : 'http://' + url;
431        return url.replace(/%23/g, '#');
432    },
433    getCurrentURL: function () {
434        var url = getLocation();
435        var canonicalURL = this.getCanonicalURL();
436        if (canonicalURL) {
437            var index = url.indexOf('#');
438            if (index >= 0 && canonicalURL.indexOf('#') < 0)
439                canonicalURL += url.substring(index);
440            url = canonicalURL;
441        }
442        return url;
443    },
444    getCanonicalURL: function () {
445        var links = document.getElementsByTagName('link');
446        for (var i = 0; i < links.length; i++) {
447            if (links[i].rel.toLowerCase() !== 'canonical' || !links[i].href) continue;
448            /* IE で href プロパティが絶対 URL に解決されないことがある */
449            var a = document.createElement('a');
450            a.href = links[i].href;
451            return a.cloneNode(false).href;
452        }
453        return null;
454    },
455    isTouchBrowser: /\b(?:iPhone|iPod);| Android /.test(navigator.userAgent),
456    forcedLayout: ''
457});
458
459extend(B.BookmarkButton.prototype, {
460    setup: function () {
461        var layout = B.BookmarkButton.forcedLayout || this.link.getAttribute('data-hatena-bookmark-layout');
462        if (layout === 'simple') {
463            this.link.setAttribute('data-hatena-bookmark-initialized', '1');
464            this.observer = new Observer(this.link, 'click', this, 'clickHandler');
465            this.button = this.link;
466            /* リンクの内容が画像ひとつのみならその画像を基準にパネルを表示 */
467            var images = this.link.getElementsByTagName('img');
468            this.anchor = (images.length === 1) ? images[0] : this.link;
469            if (!isLocal && !/^https?:\/\/b\.hatena\.ne\.jp\/(?!articles(?![\w-]))/.test(getLocation())) {
470                this.image = new Image();
471                this.image.src = B.apiOrigin + '/entry/button/?url=' + encodeURIComponent(this.url) + '&layout=simple&format=image';
472            }
473        } else {
474            var config = {
475                vertical: { height: 50 },
476                'simple-balloon'  : { width: 70,  height: 20 },
477                'standard-balloon': { width: 130, height: 20 },
478                'standard-noballoon': { width: 80, height: 20 },
479                'vertical-balloon': { width: 80,  height: 60 }
480            };
481            var width = 50, height = 20;
482            if (config[layout]) {
483                width  = config[layout]['width']  || width;
484                height = config[layout]['height'] || height;
485            }
486            this.frame = E('iframe', { className: 'hatena-bookmark-button-frame', title: this.link.title, frameBorder: 0, scrolling: 'no', allowTransparency: true, width: width, height: height, src:'javascript:false' });
487            this.frame.style.cssText = 'width: ' + width + 'px; height: ' + height + 'px;';
488            this.link.parentNode.replaceChild(this.frame, this.link);
489
490            var buttonURL = B.apiOrigin + '/entry/button/?url=' + encodeURIComponent(this.url);
491            if (layout && layout !== 'standard')
492                buttonURL += '&layout=' + encodeURIComponent(layout);
493            var lang = this.link.getAttribute('data-hatena-bookmark-lang');
494            if (lang)
495                buttonURL += '&lang=' + encodeURIComponent(lang);
496            if (this.mode === 'popup')
497                buttonURL += '&mode=' + encodeURIComponent(this.mode);
498            this.messenger = WindowMessenger.createForFrame(this.frame, buttonURL);
499            this.observer = new Observer(this.messenger, 'click', this, 'clickHandler');
500            this._resizeObserver = new Observer(this.messenger, "resize", this, "resizeMessageHandler");
501            this.button = this.frame;
502            this.anchor = this.frame;
503        }
504    },
505    getTitle: function () {
506        var title = this.link.getAttribute('data-hatena-bookmark-title');
507        if (!title) {
508            title = this.link.getAttribute('data-hatena-bookmark-escaped-title');
509            if (title)
510                try { title = decodeURIComponent(title); } catch (ex) {}
511        }
512        if (!title && this.url.replace(/#.*/, '') === getLocation().replace(/#.*/, ''))
513            title = document.title;
514        return title;
515    },
516    togglePanel: function () {
517        if (!this.panel)
518            this.panel = new B.BookmarkPanel(this.url, this.getTitle());
519        if (this.panel.isShown) {
520            this.panel.hide();
521        } else {
522            var position = this.link.getAttribute('data-hatena-bookmark-position');
523            var hideBg = this.link.getAttribute('data-hatena-bookmark-hide-bg');
524            var hideBackground = !hideBg || hideBg.toLowerCase() !== 'false';
525            this.panel.show(this.anchor, position, hideBackground);
526        }
527    },
528    gotoTouchEntry: function () {
529        var entryURL = B.origin + '/entry.touch/';
530        var urlMatch = this.url.match(/^http(s)?:\/\/([\s\S]*)$/);
531        if (urlMatch)
532            entryURL += (urlMatch[1] ? 's/' : '') + urlMatch[2];
533        else
534            entryURL += this.url;
535        location.href = entryURL.replace(/#/g, '%23');
536    },
537    gotoTouchBookmarklet: function () {
538        var bookmarkletURL = B.origin + '/bookmarklet.touch?url=' + encodeURIComponent(this.url);
539        var title = this.getTitle();
540        if (title)
541            bookmarkletURL += '&btitle=' + encodeURIComponent(title);
542        location.href = bookmarkletURL;
543    },
544    clickHandler: function (event) {
545        if ((event.which || event.button || 0) > 1) return;
546        if (event.stopPropagation) event.stopPropagation();
547        if (event.preventDefault) event.preventDefault();
548        switch (this.mode) {
549        case 'popup':
550            B.BookmarkPanel.openPopup(this.url, this.getTitle());
551            break;
552        case 'goto-touch':
553            this.gotoTouchEntry();
554            break;
555        default:
556            this.togglePanel();
557        }
558    },
559    resizeMessageHandler: function (evt) {
560        var width = evt.data.width + "";
561        var height = evt.data.height + "";
562        if (/^\d+$/.test(width)) this.frame.style.width = width + "px";
563        if (/^\d+$/.test(height)) this.frame.style.height = height + "px";
564    },
565    destroy: function () {
566        if (this.observer) this.observer.stop();
567        if (this._resizeObserver) this._resizeObserver.stop();
568        if (this.messenger) this.messenger.destroy();
569        if (this.panel) this.panel.destroy();
570        if (this.button && this.button.parentNode)
571            this.button.parentNode.removeChild(this.button);
572    }
573});
574
575B.BookmarkPanel = function (url, title) {
576    this.url = url;
577    this.title = title || '';
578    this.isShown = false;
579    this.width = 365;
580    this.height = 160;
581    this.fixedTo = 'top';
582    this.hiddenFlashes = [];
583    this.clickObserver = new Observer(document, 'click', this, 'clickHandler');
584    this.clickObserver.stop();
585    this.observers = [this.clickObserver];
586    this.setup();
587};
588
589extend(B.BookmarkPanel, {
590    currentPanel: null,
591    makePanelURL: function (url, title) {
592        var panelURL = B.origin + '/entry/panel/?url=' + encodeURIComponent(url);
593        if (title)
594            panelURL += '&btitle=' + encodeURIComponent(title);
595        return panelURL;
596    },
597    openPopup: function (url, title) {
598        var panelURL = this.makePanelURL(url, title);
599        var win = window.open(panelURL, 'hatena_bookmark_panel_popup', 'width=365,height=220,menubar=no,toolbar=no,resizable=yes');
600    },
601    hasCommonStyle: false,
602    setupCommonStyle: function () {
603        if (this.hasCommonStyle) return;
604        this.hasCommonStyle = true;
605        var resetCSSDeclarations =
606            'margin: 0; padding: 0; border: none; ' +
607            'position: static; float: none; width: auto; height: auto; ' +
608            'line-height: 1; vertical-align: baseline; ' +
609            'color: #222; background: none; ' +
610            'font-style: normal; font-weight:normal; font-size: medium; ' +
611            'text-indent: 0; text-align: left; text-decoration: none; ' +
612            'letter-spacing: normal; word-spacing: normal; white-space: normal;';
613        var resetCSSText =
614            '.hatena-bookmark-bookmark-panel, ' +
615            '.hatena-bookmark-bookmark-panel * ' +
616            '{' + resetCSSDeclarations + '}';
617        try {
618            var style = document.createElement('style');
619            style.type = 'text/css';
620            style.appendChild(document.createTextNode(resetCSSText));
621            document.getElementsByTagName('head')[0].appendChild(style);
622        } catch (ex) {
623            document.createStyleSheet().cssText = resetCSSText;
624        }
625    },
626    needsRebase: null,
627    getBasePosition: function () {
628        if (this.needsRebase === null) {
629            var style = window.getComputedStyle
630                ? getComputedStyle(document.body, null)
631                : document.body.currentStyle;
632            this.needsRebase = !!style && style.position !== 'static';
633        }
634        return this.needsRebase ? View.getElementRect(document.body) : { x: 0, y: 0 };
635    }
636});
637
638extend(B.BookmarkPanel.prototype, {
639    setup: function () {
640        /* var frameTitle = (this.title ? '『' + this.title + '』' : 'このエントリー') + 'をはてなブックマークに追加'; */
641        var frameTitle = (this.title ? '\u300E' + this.title + '\u300F' : '\u3053\u306E\u30A8\u30F3\u30C8\u30EA\u30FC') + '\u3092\u306F\u3066\u306A\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF\u306B\u8FFD\u52A0';
642        this.panel = E('div', { className: 'hatena-bookmark-bookmark-panel' },
643            this.loading = E('div', null, E('span', null, 'Now Loading...')),
644            this.content = E('div', null,
645                this.frame = E('iframe', { title: frameTitle, scrolling: 'no', frameBorder: 0, allowTransparency: true, src: 'javascript:false' })
646            )
647        );
648        this.setupStyle();
649        document.body.appendChild(this.panel);
650        var panelURL = B.BookmarkPanel.makePanelURL(this.url, this.title);
651        this.messenger = WindowMessenger.createForFrame(this.frame, panelURL);
652        this.observers.push(
653            /* frame からのメッセージ受信失敗時のä
653¿é™º */
654            new Observer(this.frame, 'load', this, 'showContent'),
655            new Observer(this.messenger, 'ready', this, 'showContent'),
656            new Observer(this.messenger, 'resize', this, 'resizeMessageHandler')
657        );
658    },
659    setupStyle: function () {
660        B.BookmarkPanel.setupCommonStyle();
661        var size = 'width: ' + this.width + 'px; height: ' + this.height + 'px;';
662        this.panel.style.cssText = 'position: absolute; z-index: 10002; display: none; ' + size;
663        this.loading.style.cssText = 'background-color: #fff; border: 3px solid #2c6ebd; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; text-align: center; width: ' + (this.width - 6) + 'px; height: ' + (this.height - 6) + 'px;';
664        this.loading.firstChild.style.cssText = 'background: url("' + B.staticOrigin + '/images/loading.gif") left center no-repeat; padding-left: 23px; font-size: 0.85em; position: relative; top: ' + (this.height / 2 - 10) + 'px;';
665        this.content.style.cssText = 'display: none;';
666        this.frame.style.cssText = 'display: block; ' + size;
667    },
668    show: function (anchor, position, hideBackground) {
669        if (this.isShown) return;
670        if (B.BookmarkPanel.currentPanel)
671            B.BookmarkPanel.currentPanel.hide();
672        if (anchor)
673            this.anchorTo(anchor, position);
674        this.panel.style.display = '';
675        this.isShown = true;
676        this.clickObserver.start();
677        if (hideBackground)
678            this.hideFlashes();
679        B.BookmarkPanel.currentPanel = this;
680        if (!this.loading)
681            this.messenger.send('notifysize');
682    },
683    hideFlashes: function () {
684        var flashes = [];
685        var embeds = document.getElementsByTagName('embed');
686        for (var i = 0, embed; embed = embeds[i]; i++)
687            if (embed.type === 'application/x-shockwave-flash')
688                flashes.push(embed);
689        var objects = document.getElementsByTagName('object');
690        for (var i = 0, object; object = objects[i]; i++)
691            if (object.type === 'application/x-shockwave-flash' ||
692                (object.classid || '').toLowerCase() === 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000')
693                flashes.push(object);
694        if (!flashes.length) return;
695        var panelRect = View.getElementRect(this.panel);
696        var left = panelRect.x;
697        var top = panelRect.y;
698        var right = panelRect.x + panelRect.width;
699        var bottom = panelRect.y + Math.max(panelRect.height, 300);
700        for (var i = 0, flash; flash = flashes[i]; i++) {
701            var rect = View.getElementRect(flash);
702            if (rect.x + rect.width < left || right < rect.x ||
703                rect.y + rect.height < top || bottom < rect.y)
704                continue;
705            flash.style.visibility = 'hidden';
706            this.hiddenFlashes.push(flash);
707        }
708    },
709    showContent: function () {
710        if (!this.loading) return;
711        this.panel.removeChild(this.loading);
712        this.loading = null;
713        this.content.style.display = '';
714    },
715    hide: function () {
716        if (!this.isShown) return;
717        this.panel.style.display = 'none';
718        this.isShown = false;
719        this.clickObserver.stop();
720        for (var i = 0, flash; flash = this.hiddenFlashes[i]; i++)
721            flash.style.visibility = '';
722        this.hiddenFlashes = [];
723        B.BookmarkPanel.currentPanel = null;
724    },
725    resize: function (width, height) {
726        if (width > 0) {
727            this.panel.style.width = width + 'px';
728            this.frame.style.width = width + 'px';
729            this.width = width;
730        }
731        if (height > 0) {
732            if (this.fixedTo === 'bottom')
733                this.panel.style.top = parseFloat(this.panel.style.top) + this.height - height + 'px';
734            this.panel.style.height = height + 'px';
735            this.frame.style.height = height + 'px';
736            this.height = height;
737        }
738    },
739    anchorTo: function (anchor, position) {
740        var rect = View.getElementRect(anchor);
741        var isLeft = position && position.indexOf('left') >= 0;
742        var isTop = position && position.indexOf('top') >= 0;
743        if (!position) {
744            var size = View.getWindowSize();
745            var scroll = View.getScroll();
746            isLeft = (scroll.x + size.width) - rect.x < this.width &&
747                     (rect.x + rect.width) - scroll.x >= this.width;
748            var h = Math.max(this.height, 300);
749            isTop = (scroll.y + size.height) - (rect.y + rect.height) < h &&
750                    rect.y - scroll.y >= h;
751        }
752        var left = isLeft ? rect.x + rect.width - this.width : rect.x;
753        var top = isTop ? rect.y - this.height - 2 : rect.y + rect.height + 2;
754        var base = B.BookmarkPanel.getBasePosition();
755        this.panel.style.left = (Math.max(left, 2) - base.x) + 'px';
756        this.panel.style.top = (Math.max(top, 2) - base.y) + 'px';
757        this.fixedTo = isTop ? 'bottom' : 'top';
758    },
759    clickHandler: function (event) {
760        for (var node = event.target; node; node = node.parentNode)
761            if (node === this.panel)
762                return;
763        this.hide();
764    },
765    resizeMessageHandler: function (event) {
766        this.resize(event.data.width || -1, event.data.height || -1);
767    },
768    destroy: function () {
769        this.hide();
770        for (var i = 0, observer; observer = this.observers[i]; i++)
771            observer.stop();
772        this.messenger.destroy();
773        if (this.panel.parentNode)
774            this.panel.parentNode.removeChild(this.panel);
775    }
776});
777
778})(Hatena);
779
780Hatena.Bookmark.BookmarkButton.setup();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.