Creates a timeline from Zotero items using the Timeline tool from MIT's Simile project. You can create a timeline by clicking the Actions icon in Zotero and then clicking Create Timeline.
This commit is contained in:
parent
d8de14f047
commit
0f5bd0d1cf
|
@ -118,7 +118,8 @@
|
|||
<menupopup id="zotero-tb-actions-popup" onpopupshowing="document.getElementById('cmd_zotero_reportErrors').setAttribute('disabled', Zotero.getErrors().length == 0)">
|
||||
<menuitem id="zotero-tb-actions-import" label="&zotero.toolbar.import.label;" oncommand="Zotero_File_Interface.importFile();"/>
|
||||
<menuitem id="zotero-tb-actions-export" label="&zotero.toolbar.export.label;" oncommand="Zotero_File_Interface.exportFile();"/>
|
||||
<menuseparator id="zotero-tb-actions-utilities-separator" hidden="true"/>
|
||||
<menuseparator id="zotero-tb-actions-utilities-separator"/>
|
||||
<menuitem id="zotero-tb-actions-timeline" label="&zotero.toolbar.timeline.label;" oncommand="window.loadURI('zotero://timeline/')"/>
|
||||
<menuseparator id="zotero-tb-actions-separator"/>
|
||||
<menuitem id="zotero-tb-actions-prefs" label="&zotero.toolbar.preferences.label;"
|
||||
oncommand="window.openDialog('chrome://zotero/content/preferences/preferences.xul', 'zotero-prefs', 'chrome,titlebar,toolbar,' + Zotero.Prefs.get('browser.preferences.instantApply', true) ? 'dialog=no' : 'modal')"/>
|
||||
|
|
56
chrome/content/zotero/xpcom/timeline.js
Normal file
56
chrome/content/zotero/xpcom/timeline.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (c) 2006 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://chnm.gmu.edu
|
||||
|
||||
Licensed under the Educational Community License, Version 1.0 (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.opensource.org/licenses/ecl1.php
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
Based on nsChromeExtensionHandler example code by Ed Anuff at
|
||||
http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol
|
||||
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
|
||||
Zotero.Timeline = new function () {
|
||||
this.generateXMLDetails = generateXMLDetails;
|
||||
this.generateXMLList = generateXMLList;
|
||||
|
||||
function generateXMLDetails(items, dateType) {
|
||||
var ZU = new Zotero.Utilities();
|
||||
var escapeXML = ZU.htmlSpecialChars;
|
||||
|
||||
var content = '<data>\n';
|
||||
for each(var arr in items) {
|
||||
if (arr[dateType]) {
|
||||
var item = Zotero.Items.get(arr.itemID);
|
||||
var theDate =(dateType == 'date') ? Zotero.Date.multipartToSQL(arr[dateType]):arr[dateType];
|
||||
content += '<event start="' + Zotero.Date.sqlToDate(theDate) + '" ';
|
||||
content += 'title=" ' + escapeXML(arr.title) + '" ';
|
||||
content += 'icon="' + item.getImageSrc() + '" ';
|
||||
content += 'color="black">';
|
||||
content += 'zotero://select/item/'+arr.itemID;
|
||||
content += '</event>\n';
|
||||
}
|
||||
}
|
||||
content += '</data>';
|
||||
return content;
|
||||
}
|
||||
|
||||
function generateXMLList(items) {
|
||||
}
|
||||
}
|
36
chrome/locale/en-US/zotero/timeline.properties
Normal file
36
chrome/locale/en-US/zotero/timeline.properties
Normal file
|
@ -0,0 +1,36 @@
|
|||
general.title = Zotero Timeline
|
||||
general.filter = Filter:
|
||||
general.highlight = Highlight:
|
||||
general.clearAll = Clear All
|
||||
general.jumpToYear = Jump to Year:
|
||||
general.firstBand = First Band:
|
||||
general.secondBand = Second Band:
|
||||
general.thirdBand = Third Band:
|
||||
general.dateType = Date Type:
|
||||
general.timelineHeight = Timeline Height:
|
||||
general.go = Go...
|
||||
general.fitToScreen = Fit to Screen
|
||||
|
||||
interval.day = Day
|
||||
interval.month = Month
|
||||
interval.year = Year
|
||||
interval.decade = Decade
|
||||
interval.century = Century
|
||||
interval.millennium = Millennium
|
||||
|
||||
dateType.published = Date Published
|
||||
dateType.added = Date Added
|
||||
dateType.modified = Date Modified
|
||||
|
||||
shortName.january = Jan
|
||||
shortName.february = Feb
|
||||
shortName.march = Mar
|
||||
shortName.april = Apr
|
||||
shortName.may = May
|
||||
shortName.june = Jun
|
||||
shortName.july = Jul
|
||||
shortName.august = Aug
|
||||
shortName.september = Sep
|
||||
shortName.october = Oct
|
||||
shortName.november = Nov
|
||||
shortName.december = Dec
|
|
@ -62,6 +62,7 @@
|
|||
<!ENTITY zotero.toolbar.actions.label "Actions">
|
||||
<!ENTITY zotero.toolbar.import.label "Import...">
|
||||
<!ENTITY zotero.toolbar.export.label "Export Library...">
|
||||
<!ENTITY zotero.toolbar.timeline.label "Create Timeline">
|
||||
<!ENTITY zotero.toolbar.preferences.label "Preferences...">
|
||||
<!ENTITY zotero.toolbar.documentation.label "Documentation">
|
||||
<!ENTITY zotero.toolbar.about.label "About Zotero">
|
||||
|
|
172
chrome/skin/default/zotero/timeline/bundle.css
Normal file
172
chrome/skin/default/zotero/timeline/bundle.css
Normal file
|
@ -0,0 +1,172 @@
|
|||
.timeline-ether-marker-bottom {
|
||||
width: 5em;
|
||||
height: 1.5em;
|
||||
border-left: 1px solid #aaa;
|
||||
padding-left: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.timeline-ether-marker-bottom-emphasized {
|
||||
width: 5em;
|
||||
height: 2em;
|
||||
border-left: 1px solid #aaa;
|
||||
padding-left: 2px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.timeline-ether-marker-top {
|
||||
width: 5em;
|
||||
height: 1.5em;
|
||||
border-left: 1px solid #aaa;
|
||||
padding-left: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.timeline-ether-marker-top-emphasized {
|
||||
width: 5em;
|
||||
height: 2em;
|
||||
border-left: 1px solid #aaa;
|
||||
padding-left: 2px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
.timeline-ether-marker-right {
|
||||
width: 5em;
|
||||
height: 1.5em;
|
||||
border-top: 1px solid #aaa;
|
||||
padding-top: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.timeline-ether-marker-right-emphasized {
|
||||
width: 7em;
|
||||
height: 1.5em;
|
||||
border-top: 1px solid #aaa;
|
||||
padding-top: 2px;
|
||||
color: black;
|
||||
}
|
||||
.timeline-ether-marker-left {
|
||||
width: 5em;
|
||||
height: 1.5em;
|
||||
border-top: 1px solid #aaa;
|
||||
padding-top: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.timeline-ether-marker-left-emphasized {
|
||||
width: 7em;
|
||||
height: 1.5em;
|
||||
border-top: 1px solid #aaa;
|
||||
padding-top: 2px;
|
||||
color: black;
|
||||
}
|
||||
.timeline-duration-event {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
|
||||
.timeline-instant-event2 {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid blue;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.timeline-instant-event {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-event-bubble-title {
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #888;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.timeline-event-bubble-body {
|
||||
}
|
||||
|
||||
.timeline-event-bubble-wiki {
|
||||
margin: 0.5em;
|
||||
text-align: right;
|
||||
color: #A0A040;
|
||||
}
|
||||
.timeline-event-bubble-wiki a {
|
||||
color: #A0A040;
|
||||
}
|
||||
|
||||
.timeline-event-bubble-time {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.timeline-event-bubble-image {
|
||||
float: right;
|
||||
padding-left: 5px;
|
||||
padding-bottom: 5px;
|
||||
}.timeline-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-copyright {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.timeline-message-container {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 35%;
|
||||
right: 35%;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
}
|
||||
.timeline-message {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
.timeline-message img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.timeline-band {
|
||||
position: absolute;
|
||||
background: #eee;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.timeline-band-inner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.timeline-band-input {
|
||||
position: absolute;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
.timeline-band-input input{
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.timeline-band-layer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.timeline-band-layer-inner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
254
chrome/skin/default/zotero/timeline/bundle.js
Normal file
254
chrome/skin/default/zotero/timeline/bundle.js
Normal file
File diff suppressed because one or more lines are too long
230
chrome/skin/default/zotero/timeline/timeline-api.js
Normal file
230
chrome/skin/default/zotero/timeline/timeline-api.js
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*==================================================
|
||||
* Timeline API
|
||||
*
|
||||
* This file will load all the Javascript files
|
||||
* necessary to make the standard timeline work.
|
||||
* It also detects the default locale.
|
||||
*
|
||||
* Include this file in your HTML file as follows:
|
||||
*
|
||||
* <script src="http://simile.mit.edu/timeline/api/scripts/timeline-api.js" type="text/javascript"></script>
|
||||
*
|
||||
*==================================================
|
||||
*/
|
||||
|
||||
var Timeline = new Object();
|
||||
Timeline.Platform = new Object();
|
||||
|
||||
/*
|
||||
HACK: We need these 2 things here because we cannot simply append
|
||||
a <script> element containing code that accesses Timeline.Platform
|
||||
to initialize it because IE executes that <script> code first
|
||||
before it loads timeline.js and util/platform.js.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var bundle = true;
|
||||
var javascriptFiles = [
|
||||
"timeline.js",
|
||||
|
||||
"util/platform.js",
|
||||
"util/debug.js",
|
||||
"util/xmlhttp.js",
|
||||
"util/dom.js",
|
||||
"util/graphics.js",
|
||||
"util/date-time.js",
|
||||
"util/data-structure.js",
|
||||
"util/html.js",
|
||||
|
||||
"units.js",
|
||||
"themes.js",
|
||||
"ethers.js",
|
||||
"ether-painters.js",
|
||||
"labellers.js",
|
||||
"sources.js",
|
||||
"layouts.js",
|
||||
"painters.js",
|
||||
"decorators.js"
|
||||
];
|
||||
var cssFiles = [
|
||||
"timeline.css",
|
||||
"ethers.css",
|
||||
"events.css"
|
||||
];
|
||||
|
||||
// ISO-639 language codes, ISO-3166 country codes (2 characters)
|
||||
var supportedLocales = [
|
||||
"cs", // Czech
|
||||
"de", // German
|
||||
"en", // English
|
||||
"es", // Spanish
|
||||
"fr", // French
|
||||
"it", // Italian
|
||||
"ru", // Russian
|
||||
"se", // Swedish
|
||||
"vi", // Vietnamese
|
||||
"zh" // Chinese
|
||||
];
|
||||
|
||||
try {
|
||||
var desiredLocales = [ "en" ];
|
||||
var defaultServerLocale = "en";
|
||||
|
||||
var parseURLParameters = function(parameters) {
|
||||
var params = parameters.split("&");
|
||||
for (var p = 0; p < params.length; p++) {
|
||||
var pair = params[p].split("=");
|
||||
if (pair[0] == "locales") {
|
||||
desiredLocales = desiredLocales.concat(pair[1].split(","));
|
||||
} else if (pair[0] == "defaultLocale") {
|
||||
defaultServerLocale = pair[1];
|
||||
} else if (pair[0] == "bundle") {
|
||||
bundle = pair[1] != "false";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(function() {
|
||||
if (typeof Timeline_urlPrefix == "string") {
|
||||
Timeline.urlPrefix = Timeline_urlPrefix;
|
||||
if (typeof Timeline_parameters == "string") {
|
||||
parseURLParameters(Timeline_parameters);
|
||||
}
|
||||
} else {
|
||||
var heads = document.documentElement.getElementsByTagName("head");
|
||||
for (var h = 0; h < heads.length; h++) {
|
||||
var scripts = heads[h].getElementsByTagName("script");
|
||||
for (var s = 0; s < scripts.length; s++) {
|
||||
var url = scripts[s].src;
|
||||
var i = url.indexOf("timeline-api.js");
|
||||
if (i >= 0) {
|
||||
Timeline.urlPrefix = url.substr(0, i);
|
||||
var q = url.indexOf("?");
|
||||
if (q > 0) {
|
||||
parseURLParameters(url.substr(q + 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to derive URL prefix for Timeline API code files");
|
||||
}
|
||||
})();
|
||||
|
||||
var includeJavascriptFiles;
|
||||
var includeCssFiles;
|
||||
if ("SimileAjax" in window) {
|
||||
includeJavascriptFiles = function(urlPrefix, filenames) {
|
||||
SimileAjax.includeJavascriptFiles(document, urlPrefix, filenames);
|
||||
}
|
||||
includeCssFiles = function(urlPrefix, filenames) {
|
||||
SimileAjax.includeCssFiles(document, urlPrefix, filenames);
|
||||
}
|
||||
} else {
|
||||
var getHead = function() {
|
||||
return document.getElementsByTagName("head")[0];
|
||||
};
|
||||
var includeJavascriptFile = function(url) {
|
||||
if (document.body == null) {
|
||||
try {
|
||||
document.write("<script src='" + url + "' type='text/javascript'></script>");
|
||||
return;
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.language = "JavaScript";
|
||||
script.src = url;
|
||||
getHead().appendChild(script);
|
||||
};
|
||||
var includeCssFile = function(url) {
|
||||
if (document.body == null) {
|
||||
try {
|
||||
document.write("<link rel='stylesheet' href='" + url + "' type='text/css'/>");
|
||||
return;
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
var link = document.createElement("link");
|
||||
link.setAttribute("rel", "stylesheet");
|
||||
link.setAttribute("type", "text/css");
|
||||
link.setAttribute("href", url);
|
||||
getHead().appendChild(link);
|
||||
}
|
||||
|
||||
includeJavascriptFiles = function(urlPrefix, filenames) {
|
||||
for (var i = 0; i < filenames.length; i++) {
|
||||
includeJavascriptFile(urlPrefix + filenames[i]);
|
||||
}
|
||||
};
|
||||
includeCssFiles = function(urlPrefix, filenames) {
|
||||
for (var i = 0; i < filenames.length; i++) {
|
||||
includeCssFile(urlPrefix + filenames[i]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Include non-localized files
|
||||
*/
|
||||
if (bundle) {
|
||||
includeJavascriptFiles(Timeline.urlPrefix, [ "bundle.js" ]);
|
||||
includeCssFiles(Timeline.urlPrefix, [ "bundle.css" ]);
|
||||
} else {
|
||||
includeJavascriptFiles(Timeline.urlPrefix + "scripts/", javascriptFiles);
|
||||
includeCssFiles(Timeline.urlPrefix + "styles/", cssFiles);
|
||||
}
|
||||
|
||||
/*
|
||||
* Include localized files
|
||||
*/
|
||||
var loadLocale = [];
|
||||
loadLocale[defaultServerLocale] = true;
|
||||
|
||||
var tryExactLocale = function(locale) {
|
||||
for (var l = 0; l < supportedLocales.length; l++) {
|
||||
if (locale == supportedLocales[l]) {
|
||||
loadLocale[locale] = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var tryLocale = function(locale) {
|
||||
if (tryExactLocale(locale)) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
var dash = locale.indexOf("-");
|
||||
if (dash > 0 && tryExactLocale(locale.substr(0, dash))) {
|
||||
return locale.substr(0, dash);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var l = 0; l < desiredLocales.length; l++) {
|
||||
tryLocale(desiredLocales[l]);
|
||||
}
|
||||
|
||||
var defaultClientLocale = defaultServerLocale;
|
||||
var defaultClientLocales = ("language" in navigator ? navigator.language : navigator.browserLanguage).split(";");
|
||||
for (var l = 0; l < defaultClientLocales.length; l++) {
|
||||
var locale = tryLocale(defaultClientLocales[l]);
|
||||
if (locale != null) {
|
||||
defaultClientLocale = locale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Timeline.Platform.serverLocale = defaultServerLocale;
|
||||
Timeline.Platform.clientLocale = defaultClientLocale;
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
})();
|
88
chrome/skin/default/zotero/timeline/timeline.html
Normal file
88
chrome/skin/default/zotero/timeline/timeline.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
<!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<style type='text/css'>
|
||||
BODY {font-family: sans-serif;
|
||||
font-size: 14px;}
|
||||
|
||||
.timeline {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="chrome://zotero/skin/timeline/timelineControls.js" type="text/javascript"></script>
|
||||
<script src="chrome://zotero/skin/timeline/timeline-api.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var tl;
|
||||
function onLoad(a, b, c, date) {
|
||||
var eventSource = new Timeline.DefaultEventSource();
|
||||
|
||||
var theme = Timeline.ClassicTheme.create();
|
||||
theme.event.label.width = 175;
|
||||
date = createDate(date);
|
||||
var bandInfos = [
|
||||
Timeline.createBandInfo({
|
||||
eventSource: eventSource,
|
||||
date: date,
|
||||
width: "70%",
|
||||
intervalUnit: a,
|
||||
intervalPixels: 100,
|
||||
theme: theme
|
||||
}),
|
||||
Timeline.createBandInfo({
|
||||
showEventText: false,
|
||||
trackHeight: 0.5,
|
||||
trackGap: 0.2,
|
||||
eventSource: eventSource,
|
||||
date: date,
|
||||
width: "25%",
|
||||
intervalUnit: b,
|
||||
intervalPixels: 200,
|
||||
theme: theme
|
||||
}),
|
||||
Timeline.createBandInfo({
|
||||
showEventText: false,
|
||||
trackHeight: 0.5,
|
||||
trackGap: 0.2,
|
||||
eventSource: eventSource,
|
||||
date: date,
|
||||
width: "5%",
|
||||
intervalUnit: c,
|
||||
intervalPixels: 200,
|
||||
theme: theme
|
||||
})
|
||||
];
|
||||
bandInfos[1].syncWith = 0;
|
||||
bandInfos[1].highlight = true;
|
||||
bandInfos[1].eventPainter.setLayout(bandInfos[0].eventPainter.getLayout());
|
||||
bandInfos[2].syncWith = 0;
|
||||
bandInfos[2].highlight = true;
|
||||
|
||||
tl = Timeline.create(document.getElementById("my-timeline"), bandInfos);
|
||||
Timeline.loadXML("zotero://timeline/data/", function(xml, url) { eventSource.loadXML(xml, url); });
|
||||
|
||||
setupFilterHighlightControls(document.getElementById("my-timeline-controls"), tl, [0,1,2], theme);
|
||||
setupOtherControls(document.getElementById("my-other-controls"), tl, document.URL);
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
var curDate = tl.getBand(0).getCenterVisibleDate().toString();
|
||||
tl.layout();
|
||||
centerTimeline(curDate);
|
||||
}
|
||||
|
||||
Timeline.DurationEventPainter.prototype._showBubble = function(x, y, evt) {
|
||||
window.location=evt.getDescription();
|
||||
}
|
||||
|
||||
document.write("<title>"+localeHash["general.title"]+"</title>");
|
||||
</script>
|
||||
</head>
|
||||
<body onload="onLoad();" onresize="onResize()">
|
||||
<script type="text/javascript">document.write(getTimeline());</script>
|
||||
<div class="controls" id="my-timeline-controls"></div><br/>
|
||||
<div class="controls" id="my-other-controls"></div>
|
||||
</body>
|
||||
</html>
|
405
chrome/skin/default/zotero/timeline/timelineControls.js
Normal file
405
chrome/skin/default/zotero/timeline/timelineControls.js
Normal file
|
@ -0,0 +1,405 @@
|
|||
var localeHash = getLocaleHash();
|
||||
|
||||
function getLocaleHash() {
|
||||
var localeHash = new Object();
|
||||
var content = getContentsFromURL("chrome://zotero/locale/timeline.properties");
|
||||
var m;
|
||||
while(m = /^[\S]+(?=\s*=)/gm.exec(content)) {
|
||||
localeHash[m] = /=[^\n]+/g.exec(content).toString().replace(/=\s*/,'');;
|
||||
}
|
||||
return localeHash;
|
||||
}
|
||||
|
||||
function getContentsFromURL(url) {
|
||||
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance();
|
||||
xmlhttp.open('GET', url, false);
|
||||
xmlhttp.send(null);
|
||||
return xmlhttp.responseText;
|
||||
}
|
||||
|
||||
|
||||
function getTimeline() {
|
||||
var tt = getHeight();
|
||||
tt -= 180;
|
||||
if (tt < 100) {
|
||||
tt = 100;
|
||||
}
|
||||
return '<div class="timeline" id="my-timeline" style="height: ' + tt + 'px; border: 1px solid #aaa"></div>';
|
||||
}
|
||||
|
||||
function getHeight() {
|
||||
var temp = document.documentElement.clientHeight;
|
||||
if(temp && temp > 0) {
|
||||
return temp;
|
||||
}
|
||||
else {
|
||||
temp=document.body.clientHeight;
|
||||
if(temp && temp > 0) {
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function doReturn(e)
|
||||
{
|
||||
if(e.which) {
|
||||
if(e.which == '13'){
|
||||
checkDate(document.getElementById('jumpYear').value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getMonthNum(month) {
|
||||
var months = new Array('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec');
|
||||
var num = months.indexOf(month.toLowerCase());
|
||||
if(num<0){
|
||||
num = 0;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
function createDate(date){
|
||||
date = date.split('.');
|
||||
var theDate = new Date();
|
||||
theDate.setMonth(getMonthNum(date[0]));
|
||||
theDate.setDate(date[1]);
|
||||
theDate.setFullYear(date[2]);
|
||||
return theDate;
|
||||
}
|
||||
|
||||
function getTimelineDate(timeline){
|
||||
var timelineDate = timeline.getBand(0).getCenterVisibleDate().toString();
|
||||
var dateParts = timelineDate.split(' ');
|
||||
timelineDate = dateParts[1] + '.' + dateParts[2] + '.' + dateParts[3];
|
||||
return timelineDate;
|
||||
}
|
||||
|
||||
function checkDate(date) {
|
||||
var arr = new Array();
|
||||
var i = 0;
|
||||
date = date.replace(/[\s|.]/g,'');
|
||||
|
||||
//check to see if the year is B.C.
|
||||
var bc = false;
|
||||
if (date.substr(date.length - 2).toLowerCase() == 'bc') {
|
||||
bc = true;
|
||||
date = date.substr(0, date.length - 2);
|
||||
}
|
||||
|
||||
if(/\D/.test(date)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bc) {
|
||||
centerTimeline(date + ' BC');
|
||||
}
|
||||
else {
|
||||
centerTimeline(date);
|
||||
}
|
||||
}
|
||||
function changeBand(band, intervals, date, url, selectedIndex) {
|
||||
var values = new Array('d', 'm', 'y', 'e', 'c', 'i');
|
||||
var temp = url.split('/');
|
||||
|
||||
var newIntervals = '';
|
||||
for (var i = 0; i < intervals.length; i++) {
|
||||
if (i == band) {
|
||||
newIntervals += values[selectedIndex];
|
||||
}
|
||||
else {
|
||||
newIntervals += intervals[i];
|
||||
}
|
||||
}
|
||||
temp[3] = newIntervals;
|
||||
temp[4] = date;
|
||||
window.location = temp.join('/');
|
||||
}
|
||||
|
||||
function changeDateType(url, intervals, values, date, seletedIndex) {
|
||||
var temp = url.split('/');
|
||||
temp[3] = intervals;
|
||||
temp[4] = date;
|
||||
temp[5] = values[seletedIndex];
|
||||
window.location = temp.join('/');
|
||||
}
|
||||
|
||||
function createOption(t, selected) {
|
||||
option = document.createElement("option");
|
||||
if (selected) {
|
||||
option.selected = "true";
|
||||
}
|
||||
text = document.createTextNode(t);
|
||||
option.setAttribute("value", t);
|
||||
option.appendChild(text);
|
||||
return option;
|
||||
}
|
||||
|
||||
function getFull(a) {
|
||||
switch (a) {
|
||||
case 'd':
|
||||
return localeHash["interval.day"];
|
||||
case 'm':
|
||||
return localeHash["interval.month"];
|
||||
case 'y':
|
||||
return localeHash["interval.year"];
|
||||
case 'e':
|
||||
return localeHash["interval.decade"];
|
||||
case 'c':
|
||||
return localeHash["interval.century"];
|
||||
case 'i':
|
||||
return localeHash["interval.millennium"];
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function setupOtherControls(div, timeline, url) {
|
||||
var table = document.createElement("table");
|
||||
|
||||
// url= zotero://timeline/intervals/timelineDate/dateType/type/ids/
|
||||
var parts = url.split('/');
|
||||
var intervals = parts[3];
|
||||
if (intervals.length < 3) {
|
||||
intervals += "mye".substr(intervals.length);
|
||||
}
|
||||
var tr = table.insertRow(0);
|
||||
|
||||
var td = tr.insertCell(0);
|
||||
td.innerHTML = localeHash["general.jumpToYear"];
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
td.innerHTML = localeHash["general.firstBand"];
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
td.innerHTML = localeHash["general.secondBand"];;
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
td.innerHTML = localeHash["general.thirdBand"];;
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
td.innerHTML = localeHash["general.dateType"];;
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
td.innerHTML = localeHash["general.timelineHeight"];;
|
||||
|
||||
tr = table.insertRow(1);
|
||||
tr.style.verticalAlign = "top";
|
||||
|
||||
td = tr.insertCell(0);
|
||||
var input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.size = "15";
|
||||
input.id="jumpYear";
|
||||
input.onkeypress=doReturn;
|
||||
td.appendChild(input);
|
||||
|
||||
var options = new Array(localeHash["interval.day"], localeHash["interval.month"], localeHash["interval.year"],
|
||||
localeHash["interval.decade"], localeHash["interval.century"], localeHash["interval.millennium"]);
|
||||
var selected = '';
|
||||
var theSelects = new Array();
|
||||
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
|
||||
var select1 = document.createElement("select");
|
||||
selected = getFull(intervals[0]);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
select1.appendChild(createOption(options[i],(options[i] == selected)));
|
||||
}
|
||||
select1.onchange = function () {
|
||||
changeBand(0, intervals, getTimelineDate(timeline), url, table.rows[1].cells[1].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select1);
|
||||
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
|
||||
var select2 = document.createElement("select");
|
||||
selected = getFull(intervals[1]);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
select2.appendChild(createOption(options[i],(options[i] == selected)));
|
||||
}
|
||||
select2.onchange = function () {
|
||||
changeBand(1, intervals, getTimelineDate(timeline), url, table.rows[1].cells[2].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select2);
|
||||
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
|
||||
var select3 = document.createElement("select");
|
||||
selected = getFull(intervals[2]);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
select3.appendChild(createOption(options[i],(options[i] == selected)));
|
||||
}
|
||||
select3.onchange = function () {
|
||||
changeBand(2, intervals, getTimelineDate(timeline), url, table.rows[1].cells[3].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select3);
|
||||
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
options = new Array(localeHash["dateType.published"], localeHash["dateType.added"], localeHash["dateType.modified"]);
|
||||
var values = new Array('date', 'dateAdded', 'dateModified');
|
||||
var select4 = document.createElement("select");
|
||||
selected = 0;
|
||||
if (parts[5]) {
|
||||
selected = values.indexOf(parts[5]);
|
||||
}
|
||||
if (selected < 0) {
|
||||
selected = 0;
|
||||
}
|
||||
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
select4.appendChild(createOption(options[i],(i == selected)));
|
||||
}
|
||||
select4.onchange = function () {
|
||||
changeDateType(url, intervals, values, getTimelineDate(timeline), table.rows[1].cells[4].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select4);
|
||||
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
var fitToScreen = document.createElement("button");
|
||||
fitToScreen.innerHTML = localeHash["general.fitToScreen"];
|
||||
Timeline.DOM.registerEvent(fitToScreen, "click", function () {
|
||||
var temp = url.split('/');
|
||||
temp[3] = intervals;
|
||||
temp[4] = getTimelineDate(timeline);
|
||||
window.location = temp.join('/');
|
||||
});
|
||||
td.appendChild(fitToScreen);
|
||||
|
||||
tr = table.insertRow(2);
|
||||
td = tr.insertCell(0);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = localeHash["general.go"];
|
||||
Timeline.DOM.registerEvent(button, "click", function () {
|
||||
checkDate(table.rows[1].cells[0].firstChild.value);
|
||||
});
|
||||
td.appendChild(button);
|
||||
|
||||
div.appendChild(table);
|
||||
}
|
||||
|
||||
/*
|
||||
Everything below is from http://simile.mit.edu/timeline/examples/examples.js
|
||||
*/
|
||||
|
||||
function centerTimeline(date) {
|
||||
tl.getBand(0).setCenterVisibleDate(Timeline.DateTime.parseGregorianDateTime(date));
|
||||
}
|
||||
|
||||
function setupFilterHighlightControls(div, timeline, bandIndices, theme) {
|
||||
var table = document.createElement("table");
|
||||
var tr = table.insertRow(0);
|
||||
|
||||
var td = tr.insertCell(0);
|
||||
td.innerHTML = localeHash["general.filter"];
|
||||
|
||||
td = tr.insertCell(1);
|
||||
td.innerHTML = localeHash["general.highlight"];
|
||||
|
||||
var handler = function (elmt, evt, target) {
|
||||
onKeyPress(timeline, bandIndices, table);
|
||||
};
|
||||
|
||||
tr = table.insertRow(1);
|
||||
tr.style.verticalAlign = "top";
|
||||
|
||||
td = tr.insertCell(0);
|
||||
|
||||
input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.size = "18";
|
||||
Timeline.DOM.registerEvent(input, "keypress", handler);
|
||||
td.appendChild(input);
|
||||
|
||||
for (var i = 0; i < theme.event.highlightColors.length; i++) {
|
||||
td = tr.insertCell(i + 1);
|
||||
|
||||
input = document.createElement("input");
|
||||
input.type = "text";
|
||||
Timeline.DOM.registerEvent(input, "keypress", handler);
|
||||
td.appendChild(input);
|
||||
input.size = "15";
|
||||
var divColor = document.createElement("div");
|
||||
divColor.style.height = "0.5em";
|
||||
divColor.style.background = theme.event.highlightColors[i];
|
||||
td.appendChild(divColor);
|
||||
}
|
||||
|
||||
td = tr.insertCell(tr.cells.length);
|
||||
button = document.createElement("button");
|
||||
button.innerHTML = localeHash["general.clearAll"];
|
||||
Timeline.DOM.registerEvent(button, "click", function () {
|
||||
clearAll(timeline, bandIndices, table);
|
||||
});
|
||||
td.appendChild(button);
|
||||
|
||||
div.appendChild(table);
|
||||
}
|
||||
|
||||
var timerID = null;
|
||||
function onKeyPress(timeline, bandIndices, table) {
|
||||
if (timerID != null) {
|
||||
window.clearTimeout(timerID);
|
||||
}
|
||||
timerID = window.setTimeout(function () {
|
||||
performFiltering(timeline, bandIndices, table);
|
||||
}, 300);
|
||||
}
|
||||
function cleanString(s) {
|
||||
return s.replace(/^\s + /, '').replace(/\s + $/, '');
|
||||
}
|
||||
function performFiltering(timeline, bandIndices, table) {
|
||||
timerID = null;
|
||||
|
||||
var tr = table.rows[1];
|
||||
var text = cleanString(tr.cells[0].firstChild.value);
|
||||
|
||||
var filterMatcher = null;
|
||||
if (text.length > 0) {
|
||||
var regex = new RegExp(text, "i");
|
||||
filterMatcher = function (evt) {
|
||||
return regex.test(evt.getText()) || regex.test(evt.getDescription());
|
||||
};
|
||||
}
|
||||
|
||||
var regexes = [];
|
||||
var hasHighlights = false;
|
||||
for (var x = 1; x < tr.cells.length - 1; x++) {
|
||||
var input = tr.cells[x].firstChild;
|
||||
var text2 = cleanString(input.value);
|
||||
if (text2.length > 0) {
|
||||
hasHighlights = true;
|
||||
regexes.push(new RegExp(text2, "i"));
|
||||
} else {
|
||||
regexes.push(null);
|
||||
}
|
||||
}
|
||||
var highlightMatcher = hasHighlights ? function (evt) {
|
||||
var text = evt.getText();
|
||||
var description = evt.getDescription();
|
||||
for (var x = 0; x < regexes.length; x++) {
|
||||
var regex = regexes[x];
|
||||
if (regex != null && (regex.test(text) || regex.test(description))) {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
return - 1;
|
||||
} : null;
|
||||
|
||||
for (var i = 0; i < bandIndices.length; i++) {
|
||||
var bandIndex = bandIndices[i];
|
||||
timeline.getBand(bandIndex).getEventPainter().setFilterMatcher(filterMatcher);
|
||||
timeline.getBand(bandIndex).getEventPainter().setHighlightMatcher(highlightMatcher);
|
||||
}
|
||||
timeline.paint();
|
||||
}
|
||||
function clearAll(timeline, bandIndices, table) {
|
||||
var tr = table.rows[1];
|
||||
for (var x = 0; x < tr.cells.length - 1; x++) {
|
||||
tr.cells[x].firstChild.value = "";
|
||||
}
|
||||
|
||||
for (var i = 0; i < bandIndices.length; i++) {
|
||||
var bandIndex = bandIndices[i];
|
||||
timeline.getBand(bandIndex).getEventPainter().setFilterMatcher(null);
|
||||
timeline.getBand(bandIndex).getEventPainter().setHighlightMatcher(null);
|
||||
}
|
||||
timeline.paint();
|
||||
}
|
|
@ -222,11 +222,206 @@ function ChromeExtensionHandler() {
|
|||
return extChannel;
|
||||
}
|
||||
};
|
||||
|
||||
var TimelineExtension = new function(){
|
||||
this.newChannel = newChannel;
|
||||
|
||||
function getInterval(a){
|
||||
switch (a){
|
||||
case 'd':
|
||||
return 'Timeline.DateTime.DAY';
|
||||
case 'm':
|
||||
return 'Timeline.DateTime.MONTH';
|
||||
case 'y':
|
||||
return 'Timeline.DateTime.YEAR';
|
||||
case 'e':
|
||||
return 'Timeline.DateTime.DECADE';
|
||||
case 'c':
|
||||
return 'Timeline.DateTime.CENTURY';
|
||||
case 'i':
|
||||
return 'Timeline.DateTime.MILLENNIUM';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
zotero://timeline/intervals/timelineDate/dateType/type/ids/ ----->creates html for timeline
|
||||
zotero://timeline/ ------> minimum needed (defaults: intervals = month, year, decade |
|
||||
timelineDate = today's date | dateType = date | type = library)
|
||||
zotero://timeline/data/dateType/type/ids ----->creates XML
|
||||
*/
|
||||
function newChannel(uri) {
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
|
||||
var Zotero = Components.classes["@zotero.org/Zotero;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
|
||||
generateContent:try {
|
||||
var mimeType, content = '';
|
||||
|
||||
var [path, queryString] = uri.path.substr(1).split('?');
|
||||
var pathParts = path.split('/');
|
||||
|
||||
if (pathParts[0] != 'data') {
|
||||
//creates HTML file
|
||||
var intervals = pathParts[0];
|
||||
var timelineDate = pathParts[1];
|
||||
content = Zotero.File.getContentsFromURL('chrome://zotero/skin/timeline/timeline.html');
|
||||
mimeType = 'text/html';
|
||||
|
||||
var theTemp = 'Timeline.loadXML("zotero://timeline/data/';
|
||||
|
||||
if(!timelineDate){
|
||||
timelineDate=Date();
|
||||
var dateParts=timelineDate.toString().split(' ');
|
||||
timelineDate=dateParts[1]+'.'+dateParts[2]+'.'+dateParts[3];
|
||||
}
|
||||
else {
|
||||
//passes information (dateType,type,ids) for when the XML is created
|
||||
content = content.replace(theTemp, theTemp + pathParts.slice(2).join('/'));
|
||||
}
|
||||
|
||||
//sets the intervals of the timeline bands
|
||||
theTemp = '<body onload="onLoad(';
|
||||
var a = (getInterval(intervals[0])) ? getInterval(intervals[0]) : 'Timeline.DateTime.MONTH';
|
||||
var b = (getInterval(intervals[1])) ? getInterval(intervals[1]) : 'Timeline.DateTime.YEAR';
|
||||
var c = (getInterval(intervals[2])) ? getInterval(intervals[2]) : 'Timeline.DateTime.DECADE';
|
||||
content = content.replace(theTemp, theTemp + a + ',' + b + ',' + c + ',\'' + timelineDate + '\'');
|
||||
|
||||
var uri_str = 'data:' + (mimeType ? mimeType + ',' : '') + encodeURIComponent(content);
|
||||
var ext_uri = ioService.newURI(uri_str, null, null);
|
||||
var extChannel = ioService.newChannelFromURI(ext_uri);
|
||||
|
||||
return extChannel;
|
||||
}
|
||||
else {
|
||||
var [data, dateType, type, ids] = pathParts;
|
||||
}
|
||||
//creates XML file
|
||||
switch (type){
|
||||
case 'collection':
|
||||
var results = Zotero.getItems(ids);
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
var s = new Zotero.Search(ids);
|
||||
var ids = s.search();
|
||||
break;
|
||||
|
||||
case 'items':
|
||||
case 'item':
|
||||
var ids = ids.split('-');
|
||||
break;
|
||||
|
||||
default:
|
||||
var type = 'library';
|
||||
var s = new Zotero.Search();
|
||||
s.addCondition('noChildren', 'true');
|
||||
var ids = s.search();
|
||||
}
|
||||
|
||||
if (!results) {
|
||||
var results = Zotero.Items.get(ids);
|
||||
}
|
||||
var items = [];
|
||||
// Only include parent items
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
if (!results[i].getSource()) {
|
||||
items.push(results[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!items) {
|
||||
mimeType = 'text/html';
|
||||
content = 'Invalid ID';
|
||||
break generateContent;
|
||||
}
|
||||
|
||||
// Convert item objects to export arrays
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
items[i] = items[i].toArray();
|
||||
}
|
||||
|
||||
mimeType = 'application/xml';
|
||||
|
||||
//default dateType = date (publication date)
|
||||
if (!dateType) {
|
||||
dateType = 'date';
|
||||
}
|
||||
content = Zotero.Timeline.generateXMLDetails(items, dateType);
|
||||
|
||||
}
|
||||
catch (e){
|
||||
Zotero.debug(e);
|
||||
throw (e);
|
||||
}
|
||||
|
||||
var uri_str = 'data:' + (mimeType ? mimeType + ',' : '') + encodeURIComponent(content);
|
||||
var ext_uri = ioService.newURI(uri_str, null, null);
|
||||
var extChannel = ioService.newChannelFromURI(ext_uri);
|
||||
|
||||
return extChannel;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
zotero://select/type/id
|
||||
*/
|
||||
|
||||
var SelectExtension = new function(){
|
||||
this.newChannel = newChannel;
|
||||
|
||||
function newChannel(uri) {
|
||||
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
|
||||
var Zotero = Components.classes["@zotero.org/Zotero;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
|
||||
generateContent:try {
|
||||
var mimeType, content = '';
|
||||
|
||||
var [path, queryString] = uri.path.substr(1).split('?');
|
||||
var [type, id] = path.split('/');
|
||||
|
||||
//currently only able to select one item
|
||||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Components.interfaces.nsIWindowMediator);
|
||||
var win = wm.getMostRecentWindow(null);
|
||||
|
||||
if(!win.ZoteroPane.isShowing()){
|
||||
win.ZoteroPane.toggleDisplay();
|
||||
}
|
||||
|
||||
win.ZoteroPane.selectItem(id);
|
||||
}
|
||||
catch (e){
|
||||
Zotero.debug(e);
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var ReportExtensionSpec = ZOTERO_SCHEME + "://report"
|
||||
ReportExtensionSpec = ReportExtensionSpec.toLowerCase();
|
||||
|
||||
this._extensions[ReportExtensionSpec] = ReportExtension;
|
||||
|
||||
var TimelineExtensionSpec = ZOTERO_SCHEME + "://timeline"
|
||||
TimelineExtensionSpec = TimelineExtensionSpec.toLowerCase();
|
||||
|
||||
this._extensions[TimelineExtensionSpec] = TimelineExtension;
|
||||
|
||||
var SelectExtensionSpec = ZOTERO_SCHEME + "://select"
|
||||
SelectExtensionSpec = SelectExtensionSpec.toLowerCase();
|
||||
|
||||
this._extensions[SelectExtensionSpec] = SelectExtension;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -69,6 +69,10 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
|||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/report.js");
|
||||
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://zotero/content/xpcom/timeline.js");
|
||||
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
|
|
Loading…
Reference in New Issue
Block a user