зеркало из https://github.com/mozilla/pjs.git
310 строки
8.5 KiB
JavaScript
310 строки
8.5 KiB
JavaScript
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Eric Hodel's <drbrain@segment7.net> code.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Eric Hodel.
|
|
* Portions created by the Initial Developer are Copyright (C) 2001
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Christopher Hoess <choess@force.stwing.upenn.edu>
|
|
* Tim Taylor <tim@tool-man.org>
|
|
* Stuart Ballard <sballard@netreach.net>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/**
|
|
* LinkToolbarHandler is a Singleton that displays LINK elements
|
|
* and nodeLists of LINK elements in the Link Toolbar. It
|
|
* associates the LINK with a corresponding LinkToolbarItem based
|
|
* on it's REL attribute and the toolbar item's ID attribute.
|
|
* LinkToolbarHandler is also a Factory and will create
|
|
* LinkToolbarItems as necessary.
|
|
*/
|
|
function LinkToolbarHandler()
|
|
{
|
|
this.items = new Array();
|
|
this.hasItems = false;
|
|
}
|
|
|
|
LinkToolbarHandler.prototype.handle =
|
|
function(element)
|
|
{
|
|
// XXX: if you're going to re-enable handling of anchor elements,
|
|
// you'll want to change this to AnchorElementDecorator
|
|
var linkElement = new LinkElementDecorator(element);
|
|
|
|
if (linkElement.isIgnored()) return;
|
|
|
|
for (var i = 0; i < linkElement.relValues.length; i++) {
|
|
if (linkElement.relValues.length > 1 && rel == "alternate")
|
|
continue; // skip "alternate" when we have "alternate XXX"
|
|
|
|
var linkType = LinkToolbarHandler.getLinkType(linkElement.relValues[i], element);
|
|
if (linkType) {
|
|
if (!this.hasItems) {
|
|
this.hasItems = true;
|
|
linkToolbarUI.activate();
|
|
}
|
|
this.getItemForLinkType(linkType).displayLink(linkElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
LinkToolbarHandler.getLinkType =
|
|
function(relAttribute, element)
|
|
{
|
|
var isFeed = false;
|
|
switch (relAttribute.toLowerCase()) {
|
|
case "start":
|
|
case "top":
|
|
case "origin":
|
|
return "top";
|
|
|
|
case "up":
|
|
case "parent":
|
|
return "up";
|
|
|
|
case "begin":
|
|
case "first":
|
|
return "first";
|
|
|
|
case "next":
|
|
case "child":
|
|
return "next";
|
|
|
|
case "prev":
|
|
case "previous":
|
|
return "prev";
|
|
|
|
case "end":
|
|
case "last":
|
|
return "last";
|
|
|
|
case "author":
|
|
case "made":
|
|
return "author";
|
|
|
|
case "contents":
|
|
case "toc":
|
|
return "toc";
|
|
|
|
case "feed":
|
|
isFeed = true;
|
|
// fall through
|
|
case "alternate":
|
|
var feed = { title: element.title, href: element.href,
|
|
type: element.type };
|
|
if (isValidFeed(feed, element.nodePrincipal, isFeed)) {
|
|
return "feed";
|
|
}
|
|
|
|
if (!isFeed) {
|
|
return "alternate";
|
|
}
|
|
// fall through
|
|
case "prefetch":
|
|
return null;
|
|
|
|
default:
|
|
return relAttribute.toLowerCase();
|
|
}
|
|
}
|
|
|
|
LinkToolbarHandler.prototype.getItemForLinkType =
|
|
function(linkType) {
|
|
if (!(linkType in this.items && this.items[linkType]))
|
|
this.items[linkType] = LinkToolbarHandler.createItemForLinkType(linkType);
|
|
|
|
return this.items[linkType];
|
|
}
|
|
|
|
LinkToolbarHandler.createItemForLinkType =
|
|
function(linkType)
|
|
{
|
|
if (!document.getElementById("link-" + linkType))
|
|
return new LinkToolbarTransientMenu(linkType);
|
|
|
|
// XXX: replace switch with polymorphism
|
|
var element = document.getElementById("link-" + linkType);
|
|
switch (element.getAttribute("type") || element.localName) {
|
|
case "toolbarbutton":
|
|
return new LinkToolbarButton(linkType);
|
|
|
|
case "menuitem":
|
|
return new LinkToolbarItem(linkType);
|
|
|
|
case "menu":
|
|
return new LinkToolbarMenu(linkType);
|
|
|
|
default:
|
|
return new LinkToolbarTransientMenu(linkType);
|
|
}
|
|
}
|
|
|
|
LinkToolbarHandler.prototype.clearAllItems =
|
|
function()
|
|
{
|
|
// Hide the 'miscellaneous' separator
|
|
document.getElementById("misc-separator").setAttribute("collapsed", "true");
|
|
|
|
// Disable the individual items
|
|
for (var linkType in this.items)
|
|
this.items[linkType].clear();
|
|
|
|
// Store the fact that the toolbar is empty
|
|
this.hasItems = false;
|
|
}
|
|
|
|
const linkToolbarHandler = new LinkToolbarHandler();
|
|
|
|
|
|
function LinkElementDecorator(element) {
|
|
/*
|
|
* XXX: this is an incomplete decorator, because it doesn't implement
|
|
* the full Element interface. If you need to use a method
|
|
* or member in the Element interface, just add it here and
|
|
* have it delegate to this.element
|
|
*
|
|
* XXX: would rather add some methods to Element.prototype instead of
|
|
* using a decorator, but Element.prototype is no longer exposed
|
|
* since the XPCDOM landing, see bug 83433
|
|
*/
|
|
|
|
if (!element) return; // skip the rest on foo.prototype = new ThisClass calls
|
|
|
|
this.element = element;
|
|
|
|
this.rel = LinkElementDecorator.convertRevMade(element.rel, element.rev);
|
|
if (this.rel)
|
|
this.relValues = this.rel.split(" ");
|
|
this.rev = element.rev;
|
|
this.title = element.title;
|
|
this.href = element.href;
|
|
this.hreflang = element.hreflang;
|
|
this.media = element.media;
|
|
this.longTitle = null;
|
|
}
|
|
|
|
LinkElementDecorator.prototype.isIgnored =
|
|
function()
|
|
{
|
|
if (!this.rel) return true;
|
|
for (var i = 0; i < this.relValues.length; i++)
|
|
if (/^stylesheet$|^icon$|^fontdef$|^p3pv|^schema./i.test(this.relValues[i]))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
LinkElementDecorator.convertRevMade =
|
|
function(rel, rev)
|
|
{
|
|
if (!rel && rev && /\bmade\b/i.test(rev))
|
|
return rev;
|
|
else
|
|
return rel;
|
|
}
|
|
|
|
LinkElementDecorator.prototype.getTooltip =
|
|
function()
|
|
{
|
|
return this.getLongTitle() != "" ? this.getLongTitle() : this.href;
|
|
}
|
|
|
|
LinkElementDecorator.prototype.getLabel =
|
|
function()
|
|
{
|
|
return this.getLongTitle() != "" ? this.getLongTitle() : this.rel;
|
|
}
|
|
|
|
LinkElementDecorator.prototype.getLongTitle =
|
|
function()
|
|
{
|
|
if (this.longTitle == null)
|
|
this.longTitle = this.makeLongTitle();
|
|
|
|
return this.longTitle;
|
|
}
|
|
|
|
LinkElementDecorator.prototype.makeLongTitle =
|
|
function()
|
|
{
|
|
var prefix = "";
|
|
|
|
// XXX: lookup more meaningful and localized version of media,
|
|
// i.e. media="print" becomes "Printable" or some such
|
|
// XXX: use localized version of ":" separator
|
|
if (this.media && !/\ball\b|\bscreen\b/i.test(this.media))
|
|
prefix += this.media + ": ";
|
|
if (this.hreflang)
|
|
prefix += languageDictionary.lookupLanguageName(this.hreflang)
|
|
+ ": ";
|
|
|
|
return this.title ? prefix + this.title : prefix;
|
|
}
|
|
|
|
function AnchorElementDecorator(element) {
|
|
this.constructor(element);
|
|
}
|
|
AnchorElementDecorator.prototype = new LinkElementDecorator;
|
|
|
|
AnchorElementDecorator.prototype.getLongTitle =
|
|
function()
|
|
{
|
|
return this.title ? this.__proto__.getLongTitle.apply(this)
|
|
: getText(this.element);
|
|
}
|
|
|
|
AnchorElementDecorator.prototype.getText =
|
|
function(element)
|
|
{
|
|
return condenseWhitespace(getTextRecursive(element));
|
|
}
|
|
|
|
AnchorElementDecorator.prototype.getTextRecursive =
|
|
function(node)
|
|
{
|
|
var text = "";
|
|
node.normalize();
|
|
if (node.hasChildNodes()) {
|
|
for (var i = 0; i < node.childNodes.length; i++) {
|
|
if (node.childNodes.item(i).nodeType == Node.TEXT_NODE)
|
|
text += node.childNodes.item(i).nodeValue;
|
|
else if (node.childNodes.item(i).nodeType == Node.ELEMENT_NODE)
|
|
text += getTextRecursive(node.childNodes.item(i));
|
|
}
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
AnchorElementDecorator.prototype.condenseWhitespace =
|
|
function(text)
|
|
{
|
|
return text.replace(/\W*$/, "").replace(/^\W*/, "").replace(/\W+/g, " ");
|
|
}
|