diff --git a/chrome/content/scaffold/load.js b/chrome/content/scaffold/load.js
index 6308178689..62067c1455 100644
--- a/chrome/content/scaffold/load.js
+++ b/chrome/content/scaffold/load.js
@@ -47,6 +47,10 @@ var Scaffold_Load = new function() {
.sort((a, b) => a.label.localeCompare(b.label));
translators["Import Translators"] = (yield translatorProvider.getAllForType("import"))
.sort((a, b) => a.label.localeCompare(b.label));
+ translators["Export Translators"] = (yield translatorProvider.getAllForType("export"))
+ .sort((a, b) => a.label.localeCompare(b.label));
+ translators["Search Translators"] = (yield translatorProvider.getAllForType("search"))
+ .sort((a, b) => a.label.localeCompare(b.label));
for (set in translators) {
// Make a separator
diff --git a/chrome/content/scaffold/monaco/monaco.html b/chrome/content/scaffold/monaco/monaco.html
new file mode 100644
index 0000000000..22ccab4a1f
--- /dev/null
+++ b/chrome/content/scaffold/monaco/monaco.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+ Monaco
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chrome/content/scaffold/scaffold.js b/chrome/content/scaffold/scaffold.js
index 05ae6734b6..3d33a06ea1 100644
--- a/chrome/content/scaffold/scaffold.js
+++ b/chrome/content/scaffold/scaffold.js
@@ -52,37 +52,46 @@ function fix2028(str) {
return str;
}
-var Scaffold = new function() {
+var Scaffold = new function () {
var _browser, _frames, _document;
var _translatorsLoadedPromise;
- var _translatorProvider = null
+ var _translatorProvider = null;
+ var _lastModifiedTime = 0;
+ var _lastHadFocus = true;
var _editors = {};
var _propertyMap = {
- 'textbox-translatorID':'translatorID',
- 'textbox-label':'label',
- 'textbox-creator':'creator',
- 'textbox-target':'target',
- 'textbox-minVersion':'minVersion',
- 'textbox-maxVersion':'maxVersion',
- 'textbox-priority':'priority',
- 'textbox-target-all':'targetAll',
- 'textbox-hidden-prefs':'hiddenPrefs'
+ 'textbox-translatorID': 'translatorID',
+ 'textbox-label': 'label',
+ 'textbox-creator': 'creator',
+ 'textbox-target': 'target',
+ 'textbox-minVersion': 'minVersion',
+ 'textbox-priority': 'priority',
+ 'textbox-target-all': 'targetAll',
+ 'textbox-hidden-prefs': 'hiddenPrefs'
};
+ var _linesOfMetadata = 15;
+
this.onLoad = async function (e) {
- if(e.target !== document) return;
+ if (e.target !== document) return;
_document = document;
+
+ if (Zotero.isWin) {
+ // Hack to fix Windows toolbar
+ let toolbar = document.getElementById('zotero-toolbar');
+ toolbar.className = '';
+ }
- _browser = document.getElementsByTagName('browser')[0];
+ _browser = document.getElementById('browser');
_browser.addEventListener("pageshow",
_updateFrames, true);
_updateFrames();
let browserUrl = document.getElementById("browser-url");
- browserUrl.addEventListener('keypress', function(e) {
+ browserUrl.addEventListener('keypress', function (e) {
if (e.keyCode == e.DOM_VK_RETURN) {
_browser.loadURIWithFlags(
browserUrl.value,
@@ -90,31 +99,7 @@ var Scaffold = new function() {
);
}
});
-
- var importWin = document.getElementById("editor-import").contentWindow;
- var codeWin = document.getElementById("editor-code").contentWindow;
- var testsWin = document.getElementById("editor-tests").contentWindow;
-
- _editors.import = importWin.editor;
- _editors.code = codeWin.editor;
- _editors.tests = testsWin.editor;
-
- for (let i in _editors) {
- _editors[i].setTheme('ace/theme/monokai');
- }
-
- _editors.code.getSession().setMode(new codeWin.JavaScriptMode);
- _editors.code.getSession().setUseSoftTabs(false);
- // The first code line is preceeded by some metadata lines, such that
- // the code lines start (usually) at line 15.
- _editors.code.getSession().setOption("firstLineNumber", 15);
-
- _editors.tests.getSession().setUseWorker(false);
- _editors.tests.getSession().setMode(new testsWin.JavaScriptMode);
- _editors.tests.getSession().setUseSoftTabs(false);
-
- _editors.import.getSession().setMode(new importWin.TextMode);
-
+
// Set font size from general pref
Zotero.setFontSize(document.getElementById('scaffold-pane'));
@@ -123,18 +108,7 @@ var Scaffold = new function() {
if (size) {
this.setFontSize(size);
}
-
- // Set resize handler
- _document.addEventListener("resize", this.onResize, false);
- // Disable editing if external editor is enabled, enable when it is disabled
- document.getElementById('checkbox-editor-external').addEventListener("command",
- function() {
- var external = document.getElementById('checkbox-editor-external').checked;
- _editors.code.setReadOnly(external);
- _editors.tests.setReadOnly(external);
- }, true);
-
this.generateTranslatorID();
// Add List fields help menu entries for all other item types
@@ -145,7 +119,9 @@ var Scaffold = new function() {
if (primaryTypes.includes(type)) continue;
var menuitem = document.createElement('menuitem');
menuitem.setAttribute('label', type);
- menuitem.addEventListener('command', () => { Scaffold.addTemplate('templateNewItem', type) });
+ menuitem.addEventListener('command', () => {
+ Scaffold.addTemplate('templateNewItem', type);
+ });
morePopup.appendChild(menuitem);
}
@@ -155,6 +131,48 @@ var Scaffold = new function() {
return;
}
}
+
+ var importWin = document.getElementById("editor-import").contentWindow;
+ var codeWin = document.getElementById("editor-code").contentWindow;
+ var testsWin = document.getElementById("editor-tests").contentWindow;
+
+ _editors.import = importWin.editor;
+ _editors.importGlobal = importWin.globalEditor;
+ _editors.code = codeWin.editor;
+ _editors.codeGlobal = codeWin.globalEditor;
+ _editors.tests = testsWin.editor;
+ _editors.testsGlobal = testsWin.globalEditor;
+
+ this.initImportEditor();
+ this.initCodeEditor();
+ this.initTestsEditor();
+
+ // Listen for Scaffold coming to the foreground and reload translators.
+ // We can't just set a focus listener on the because it'll fire
+ // when focus switches between the root window and any of the iframes.
+ setInterval(() => {
+ let hasFocus = document.hasFocus();
+ if (_lastHadFocus == hasFocus) {
+ return;
+ }
+
+ if (hasFocus) {
+ this.reloadTranslators();
+ }
+
+ _lastHadFocus = hasFocus;
+ }, 1000);
+
+
+ Scaffold_Translators.setLoadListener({
+ onLoadBegin: () => {
+ document.getElementById('cmd_load').setAttribute('disabled', true);
+ },
+
+ onLoadComplete: () => {
+ document.getElementById('cmd_load').removeAttribute('disabled');
+ }
+ });
_translatorsLoadedPromise = Scaffold_Translators.load();
_translatorProvider = Scaffold_Translators.getProvider();
@@ -210,89 +228,410 @@ var Scaffold = new function() {
Scaffold_Translators.load(true); // async
return path;
};
-
- this.onResize = function() {
- // We try to let ACE resize itself
- _editors.import.resize();
- _editors.code.resize();
- _editors.tests.resize();
- return true;
- }
+ this.reloadTranslators = async function () {
+ Zotero.debug('Reloading translators quietly');
+ let { numLoaded, numDeleted } = await Scaffold_Translators.load(true);
+ if (numLoaded) {
+ _logOutput(`${numLoaded} ${Zotero.Utilities.pluralize(numLoaded, 'translator')} updated.`);
+ }
+ if (numDeleted) {
+ _logOutput(`${numDeleted} ${Zotero.Utilities.pluralize(numDeleted, 'translator')} deleted.`);
+ }
- this.setFontSize = function(size) {
+ let translatorID = document.getElementById('textbox-translatorID').value;
+ let modifiedTime = Scaffold_Translators.getModifiedTime(translatorID);
+ if (modifiedTime && modifiedTime > _lastModifiedTime) {
+ let ps = Services.prompt;
+ let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
+ var index = ps.confirmEx(null,
+ "Scaffold",
+ "Translator code changed externally. Discard unsaved changes and reload?",
+ buttonFlags,
+ Zotero.getString('general.no'),
+ Zotero.getString('general.yes'),
+ null, null, {}
+ );
+ if (index == 1) {
+ this.load(translatorID);
+ }
+ else {
+ _lastModifiedTime = modifiedTime;
+ }
+ }
+ };
+
+ this.initImportEditor = function () {
+ let monaco = _editors.importGlobal, editor = _editors.import;
+ monaco.editor.setModelLanguage(editor.getModel(), 'plaintext');
+ };
+
+ this.initCodeEditor = async function () {
+ let monaco = _editors.codeGlobal, editor = _editors.code;
+
+ editor.getModel().updateOptions({
+ insertSpaces: false
+ });
+
+ editor.updateOptions({
+ lineNumbers: num => num + _linesOfMetadata - 1,
+ // clicking links doesn't actually work, so disable them (for now)
+ links: false
+ });
+
+ monaco.languages.registerCodeLensProvider('javascript', this.createRunCodeLensProvider(monaco, editor));
+ monaco.languages.registerHoverProvider('javascript', this.createHoverProvider(monaco, editor));
+
+ let tsLib = await Zotero.File.getContentsAsync(
+ OS.Path.join(Scaffold_Translators.getDirectory(), 'index.d.ts'));
+ let tsLibPath = 'ts:filename/index.d.ts';
+ monaco.languages.typescript.javascriptDefaults.addExtraLib(tsLib, tsLibPath);
+ // this would allow peeking:
+ // monaco.editor.createModel(tsLib, 'typescript', monaco.Uri.parse(tsLibPath));
+ // but it doesn't currently seem to work
+ };
+
+ this.initTestsEditor = function () {
+ let monaco = _editors.testsGlobal, editor = _editors.tests;
+
+ monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
+ validate: true,
+ allowComments: false,
+ trailingCommas: false,
+ schemaValidation: 'error'
+ });
+
+ monaco.editor.setModelLanguage(editor.getModel(), 'json');
+
+ editor.getModel().updateOptions({
+ insertSpaces: false
+ });
+
+ editor.getModel().onDidChangeContent((_) => {
+ this.populateTests();
+ });
+
+ editor.updateOptions({
+ links: false
+ });
+
+ monaco.languages.registerCodeLensProvider('json', this.createTestCodeLensProvider(monaco, editor));
+ };
+
+ this.createRunCodeLensProvider = function (monaco, editor) {
+ let runMethod = editor.addCommand(0, (_ctx, method) => this.run(method), '');
+
+ return {
+ provideCodeLenses: (model, _token) => {
+ let methodRe = '(async\\s+)?\\bfunction\\s+(detect|do)(Web|Import|Export|Search)\\s*\\(';
+ let lenses = [];
+
+ let matches = model.findMatches(
+ methodRe,
+ /* searchOnlyEditableRange: */ false,
+ /* isRegex: */ true,
+ /* matchCase: */ true,
+ /* wordSeparators: */ null,
+ /* captureMatches: */ true
+ );
+
+ for (let match of matches) {
+ let line = match.matches[0];
+ let methodName = line.match(/function\s+(\w*)/)[1];
+ lenses.push({
+ range: match.range,
+ command: {
+ id: runMethod,
+ title: `Run ${methodName}`,
+ arguments: [methodName]
+ }
+ });
+ }
+
+ return lenses;
+ },
+ resolveCodeLens: (_model, codeLens, _token) => codeLens
+ };
+ };
+
+ this.createHoverProvider = function (monaco, _editor) {
+ let uuidRe = `(["'])([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\1`;
+ let types = Zotero.ItemTypes.getTypes().map(t => t.name);
+ let itemTypeRe = `(["'])(${types.join('|')})\\1`;
+
+ return {
+ provideHover: (model, position) => {
+ let lineRange = new monaco.Range(
+ position.lineNumber,
+ model.getLineMinColumn(position.lineNumber),
+ position.lineNumber,
+ model.getLineMaxColumn(position.lineNumber)
+ );
+
+ let matches = model.findMatches(
+ uuidRe,
+ /* searchScope: */ lineRange,
+ /* isRegex: */ true,
+ /* matchCase: */ true,
+ /* wordSeparators: */ null,
+ /* captureMatches: */ true
+ );
+
+ for (let uuidMatch of matches) {
+ if (!uuidMatch.range.containsPosition(position)) continue;
+ let translator = _translatorProvider.get(uuidMatch.matches[2]);
+
+ if (translator) {
+ let metadataJSON = JSON.stringify(translator.metadata, null, '\t');
+
+ return {
+ range: uuidMatch.range,
+ contents: [
+ { value: `**${translator.label}**` },
+ { value: '```json\n' + metadataJSON + '\n```' }
+ ]
+ };
+ }
+ }
+
+ matches = model.findMatches(
+ itemTypeRe,
+ /* searchScope: */ lineRange,
+ /* isRegex: */ true,
+ /* matchCase: */ true,
+ /* wordSeparators: */ null,
+ /* captureMatches: */ true
+ );
+
+ for (let itemTypeMatch of matches) {
+ if (!itemTypeMatch.range.containsPosition(position)) continue;
+
+ let fieldsJSON = this.listFieldsForItemType(itemTypeMatch.matches[2]);
+ return {
+ range: itemTypeMatch.range,
+ contents: [
+ { value: '```json\n' + fieldsJSON + '\n```' }
+ ]
+ };
+ }
+
+ return null;
+ }
+ };
+ };
+
+ this.createTestCodeLensProvider = function (monaco, editor) {
+ let runTestsCommand = editor.addCommand(
+ 0,
+ (_ctx, testIndices) => {
+ let tests;
+ try {
+ tests = JSON.parse(editor.getValue());
+ }
+ catch (e) {
+ _logOutput('Error parsing tests:\n' + e);
+ }
+
+ if (testIndices) {
+ tests = testIndices.map(index => tests[index]);
+ }
+
+ this.runTests(tests);
+ },
+ '');
+
+ let updateTestsCommand = editor.addCommand(
+ 0,
+ async (_ctx, testIndices) => {
+ testIndices = testIndices || Object.keys(allTests);
+
+ try {
+ var allTests = JSON.parse(editor.getValue());
+ }
+ catch (e) {
+ _logOutput('Error parsing tests:\n' + e);
+ return;
+ }
+
+ let tests = testIndices.map(index => allTests[index]);
+
+ await this.updateTests(tests,
+ (newTest) => {
+ allTests[testIndices.shift()] = newTest;
+ });
+
+ _writeTestsToPane(allTests);
+ },
+ '');
+
+ return {
+ provideCodeLenses: (model, _token) => {
+ let lenses = [];
+
+ let firstChar = {
+ startLineNumber: 1,
+ startColumn: 1,
+ endLineNumber: 1,
+ endColumn: 1
+ };
+ lenses.push({
+ range: firstChar,
+ command: {
+ id: runTestsCommand,
+ title: 'Run All'
+ }
+ });
+ lenses.push({
+ range: firstChar,
+ command: {
+ id: updateTestsCommand,
+ title: 'Run and Update All'
+ }
+ });
+
+ for (let [testIndex, range] of _findTestObjectTops(monaco, model).entries()) {
+ lenses.push({
+ range: range,
+ command: {
+ id: runTestsCommand,
+ title: 'Run',
+ arguments: [[testIndex]]
+ }
+ });
+
+ lenses.push({
+ range: range,
+ command: {
+ id: updateTestsCommand,
+ title: 'Run and Update',
+ arguments: [[testIndex]]
+ }
+ });
+ }
+
+ return lenses;
+ },
+ resolveCodeLens: (_model, codeLens, _token) => codeLens
+ };
+ };
+
+ this.updateModelMarkers = function (translatorPath) {
+ runESLint(translatorPath)
+ .then(eslintOutputToModelMarkers)
+ .then(markers => _editors.codeGlobal.editor.setModelMarkers(_editors.code.getModel(), 'eslint', markers));
+ };
+
+ this.setFontSize = function (size) {
var sizeWithPX = size + 'px';
- _editors.import.setOptions({fontSize: sizeWithPX});
- _editors.code.setOptions({fontSize: sizeWithPX});
- _editors.tests.setOptions({fontSize: sizeWithPX});
+ _editors.import.updateOptions({ fontSize: size + 1 }); // editor font needs to be a little bigger
+ _editors.code.updateOptions({ fontSize: size + 1 });
+ _editors.tests.updateOptions({ fontSize: size + 1 });
document.getElementById("scaffold-pane").style.fontSize = sizeWithPX;
- if (size==11) {
+ if (size == 11) {
// for the default value 11, clear the prefs
Zotero.Prefs.clear('scaffold.fontSize');
- } else {
+ }
+ else {
Zotero.Prefs.set("scaffold.fontSize", size);
}
- }
+ };
- this.increaseFontSize = function() {
+ this.increaseFontSize = function () {
var currentSize = Zotero.Prefs.get("scaffold.fontSize") || 11;
- this.setFontSize(currentSize+2);
- }
- this.decreaseFontSize = function() {
+ this.setFontSize(currentSize + 2);
+ };
+ this.decreaseFontSize = function () {
var currentSize = Zotero.Prefs.get("scaffold.fontSize") || 11;
- this.setFontSize(currentSize-2);
- }
+ this.setFontSize(currentSize - 2);
+ };
+
+ this.newTranslator = async function () {
+ _logOutput('Saving translator and resetting...');
+ await this.save();
+
+ this.generateTranslatorID();
+ document.getElementById('textbox-label').value = 'Untitled';
+ document.getElementById('textbox-creator').value
+ = document.getElementById('textbox-target').value
+ = document.getElementById('textbox-target-all').value
+ = document.getElementById('textbox-configOptions').value
+ = document.getElementById('textbox-displayOptions').value
+ = document.getElementById('textbox-hidden-prefs').value
+ = '';
+ document.getElementById('textbox-minVersion').value = '5.0';
+ document.getElementById('textbox-priority').value = '100';
+ document.getElementById('checkbox-import').checked = false;
+ document.getElementById('checkbox-export').checked = false;
+ document.getElementById('checkbox-web').checked = true;
+ document.getElementById('checkbox-search').checked = false;
+
+ _editors.code.setValue('');
+ _editors.tests.setValue('');
+
+ this.populateTests();
+
+ document.getElementById('textbox-label').focus();
+ _showTab('metadata');
+ };
/*
* load translator
*/
- this.load = Zotero.Promise.coroutine(function* (translatorID) {
+ this.load = async function (translatorID) {
+ await _translatorsLoadedPromise;
+
var translator = false;
if (translatorID === undefined) {
var io = {};
io.translatorProvider = _translatorProvider;
- io.url = _getDocument().location.href;
+ io.url = _getDocument()?.location.href || 'about:blank';
io.rootUrl = _browser.contentDocument.location.href;
window.openDialog("chrome://scaffold/content/load.xul",
- "_blank","chrome,modal", io);
+ "_blank", "chrome,modal", io);
translator = io.dataOut;
- } else {
- yield _translatorsLoadedPromise;
+ }
+ else {
translator = _translatorProvider.get(translatorID);
}
// No translator was selected in the dialog.
- if (!translator) return false;
+ if (!translator) return;
- for(var id in _propertyMap) {
+ for (var id in _propertyMap) {
document.getElementById(id).value = translator[_propertyMap[id]] || "";
}
//Strip JSON metadata
- var code = yield _translatorProvider.getCodeForTranslator(translator);
+ var code = await _translatorProvider.getCodeForTranslator(translator);
var lastUpdatedIndex = code.indexOf('"lastUpdated"');
var header = code.substr(0, lastUpdatedIndex + 50);
var m = /^\s*{[\S\s]*?}\s*?[\r\n]+/.exec(header);
- var fixedCode = code.substr(m[0].length);
+ var fixedCode = code.substr(m[0].length);
// adjust the first line number when there are an unusual number of metadata lines
- var linesOfMetadata = m[0].split('\n').length;
- _editors.code.getSession().setOption("firstLineNumber", linesOfMetadata);
- // load tests into test editing pane, but clear it first
- _editors["tests"].getSession().setValue('');
- _loadTests(fixedCode);
- // and remove them from the translator code
- var testStart = fixedCode.indexOf("/** BEGIN TEST CASES **/");
- var testEnd = fixedCode.indexOf("/** END TEST CASES **/");
- if (testStart !== -1 && testEnd !== -1)
- fixedCode = fixedCode.substr(0,testStart) + fixedCode.substr(testEnd+23);
-
+ _linesOfMetadata = m[0].split('\n').length;
+ // load tests into test editing pane
+ _loadTestsFromTranslator(fixedCode);
+ // clear selection
+ _editors.tests.setSelection({
+ startLineNumber: 1,
+ endLineNumber: 1,
+ startColumn: 1,
+ endColumn: 1
+ });
+
// Set up the test running pane
this.populateTests();
+ // remove tests from the translator code before loading into the code editor
+ var testStart = fixedCode.indexOf("/** BEGIN TEST CASES **/");
+ var testEnd = fixedCode.indexOf("/** END TEST CASES **/");
+ if (testStart !== -1 && testEnd !== -1) fixedCode = fixedCode.substr(0, testStart) + fixedCode.substr(testEnd + 23);
+
// Convert whitespace to tabs
- _editors.code.getSession().setValue(normalizeWhitespace(fixedCode));
+ _editors.code.setValue(normalizeWhitespace(fixedCode));
// Then go to line 1
- _editors.code.gotoLine(1);
+ _editors.code.setPosition({ lineNumber: 1, column: 1 });
// Reset configOptions and displayOptions before loading
document.getElementById('textbox-configOptions').value = '';
@@ -303,7 +642,7 @@ var Scaffold = new function() {
if (configOptions != '{}') {
document.getElementById('textbox-configOptions').value = configOptions;
}
- }
+ }
if (translator.displayOptions) {
let displayOptions = JSON.stringify(translator.displayOptions);
if (displayOptions != '{}') {
@@ -314,21 +653,15 @@ var Scaffold = new function() {
// get translator type; might as well have some fun here
var type = translator.translatorType;
var types = ["import", "export", "web", "search"];
- for(var i=2; i<=16; i*=2) {
+ for (var i = 2; i <= 16; i *= 2) {
var mod = type % i;
- document.getElementById('checkbox-'+types.shift()).checked = !!mod;
- if(mod) type -= mod;
- }
-
- // get browser support
- var browserSupport = translator.browserSupport;
- if(!browserSupport) browserSupport = "g";
- const browsers = {gecko:"g", chrome:"c", safari:"s", ie:"i", bookmarklet:"b", server:"v"};
- for (var browser in browsers) {
- document.getElementById('checkbox-'+browser).checked = browserSupport.indexOf(browsers[browser]) !== -1;
+ document.getElementById('checkbox-' + types.shift()).checked = !!mod;
+ if (mod) type -= mod;
}
- });
+ this.updateModelMarkers(translator.path);
+ _lastModifiedTime = new Date().getTime();
+ };
function _getMetadataObject() {
var metadata = {
@@ -337,7 +670,7 @@ var Scaffold = new function() {
creator: document.getElementById('textbox-creator').value,
target: document.getElementById('textbox-target').value,
minVersion: document.getElementById('textbox-minVersion').value,
- maxVersion: document.getElementById('textbox-maxVersion').value,
+ maxVersion: '',
priority: parseInt(document.getElementById('textbox-priority').value)
};
@@ -350,59 +683,41 @@ var Scaffold = new function() {
}
if (document.getElementById('textbox-configOptions').value) {
- metadata.configOptions = JSON.parse(document.getElementById('textbox-configOptions').value);
+ metadata.configOptions = JSON.parse(document.getElementById('textbox-configOptions').value);
}
if (document.getElementById('textbox-displayOptions').value) {
- metadata.displayOptions = JSON.parse(document.getElementById('textbox-displayOptions').value);
+ metadata.displayOptions = JSON.parse(document.getElementById('textbox-displayOptions').value);
}
// no option for this
metadata.inRepository = true;
metadata.translatorType = 0;
- if(document.getElementById('checkbox-import').checked) {
+ if (document.getElementById('checkbox-import').checked) {
metadata.translatorType += 1;
}
- if(document.getElementById('checkbox-export').checked) {
+ if (document.getElementById('checkbox-export').checked) {
metadata.translatorType += 2;
}
- if(document.getElementById('checkbox-web').checked) {
+ if (document.getElementById('checkbox-web').checked) {
metadata.translatorType += 4;
}
- if(document.getElementById('checkbox-search').checked) {
+ if (document.getElementById('checkbox-search').checked) {
metadata.translatorType += 8;
}
- if (document.getElementById('checkbox-web').checked) {
- // save browserSupport only for web tranlsators
- metadata.browserSupport = "";
- if(document.getElementById('checkbox-gecko').checked) {
- metadata.browserSupport += "g";
- }
- if(document.getElementById('checkbox-chrome').checked) {
- metadata.browserSupport += "c";
- }
- if(document.getElementById('checkbox-safari').checked) {
- metadata.browserSupport += "s";
- }
- if(document.getElementById('checkbox-ie').checked) {
- metadata.browserSupport += "i";
- }
- if(document.getElementById('checkbox-bookmarklet').checked) {
- metadata.browserSupport += "b";
- }
- if(document.getElementById('checkbox-server').checked) {
- metadata.browserSupport += "v";
- }
- }
+ if (document.getElementById('checkbox-web').checked) {
+ // save browserSupport only for web tranlsators
+ metadata.browserSupport = "gcsibv";
+ }
var date = new Date();
metadata.lastUpdated = date.getUTCFullYear()
- +"-"+Zotero.Utilities.lpad(date.getUTCMonth()+1, '0', 2)
- +"-"+Zotero.Utilities.lpad(date.getUTCDate(), '0', 2)
- +" "+Zotero.Utilities.lpad(date.getUTCHours(), '0', 2)
- +":"+Zotero.Utilities.lpad(date.getUTCMinutes(), '0', 2)
- +":"+Zotero.Utilities.lpad(date.getUTCSeconds(), '0', 2);
+ + "-" + Zotero.Utilities.lpad(date.getUTCMonth() + 1, '0', 2)
+ + "-" + Zotero.Utilities.lpad(date.getUTCDate(), '0', 2)
+ + " " + Zotero.Utilities.lpad(date.getUTCHours(), '0', 2)
+ + ":" + Zotero.Utilities.lpad(date.getUTCMinutes(), '0', 2)
+ + ":" + Zotero.Utilities.lpad(date.getUTCSeconds(), '0', 2);
return metadata;
}
@@ -410,10 +725,12 @@ var Scaffold = new function() {
/*
* save translator to database
*/
- this.save = Zotero.Promise.coroutine(function* (updateZotero) {
- var code = _editors.code.getSession().getValue();
- var tests = _editors.tests.getSession().getValue();
- code += tests;
+ this.save = async function (updateZotero) {
+ var code = _editors.code.getValue();
+ var tests = _editors.tests.getValue().trim();
+ if (!tests || tests == '[]') tests = '[\n]'; // eslint wants a line break between the brackets
+
+ code += '/** BEGIN TEST CASES **/\nvar testCases = ' + tests + '\n/** END TEST CASES **/';
var metadata = _getMetadataObject();
if (metadata.label === "Untitled") {
@@ -421,14 +738,34 @@ var Scaffold = new function() {
return;
}
- yield _translatorProvider.save(metadata, code);
+ var path = await _translatorProvider.save(metadata, code);
if (updateZotero) {
- yield Zotero.Translators.save(metadata, code);
- yield Zotero.Translators.reinit();
+ await Zotero.Translators.save(metadata, code);
+ await Zotero.Translators.reinit();
}
- });
-
+
+ _lastModifiedTime = new Date().getTime();
+
+ this.updateModelMarkers(path);
+ this.reloadTranslators();
+ };
+
+ /**
+ * If an editor is focused, trigger `editorTrigger` in it.
+ * Otherwise, run `fallbackCommand`.
+ */
+ this.trigger = function (editorTrigger, fallbackCommand) {
+ let activeEditor = _editors[_getActiveEditorName()];
+ if (activeEditor) {
+ activeEditor.trigger('Scaffold.trigger', editorTrigger);
+ }
+ else {
+ // editMenuOverlay.js
+ goDoCommand(fallbackCommand);
+ }
+ };
+
this.handleTabSelect = function (event) {
// Focus editor when switching to tab
var tab = event.target.selectedItem.id.match(/^tab-(.+)$/)[1];
@@ -436,92 +773,153 @@ var Scaffold = new function() {
case 'import':
case 'code':
case 'tests':
- _editors[tab].focus();
+ // the select event's default behavior is to focus the selected tab.
+ // we don't want to prevent *all* of the event's default behavior,
+ // but we do want to focus the editor instead of the tab.
+ // so this stupid hack waits 10 ms for event processing to finish
+ // before focusing the editor.
+ setTimeout(() => {
+ document.getElementById(`editor-${tab}`).focus();
+ _editors[tab].focus();
+ }, 10);
break;
}
+
+ let codeTabBroadcaster = document.getElementById('code-tab-only');
+ if (tab == 'code') {
+ codeTabBroadcaster.removeAttribute('disabled');
+ }
+ else {
+ codeTabBroadcaster.setAttribute('disabled', true);
+ }
+ };
+
+ this.handleTestSelect = function (event) {
+ let selected = event.target.selectedItems[0];
+ if (!selected) return;
+
+ let editImport = document.getElementById('testing_editImport');
+ let openURL = document.getElementById('testing_openURL');
+ if (selected.getUserData('test-type') == 'web') {
+ editImport.setAttribute('disabled', true);
+ openURL.removeAttribute('disabled');
+ }
+ else {
+ editImport.removeAttribute('disabled');
+ openURL.setAttribute('disabled', true);
+ }
+ };
+
+ this.listFieldsForItemType = function (itemType) {
+ var outputObject = {};
+ outputObject.itemType = Zotero.ItemTypes.getName(itemType);
+ var typeID = Zotero.ItemTypes.getID(itemType);
+ var fieldList = Zotero.ItemFields.getItemTypeFields(typeID);
+ for (let field of fieldList) {
+ var key = Zotero.ItemFields.getName(field);
+ outputObject[key] = "";
+ }
+ var creatorList = Zotero.CreatorTypes.getTypesForItemType(typeID);
+ var creators = [];
+ for (let creatorType of creatorList) {
+ creators.push({ firstName: "", lastName: "", creatorType: creatorType.name, fieldMode: true });
+ }
+ outputObject.creators = creators;
+ outputObject.attachments = [{ url: "", document: "", title: "", mimeType: "" }];
+ outputObject.tags = [{ tag: "" }];
+ outputObject.notes = [{ note: "" }];
+ outputObject.seeAlso = [];
+ return JSON.stringify(outputObject, null, '\t');
};
/*
* add template code
*/
- this.addTemplate = Zotero.Promise.coroutine(function* (template, second) {
- switch(template) {
+ this.addTemplate = async function (template, second) {
+ switch (template) {
case "templateNewItem":
- var outputObject = {};
- outputObject.itemType = Zotero.ItemTypes.getName(second);
- var typeID = Zotero.ItemTypes.getID(second);
- var fieldList = Zotero.ItemFields.getItemTypeFields(typeID);
- for (var i=0; i t.name);
document.getElementById('output').value = JSON.stringify(typeNames, null, '\t');
break;
- case "shortcuts":
- var value = Zotero.File.getContentsFromURL(`chrome://scaffold/content/templates/shortcuts.txt`);
- document.getElementById('output').value = value;
- break
- default:
+ default: {
//newWeb, scrapeEM, scrapeRIS, scrapeBibTeX, scrapeMARC
//These names in the XUL file have to match the file names in template folder.
- var cursorPos = _editors.code.getSession().selection.getCursor();
- var value = Zotero.File.getContentsFromURL(`chrome://scaffold/content/templates/${template}.js`);
- _editors.code.getSession().insert(cursorPos, value);
- break
+ let value = Zotero.File.getContentsFromURL(`chrome://scaffold/content/templates/${template}.js`);
+ let cursorOffset = value.indexOf('$$CURSOR$$');
+ value = value.replace('$$CURSOR$$', '');
+
+ var selection = _editors.code.getSelection();
+ var id = { major: 1, minor: 1 };
+ var op = { identifier: id, range: selection, text: value, forceMoveMarkers: true };
+ _editors.code.executeEdits("addTemplate", [op]);
+
+ if (cursorOffset != -1) {
+ _editors.code.setPosition(_editors.code.getModel().getPositionAt(cursorOffset));
+ }
+
+ break;
+ }
}
- });
+ };
/*
* run translator
*/
- this.run = Zotero.Promise.coroutine(function* (functionToRun) {
+ this.run = async function (functionToRun) {
if (document.getElementById('textbox-label').value == 'Untitled') {
- alert("Translator title not set");
+ _logOutput("Translator title not set");
return;
}
_clearOutput();
- if(document.getElementById('checkbox-editor-external').checked) {
- // We don't save the translator-- we reload it instead
- var translatorID = document.getElementById('textbox-translatorID').value;
- yield this.load(translatorID);
- }
-
// Handle generic call run('detect'), run('do')
if (functionToRun == "detect" || functionToRun == "do") {
- var isWeb = document.getElementById('checkbox-web').checked;
- functionToRun += isWeb ? "Web" : "Import";
+ if (document.getElementById('checkbox-web').checked
+ && _browser.contentWindow.location.href != 'about:blank') {
+ functionToRun += 'Web';
+ }
+ else if (document.getElementById('checkbox-import').checked
+ && _editors.import.getValue().trim()) {
+ functionToRun += 'Import';
+ }
+ else if (document.getElementById('checkbox-export').checked
+ && functionToRun == 'do') {
+ functionToRun += 'Export';
+ }
+ else if (document.getElementById('checkbox-search').checked
+ && _editors.import.getValue().trim()) {
+ functionToRun += 'Search';
+ }
+ else {
+ _logOutput('No appropriate detect/do function to run');
+ return;
+ }
}
+
+ _logOutput(`Running ${functionToRun}`);
- if (functionToRun == "detectWeb" || functionToRun == "doWeb") {
- _run(functionToRun, _getDocument(), _selectItems, _myItemDone, _translators);
- } else if (functionToRun == "detectImport" || functionToRun == "doImport") {
- _run(functionToRun, _getImport(), _selectItems, _myItemDone, _translatorsImport);
+ let input = _getInput(functionToRun);
+
+ if (functionToRun.endsWith('Export')) {
+ let numItems = Zotero.getActiveZoteroPane().getSelectedItems().length;
+ _logOutput(`Exporting ${numItems} item${numItems == 1 ? '' : 's'} selected in library`);
+ _run(functionToRun, input, _selectItems, () => {}, _getTranslatorsHandler(functionToRun), _myExportDone);
}
- });
+ else {
+ _run(functionToRun, input, _selectItems, _myItemDone, _getTranslatorsHandler(functionToRun));
+ }
+ };
/*
* run translator in given mode with given input
*/
- async function _run(functionToRun, input, selectItems, itemDone, detectHandler, done) {
+ function _run(functionToRun, input, selectItems, itemDone, detectHandler, done) {
if (functionToRun == "detectWeb" || functionToRun == "doWeb") {
var translate = new Zotero.Translate.Web();
- var utilities = new Zotero.Utilities.Translate(translate);
if (!_testTargetRegex(input)) {
_logOutput("Target did not match " + _getDocumentURL(input));
if (done) {
@@ -537,20 +935,29 @@ var Scaffold = new function() {
_getDocumentURL(input),
input.cookie
));
- } else if (functionToRun == "detectImport" || functionToRun == "doImport") {
- var translate = new Zotero.Translate.Import();
+ }
+ else if (functionToRun == "detectImport" || functionToRun == "doImport") {
+ translate = new Zotero.Translate.Import();
translate.setString(input);
}
+ else if (functionToRun == "doExport") {
+ translate = new Zotero.Translate.Export();
+ translate.setItems(input);
+ }
+ else if (functionToRun == "detectSearch" || functionToRun == "doSearch") {
+ translate = new Zotero.Translate.Search();
+ translate.setSearch(input);
+ }
translate.setTranslatorProvider(_translatorProvider);
translate.setHandler("error", _error);
translate.setHandler("debug", _debug);
if (done) {
translate.setHandler("done", done);
}
-
- if (functionToRun == "detectWeb") {
- // get translator
- var translator = _getTranslatorFromPane();
+
+ // get translator
+ var translator = _getTranslatorFromPane();
+ if (functionToRun.startsWith('detect')) {
// don't let target prevent translator from operating
translator.target = null;
// generate sandbox
@@ -560,42 +967,17 @@ var Scaffold = new function() {
translate._foundTranslators = [];
translate._currentState = "detect";
translate._detect();
- } else if (functionToRun == "doWeb") {
- // get translator
- var translator = _getTranslatorFromPane();
+ }
+ else {
// don't let the detectCode prevent the translator from operating
translator.detectCode = null;
translate.setTranslator(translator);
translate.setHandler("select", selectItems);
translate.clearHandlers("itemDone");
- translate.setHandler("itemDone", itemDone);
- translate.translate({
- // disable saving to database
- libraryID: false
- });
- } else if (functionToRun == "detectImport") {
- // get translator
- var translator = _getTranslatorFromPane();
- // don't let target prevent translator from operating
- translator.target = null;
- // generate sandbox
- translate.setHandler("translators", detectHandler);
- // internal hack to call detect on this translator
- translate._potentialTranslators = [translator];
- translate._foundTranslators = [];
- translate._currentState = "detect";
- translate._detect();
- } else if (functionToRun == "doImport") {
- // get translator
- var translator = _getTranslatorFromPane();
- // don't let the detectCode prevent the translator from operating
- translator.detectCode = null;
- translate.setTranslator(translator);
- translate.clearHandlers("itemDone");
translate.clearHandlers("collectionDone");
translate.setHandler("itemDone", itemDone);
- translate.setHandler("collectionDone", function(obj, collection) {
- _logOutput("Collection: "+ collection.name + ", "+collection.children.length+" items");
+ translate.setHandler("collectionDone", function (obj, collection) {
+ _logOutput("Collection: " + collection.name + ", " + collection.children.length + " items");
});
translate.translate({
// disable saving to database
@@ -605,21 +987,21 @@ var Scaffold = new function() {
}
this.runTranslatorOrTests = async function () {
- var tabs = document.getElementById('tabs');
- if (tabs.selectedItem.id == 'tab-testing') {
+ if (document.getElementById('tabs').selectedItem.id == 'tab-tests'
+ && document.activeElement.id == 'testing-listbox') {
this.runSelectedTests();
}
else {
this.run('do');
}
- }
+ };
/*
* generate translator GUID
*/
- this.generateTranslatorID = function() {
+ this.generateTranslatorID = function () {
document.getElementById("textbox-translatorID").value = _generateGUID();
- }
+ };
/**
* Test target regular expression against document URL and log the result
@@ -648,9 +1030,9 @@ var Scaffold = new function() {
* called to select items
*/
function _selectItems(obj, itemList) {
- var io = { dataIn:itemList, dataOut:null }
- var newDialog = window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
- "_blank","chrome,modal,centerscreen,resizable=yes", io);
+ var io = { dataIn: itemList, dataOut: null };
+ window.openDialog("chrome://zotero/content/ingester/selectitems.xul",
+ "_blank", "chrome,modal,centerscreen,resizable=yes", io);
return io.dataOut;
}
@@ -658,12 +1040,9 @@ var Scaffold = new function() {
/*
* called if an error occurs
*/
- function _error(obj, error) {
- if(error && error.lineNumber &&
- error.fileName == obj.translator[0].label ) {
- var lines = _editors.code.getSession().getOption("firstLineNumber");
- _editors.code.gotoLine(error.lineNumber-lines+1); // subtract the metadata lines
- }
+ function _error(_obj, _error) {
+ // stub: this handler doesn't actually seem to get called by the current
+ // translation architecture when a translator throws
}
/*
@@ -677,35 +1056,42 @@ var Scaffold = new function() {
* logs item output
*/
function _myItemDone(obj, item) {
- Zotero.debug("Item returned");
-
item = _sanitizeItem(item);
- _logOutput("Returned item:\n"+Zotero_TranslatorTester._generateDiff(item, Zotero_TranslatorTester._sanitizeItem(item, true)));
+ _logOutput("Returned item:\n" + Zotero_TranslatorTester._generateDiff(item, Zotero_TranslatorTester._sanitizeItem(item, true)));
}
/*
- * prints information from detectCode to window
+ * logs string output
*/
- function _translators(obj, translators) {
- if(translators && translators.length != 0) {
- _logOutput('detectWeb returned type "'+translators[0].itemType+'"');
- } else {
- _logOutput('detectWeb did not match');
+ function _myExportDone({ string }, worked) {
+ if (worked) {
+ Zotero.debug("Export successful");
+ _logOutput("Returned string:\n" + string);
}
-
- }
+ else {
+ Zotero.debug("Export failed");
+ }
+ }
/*
- * prints information from detectCode to window, for import
+ * returns a 'translators' handler that prints information from detectCode to window
*/
- function _translatorsImport(obj, translators) {
- if(translators && translators.length != 0 && translators[0].itemType) {
- _logOutput('detectImport matched');
- } else {
- _logOutput('detectImport did not match');
- }
- }
+ function _getTranslatorsHandler(fnName) {
+ return (obj, translators) => {
+ if (translators && translators.length != 0) {
+ if (translators[0].itemType === true) {
+ _logOutput(`${fnName} matched`);
+ }
+ else {
+ _logOutput(`${fnName} returned type "${translators[0].itemType}"`);
+ }
+ }
+ else {
+ _logOutput(`${fnName} did not match`);
+ }
+ };
+ }
/*
* logs debug info (instead of console)
@@ -714,15 +1100,15 @@ var Scaffold = new function() {
var date = new Date();
var output = document.getElementById('output');
- if(typeof string != "string") {
+ if (typeof string != "string") {
string = fix2028(Zotero.Utilities.varDump(string));
}
- if(output.value) output.value += "\n";
+ if (output.value) output.value += "\n";
output.value += Zotero.Utilities.lpad(date.getHours(), '0', 2)
- +":"+Zotero.Utilities.lpad(date.getMinutes(), '0', 2)
- +":"+Zotero.Utilities.lpad(date.getSeconds(), '0', 2)
- +" "+string.replace(/\n/g, "\n ");
+ + ":" + Zotero.Utilities.lpad(date.getMinutes(), '0', 2)
+ + ":" + Zotero.Utilities.lpad(date.getSeconds(), '0', 2)
+ + " " + string.replace(/\n/g, "\n ");
// move to end
output.inputField.scrollTop = output.inputField.scrollHeight;
}
@@ -731,25 +1117,71 @@ var Scaffold = new function() {
* gets import text for import translator
*/
function _getImport() {
- var text = _editors.import.getSession().getValue();
+ var text = _editors.import.getValue();
return text;
}
+ /*
+ * gets items to export for export translator
+ */
+ function _getExport() {
+ return Zotero.getActiveZoteroPane().getSelectedItems();
+ }
+
+ /*
+ * gets search JSON object for search translator
+ */
+ function _getSearch() {
+ return JSON.parse(_getImport());
+ }
+
+ /*
+ * gets appropriate input for the given type/method
+ */
+ function _getInput(typeOrMethod) {
+ typeOrMethod = typeOrMethod.toLowerCase();
+ if (typeOrMethod.endsWith('web')) {
+ return _getDocument();
+ }
+ else if (typeOrMethod.endsWith('import')) {
+ return _getImport();
+ }
+ else if (typeOrMethod.endsWith('export')) {
+ return _getExport();
+ }
+ else if (typeOrMethod.endsWith('search')) {
+ return _getSearch();
+ }
+ return null;
+ }
+
/*
* transfers metadata to the translator object
* Replicated from translator.js
*/
function _metaToTranslator(translator, metadata) {
- var props = ["translatorID", "translatorType", "label", "creator", "target",
- "minVersion", "maxVersion", "priority", "lastUpdated", "inRepository", "configOptions",
- "displayOptions", "browserSupport", "targetAll", "hiddenPrefs"];
- for (var i=0; i2.1
- if(Zotero.Translator.RUN_MODE_IN_BROWSER) {
+ if (Zotero.Translator.RUN_MODE_IN_BROWSER) {
translator.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
}
return translator;
}
+ /*
+ * loads the translator's tests from the translator code
+ */
+ function _loadTestsFromTranslator(code) {
+ var testStart = code.indexOf("/** BEGIN TEST CASES **/");
+ var testEnd = code.indexOf("/** END TEST CASES **/");
+ if (testStart !== -1 && testEnd !== -1) {
+ code = code.substring(testStart + 24, testEnd);
+ }
+
+ code = code.replace(/var testCases = /, '').trim();
+ // The JSON parser doesn't like final semicolons
+ if (code.lastIndexOf(';') == code.length - 1) {
+ code = code.slice(0, -1);
+ }
+
+ try {
+ var testObject = JSON.parse(code);
+ }
+ catch (e) {
+ testObject = [];
+ }
+
+ // We don't use _writeTestsToPane here because we want to avoid _stringifyTests,
+ // which assumes valid test data and will choke on/incorrectly "fix"
+ // weird inputs that the user might want to fix manually.
+ _writeToEditor(_editors.tests, JSON.stringify(testObject, null, "\t"));
+ }
+
/*
* loads the translator's tests from the pane
*/
- function _loadTests(code) {
- var testStart = code.indexOf("/** BEGIN TEST CASES **/");
- var testEnd = code.indexOf("/** END TEST CASES **/");
- if (testStart !== -1 && testEnd !== -1) {
- test = code.substring(testStart + 24, testEnd);
- test = test.replace(/var testCases = /,'').trim();
- // The JSON parser doesn't like final semicolons
- if (test.lastIndexOf(';') == (test.length-1))
- test = test.slice(0,-1);
- try {
- var testObject = JSON.parse(test);
- _writeTests(JSON.stringify(testObject, null, "\t")); // Don't modify current tests
- return testObject;
- } catch (e) {
- _logOutput("Exception parsing test JSON:\n\n" + e);
- return false;
- }
- } else {
- return false;
+ function _loadTestsFromPane() {
+ try {
+ return JSON.parse(_editors.tests.getValue().trim() || '[]');
}
+ catch (e) {
+ return null;
+ }
+ }
+
+ /**
+ * Write text to an editor, overwriting its current value.
+ * This operation can be undone.
+ */
+ function _writeToEditor(editor, text) {
+ editor.executeEdits('_writeToEditor', [{
+ range: editor.getModel().getFullModelRange(),
+ text
+ }]);
}
/*
* writes tests back into the translator
*/
- function _writeTests(testString) {
- var code = "/** BEGIN TEST CASES **/\nvar testCases = "
- + testString + "\n/** END TEST CASES **/";
- _editors["tests"].getSession().setValue(code);
- }
-
- /* clear tests pane */
- function _clearTests() {
- var listbox = document.getElementById("testing-listbox");
- var count = listbox.itemCount;
- while(count-- > 0){
- listbox.removeItemAt(0);
- }
+ function _writeTestsToPane(tests) {
+ _writeToEditor(_editors.tests, _stringifyTests(tests));
}
/* turns an item into a test-safe item
@@ -824,9 +1272,8 @@ var Scaffold = new function() {
function _sanitizeItem(item) {
// Clear attachment document objects
if (item && item.attachments && item.attachments.length) {
- for (var i=0; i 1 ? "\n]" : ']');
}
- if(!value.itemType) {
+ if (!value.itemType) {
// Not a Zotero.Item object
let str = '{';
-
- function processRow(key, value) {
- let val = _stringifyTests(value, level+1);
- if(val === undefined) return;
-
- val = val.replace(/\n/g, "\n\t");
- return JSON.stringify(''+key) + ': ' + val;
- }
-
+
if (level < 2 && value.items) {
// Test object. Arrange properties in set order
let order = ['type', 'url', 'input', 'defer', 'items'];
- for (let i=0; i 1 ? ',' : '') + '\n\t' + val;
}
- } else {
+ }
+ else {
for (let i in value) {
let val = processRow(i, value[i]);
if (val === undefined) continue;
@@ -910,35 +1358,44 @@ var Scaffold = new function() {
}
// Zotero.Item object
- const topFields = ['itemType', 'title', 'caseName', 'nameOfAct', 'subject',
- 'creators', 'date', 'dateDecided', 'issueDate', 'dateEnacted'];
+ const topFields = ['itemType',
+ 'title',
+ 'caseName',
+ 'nameOfAct',
+ 'subject',
+ 'creators',
+ 'date',
+ 'dateDecided',
+ 'issueDate',
+ 'dateEnacted'];
const bottomFields = ['attachments', 'tags', 'notes', 'seeAlso'];
let otherFields = Object.keys(value);
let presetFields = topFields.concat(bottomFields);
- for(let i=0; i 1 ? ',':'') + "\n\t" + JSON.stringify(fields[i]) + ': ' + val;
+ str += (str.length > 1 ? ',' : '') + "\n\t" + JSON.stringify(fields[i]) + ': ' + val;
}
return str + "\n}";
@@ -948,170 +1405,191 @@ var Scaffold = new function() {
* adds a new test from the current input/translator
* web or import only for now
*/
- this.newTestFromCurrent = function(type) {
+ this.saveTestFromCurrent = async function (type) {
+ _logOutput(`Creating ${type} test...`);
+
+ try {
+ let test = await this.constructTestFromCurrent(type);
+ _writeTestsToPane([..._loadTestsFromPane(), test]);
+ }
+ catch (e) {
+ _logOutput('Creation failed');
+ return;
+ }
+
+ _showTab('tests');
+ let listBox = document.getElementById('testing-listbox');
+ listBox.selectedIndex = listBox.getRowCount() - 1;
+ listBox.focus();
+ };
+
+ this.constructTestFromCurrent = async function (type) {
_clearOutput();
- var input, label;
- if (type == "web" && !document.getElementById('checkbox-web').checked) {
- _logOutput("Current translator isn't a web translator");
- return false;
- } else if (type == "import" && !document.getElementById('checkbox-import').checked) {
- _logOutput("Current translator isn't an import translator");
- return false;
+ if ((type === "web" && !document.getElementById('checkbox-web').checked)
+ || (type === "import" && !document.getElementById('checkbox-import').checked)
+ || (type === "search" && !document.getElementById('checkbox-search').checked)) {
+ _logOutput(`Translator does not support ${type} tests`);
+ return Promise.reject(new Error());
}
- if (type == "web") {
- input = _getDocument();
- label = Zotero.Proxies.proxyToProper(input.location.href);
- } else if (type == "import") {
- input = _getImport();
- label = input;
- } else {
- return false;
+ if (type == 'export') {
+ return Promise.reject(new Error(`Test of type export cannot be created`));
}
- var listbox = document.getElementById("testing-listbox");
- var listitem = document.createElement("listitem");
- var listcell = document.createElement("listcell");
- listcell.setAttribute("label", label);
- listitem.appendChild(listcell);
- listcell = document.createElement("listcell");
- listcell.setAttribute("label", "Creating...");
- listitem.appendChild(listcell);
- listbox.appendChild(listitem);
+ let input = _getInput(type);
if (type == "web") {
- // Creates the test. The test isn't saved yet!
let tester = new Zotero_TranslatorTester(
_getTranslatorFromPane(),
type,
_debug,
_translatorProvider
);
- tester.newTest(input, function (obj, newTest) { // "done" handler for do
- if(newTest) {
- listcell.setAttribute("label", "New unsaved test");
- listitem.setUserData("test-string", JSON.stringify(_sanitizeItemsInTest(newTest)), null);
- } else {
- listcell.setAttribute("label", "Creation failed");
- }
- });
+ return new Promise(
+ (resolve, reject) => tester.newTest(input, function (obj, newTest) { // "done" handler for do
+ if (newTest) {
+ resolve(_sanitizeItemsInTest(newTest));
+ }
+ else {
+ reject(new Error('Creation failed'));
+ }
+ })
+ );
}
+ else if (type == "import" || type == "search") {
+ let test = { type, input: input, items: [] };
- if (type == "import") {
- var test = {"type" : "import", "input" : input, "items" : []};
-
- // Creates the test. The test isn't saved yet!
// TranslatorTester doesn't handle these correctly, so we do it manually
- _run("doImport", input, null, function(obj, item) {
- if(item) {
- test.items.push(Zotero_TranslatorTester._sanitizeItem(item));
- }
- }, null, function(){
- listcell.setAttribute("label", "New unsaved test");
- listitem.setUserData("test-string", JSON.stringify(test), null);
- });
+ return new Promise(
+ resolve => _run(`do${type == 'import' ? 'Import' : 'Search'}`, input, null, function (obj, item) {
+ if (item) {
+ test.items.push(Zotero_TranslatorTester._sanitizeItem(item));
+ }
+ }, null, function () {
+ resolve(test);
+ })
+ );
}
- }
+
+ return Promise.reject(new Error('Invalid type: ' + type));
+ };
/*
* populate tests pane and url options in browser pane
*/
- this.populateTests = function() {
- _clearTests();
- // Clear entries (but not value) in the url dropdown in the browser tab
- var browserURL = document.getElementById("browser-url");
- var currentURL = browserURL.value;
+ this.populateTests = function () {
+ let tests = _loadTestsFromPane();
+ let validateTestsBroadcaster = document.getElementById('validate-tests');
+ if (tests === null) {
+ validateTestsBroadcaster.setAttribute('disabled', true);
+ return;
+ }
+ else {
+ validateTestsBroadcaster.removeAttribute('disabled');
+ }
+
+ let browserURL = document.getElementById("browser-url");
+ let currentURL = browserURL.value;
browserURL.removeAllItems();
browserURL.value = currentURL;
-
- var tests = _loadTests(_editors["tests"].getSession().getValue());
- // We've got tests, let's display them
- var listbox = document.getElementById("testing-listbox");
- for (var i=0; i= count) {
+ listBox.appendChild(item);
}
- var test = item.getUserData("test-string");
- if(test) tests.push(JSON.parse(test));
- i++;
+
+ if (test.type == 'web') {
+ browserURL.appendItem(test.url);
+ }
+
+ testIndex++;
}
- _writeTests(_stringifyTests(tests));
- });
+
+ // remove old rows that we didn't reuse
+ while (listBox.getItemAtIndex(testIndex)) {
+ listBox.removeItemAt(testIndex);
+ }
+ };
/*
- * Delete selected test(s), from UI
+ * Delete selected test(s)
*/
- this.deleteSelectedTests = function() {
+ this.deleteSelectedTests = function () {
var listbox = document.getElementById("testing-listbox");
- var count = listbox.selectedCount;
- while (count--) {
- var item = listbox.selectedItems[0];
- listbox.removeItemAt(listbox.getIndexOfItem(item));
- }
- }
+ var indicesToRemove = [...listbox.selectedItems].map(item => listbox.getIndexOfItem(item));
+
+ let tests = _loadTestsFromPane();
+ indicesToRemove.forEach(i => tests.splice(i, 1));
+ _writeTestsToPane(tests);
+
+ this.populateTests();
+ };
/*
* Load the import input for the first selected test in the import pane,
* from the UI.
- */
- this.editImportFromTest = function() {
+ */
+ this.editImportFromTest = function () {
var listbox = document.getElementById("testing-listbox");
var item = listbox.selectedItems[0];
var test = JSON.parse(item.getUserData("test-string"));
if (test.input === undefined) {
- _logOutput("Can't edit import data for a non-import test.");
+ _logOutput("Can't edit input of a non-import/search test.");
}
- _editors.import.getSession().setValue(test.input);
- }
+ _writeToEditor(_editors.import,
+ test.type == 'import'
+ ? test.input
+ : JSON.stringify(test.input, null, '\t'));
+ _editors.import.setSelection({
+ startLineNumber: 1,
+ endLineNumber: 1,
+ startColumn: 1,
+ endColumn: 1
+ });
+ _showTab('import');
+ };
/*
* Copy the url or data of the first selected test to the clipboard.
- */
- this.copyToClipboard = function() {
+ */
+ this.copyToClipboard = function () {
var listbox = document.getElementById("testing-listbox");
var item = listbox.selectedItems[0];
var url = item.getElementsByTagName("listcell")[0].getAttribute("label");
var test = JSON.parse(item.getUserData("test-string"));
var urlOrData = (test.input !== undefined) ? test.input : url;
Zotero.Utilities.Internal.copyTextToClipboard(urlOrData);
- }
+ };
/**
* Open the url of the first selected test in the browser (Browser tab or
@@ -1126,101 +1604,137 @@ var Scaffold = new function() {
Zotero.launchURL(url);
}
else {
- var tabs = document.getElementById('tabs');
_browser.loadURIWithFlags(
url,
Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
);
- tabs.selectedItem = document.getElementById('tab-browser');
+ _showTab('browser');
}
- }
+ };
+
+ this.runTests = function (tests, callback) {
+ callback = callback || (() => {});
+
+ _clearOutput();
+
+ let testsByType = {
+ import: [],
+ export: [],
+ web: [],
+ search: []
+ };
+
+ for (let test of tests) {
+ testsByType[test.type].push(test);
+ }
+
+ for (let [type, testsOfType] of Object.entries(testsByType)) {
+ if (testsOfType.length) {
+ let tester = new Zotero_TranslatorTester(
+ _getTranslatorFromPane(),
+ type,
+ _debug,
+ _translatorProvider
+ );
+ tester.setTests(testsOfType);
+ tester.runTests(callback);
+ }
+ }
+ };
/*
* Run selected test(s)
*/
- this.runSelectedTests = function() {
- _clearOutput();
-
+ this.runSelectedTests = function () {
var listbox = document.getElementById("testing-listbox");
var items = listbox.selectedItems;
- if(!items || items.length == 0) return false; // No action if nothing selected
- var webtests = [];
- var importtests = [];
- for (var i=0; i 0) {
- let webtester = new Zotero_TranslatorTester(
- _getTranslatorFromPane(),
- "web",
- _debug,
- _translatorProvider
- );
- webtester.setTests(webtests);
- webtester.runTests(function(obj, test, status, message) {
- test["ui-item"].getElementsByTagName("listcell")[1].setAttribute("label", message);
- });
- }
-
- if (importtests.length > 0 ) {
- let importtester = new Zotero_TranslatorTester(
- _getTranslatorFromPane(),
- "import",
- _debug,
- _translatorProvider
- );
- importtester.setTests(importtests);
- importtester.runTests(function(obj, test, status, message) {
- test["ui-item"].getElementsByTagName("listcell")[1].setAttribute("label", message);
- });
- }
- }
+
+ this.runTests(tests, (obj, test, status, message) => {
+ test["ui-item"].getElementsByTagName("listcell")[1].setAttribute("label", message);
+ });
+ };
+
+ this.updateTests = function (tests, testUpdatedCallback) {
+ _clearOutput();
+
+ var updater = new TestUpdater(tests);
+ return new Promise(resolve => updater.updateTests(
+ testUpdatedCallback,
+ resolve
+ ));
+ };
/*
* Update selected test(s)
*/
- this.updateSelectedTests = function () {
- _clearOutput();
+ this.updateSelectedTests = async function () {
var listbox = document.getElementById("testing-listbox");
var items = [...listbox.selectedItems];
- if(!items || items.length == 0) return false; // No action if nothing selected
+ if (!items || items.length == 0) return; // No action if nothing selected
+ var itemIndices = items.map(item => listbox.getIndexOfItem(item));
var tests = [];
- for (var i=0; i {
+ let message;
// Assume sequential. TODO: handle this properly via test ID of some sort
- if(newTest) {
+ if (newTest) {
message = "Test updated";
- //Zotero.debug(newTest[testsDone]);
- items[testsDone].setUserData("test-string", JSON.stringify(newTest), null);
- } else {
- message = "Update failed"
+ items[testsDone].setUserData("test-string", _stringifyTests(newTest, 1));
+ tests[testsDone] = newTest;
+ }
+ else {
+ message = "Update failed";
}
items[testsDone].getElementsByTagName("listcell")[1].setAttribute("label", message);
testsDone++;
- },
- () => {
- _logOutput("Tests updated.");
- // Save tests
- _logOutput("Saving tests and translator.");
- this.saveTests();
- }
- );
- }
+ });
+
+ let allTests = _loadTestsFromPane();
+ for (let [i, test] of Object.entries(tests)) {
+ allTests[itemIndices[i]] = test;
+ }
+ _writeTestsToPane(allTests);
+ _logOutput("Tests updated.");
+ };
+
+ this.populateLinterMenu = function () {
+ let status = 'Path: ' + getDefaultESLintPath();
+ let toggle = Zotero.Prefs.get('scaffold.eslint.enabled') ? 'Disable' : 'Enable';
+ document.getElementById('menu_eslintStatus').label = status;
+ document.getElementById('menu_toggleESLint').label = toggle;
+ };
+
+ this.toggleESLint = async function () {
+ Zotero.Prefs.set('scaffold.eslint.enabled', !Zotero.Prefs.get('scaffold.eslint.enabled'));
+ await getESLintPath();
+ };
+
+ this.showTabNumbered = function (tabNumber) {
+ let tabBox = document.getElementById('left-tabbox');
+ let numTabs = tabBox.querySelectorAll('tabs > tab').length;
+ if (tabNumber > numTabs) {
+ tabNumber = numTabs;
+ }
+
+ tabBox.selectedIndex = tabNumber - 1;
+ };
- var TestUpdater = function(tests) {
+ var TestUpdater = function (tests) {
this.testsToUpdate = tests.slice();
this.numTestsTotal = this.testsToUpdate.length;
this.newTests = [];
@@ -1230,17 +1744,17 @@ var Scaffold = new function() {
_debug,
_translatorProvider
);
- }
+ };
- TestUpdater.prototype.updateTests = function(testDoneCallback, doneCallback) {
- this.testDoneCallback = testDoneCallback || function() { /* no-op */};
- this.doneCallback = doneCallback || function() { /* no-op */};
+ TestUpdater.prototype.updateTests = function (testDoneCallback, doneCallback) {
+ this.testDoneCallback = testDoneCallback || function () { /* no-op */ };
+ this.doneCallback = doneCallback || function () { /* no-op */ };
this._updateTests();
- }
+ };
- TestUpdater.prototype._updateTests = function() {
- if(!this.testsToUpdate.length) {
+ TestUpdater.prototype._updateTests = function () {
+ if (!this.testsToUpdate.length) {
this.doneCallback(this.newTests);
return;
}
@@ -1250,74 +1764,79 @@ var Scaffold = new function() {
var me = this;
- if (test.type == "import") {
+ if (test.type == 'web') {
+ _logOutput("Loading web page from " + test.url);
+ var hiddenBrowser = Zotero.HTTP.loadDocuments(
+ test.url,
+ function (doc) {
+ _logOutput("Page loaded");
+ if (test.defer) {
+ _logOutput("Waiting " + (Zotero_TranslatorTester.DEFER_DELAY / 1000)
+ + " second(s) for page content to settle"
+ );
+ }
+ Zotero.setTimeout(
+ function () {
+ doc = hiddenBrowser.contentDocument;
+ if (doc.location.href != test.url) {
+ _logOutput("Page URL differs from test. Will be updated. " + doc.location.href);
+ }
+ me.tester.newTest(doc, function (obj, newTest) {
+ Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
+ if (test.defer) {
+ newTest.defer = true;
+ }
+ newTest = _sanitizeItemsInTest(newTest);
+ me.newTests.push(newTest);
+ me.testDoneCallback(newTest);
+ me._updateTests();
+ });
+ },
+ test.defer ? Zotero_TranslatorTester.DEFER_DELAY : 0,
+ true
+ );
+ },
+ null,
+ function (e) {
+ Zotero.logError(e);
+ me.newTests.push(false);
+ me.testDoneCallback(false);
+ me._updateTests();
+ },
+ true
+ );
+
+ hiddenBrowser.docShell.allowMetaRedirects = true;
+ }
+ else {
test.items = [];
+ const methods = {
+ import: 'doImport',
+ export: 'doExport', // not supported, will error
+ search: 'doSearch'
+ };
+
// Re-runs the test.
// TranslatorTester doesn't handle these correctly, so we do it manually
- _run("doImport", test.input, null, function(obj, item) {
- if(item) {
+ _run(methods[test.type], test.input, null, function (obj, item) {
+ if (item) {
test.items.push(Zotero_TranslatorTester._sanitizeItem(item));
- }
- }, null, function() {
+ }
+ }, null, function () {
if (!test.items.length) test = false;
me.newTests.push(test);
me.testDoneCallback(test);
me._updateTests();
});
- // Don't want to run the web portion
- return true;
}
-
- _logOutput("Loading web page from " + test.url);
- var hiddenBrowser = Zotero.HTTP.loadDocuments(
- test.url,
- function (doc) {
- _logOutput("Page loaded");
- if (test.defer) {
- _logOutput("Waiting " + (Zotero_TranslatorTester.DEFER_DELAY/1000)
- + " second(s) for page content to settle"
- );
- }
- Zotero.setTimeout(
- function() {
- doc = hiddenBrowser.contentDocument;
- if (doc.location.href != test.url) {
- _logOutput("Page URL differs from test. Will be updated. "+ doc.location.href);
- }
- me.tester.newTest(doc, function(obj, newTest) {
- Zotero.Browser.deleteHiddenBrowser(hiddenBrowser);
- if (test.defer) {
- newTest.defer = true;
- }
- newTest = _sanitizeItemsInTest(newTest);
- me.newTests.push(newTest);
- me.testDoneCallback(newTest);
- me._updateTests();
- });
- },
- test.defer ? Zotero_TranslatorTester.DEFER_DELAY : 0,
- true
- )
- },
- null,
- function(e) {
- Zotero.logError(e);
- me.newTests.push(false);
- me.testDoneCallback(false);
- me._updateTests();
- },
- true
- );
-
- hiddenBrowser.docShell.allowMetaRedirects = true;
- }
+ };
/*
* Normalize whitespace to the Zotero norm of tabs
*/
function normalizeWhitespace(text) {
- return text.replace(/^[ \t]+/gm, function(str) {
+ return text.replace(/^[ \t]+/gm, function (str) {
return str.replace(/ {4}/g, "\t");
});
}
@@ -1334,16 +1853,16 @@ var Scaffold = new function() {
*/
function _generateGUID() {
var guid = "";
- for(var i=0; i<16; i++) {
+ for (var i = 0; i < 16; i++) {
var bite = Math.floor(Math.random() * 255);
- if(i == 4 || i == 6 || i == 8 || i == 10) {
+ if (i == 4 || i == 6 || i == 8 || i == 10) {
guid += "-";
// version
- if(i == 6) bite = bite & 0x0f | 0x40;
+ if (i == 6) bite = bite & 0x0f | 0x40;
// variant
- if(i == 8) bite = bite & 0x3f | 0x80;
+ if (i == 8) bite = bite & 0x3f | 0x80;
}
var str = bite.toString(16);
guid += str.length == 1 ? '0' + str : str;
@@ -1352,7 +1871,7 @@ var Scaffold = new function() {
}
/*
- * updates list of available frames and show URL of active tab
+ * updates list of available frames and show URL of active tab
*/
function _updateFrames() {
var doc = _browser.contentDocument;
@@ -1362,18 +1881,19 @@ var Scaffold = new function() {
// No need to run if Scaffold isn't open
var menulist = _document.getElementById("menulist-testFrame");
- if (!_document || !menulist) return true;
+ if (!_document || !menulist) return;
menulist.removeAllItems();
var popup = _document.createElement("menupopup");
menulist.appendChild(popup);
- _frames = new Array();
+ _frames = [];
var frames = doc.getElementsByTagName("frame");
- if(frames.length) {
+ if (frames.length) {
_getFrames(frames, popup);
- } else {
+ }
+ else {
var item = _document.createElement("menuitem");
item.setAttribute("label", "Default");
popup.appendChild(item);
@@ -1388,16 +1908,18 @@ var Scaffold = new function() {
* recursively searches for frames
*/
function _getFrames(frames, popup) {
- for (var i=0; i ({
+ startLineNumber: message.line - _linesOfMetadata + 1,
+ startColumn: message.column,
+ endLineNumber: message.endLine - _linesOfMetadata + 1,
+ endColumn: message.endColumn,
+ message: message.message,
+ severity: message.severity * 4,
+ source: 'ESLint',
+ tags: [
+ message.ruleId
+ ]
+ }));
+ }
+
+ function getTestLabel(test) {
+ switch (test.type) {
+ case 'import':
+ return test.input.substr(0, 80);
+ case 'web':
+ return test.url;
+ case 'search':
+ return JSON.stringify(test.input).substr(0, 80);
+ default:
+ return `Unknown type: ${test.type}`;
+ }
+ }
+
+ function _showTab(tab) {
+ document.getElementById('tabs').selectedItem = document.getElementById(`tab-${tab}`);
+ }
+
+ function _getActiveEditorName() {
+ let activeElement = document.activeElement;
+ if (activeElement && activeElement.id && activeElement.id.startsWith('editor-')) {
+ return activeElement.id.substring(7);
+ }
+ return null;
+ }
+};
+
+window.addEventListener("load", function (e) {
+ Scaffold.onLoad(e);
+}, false);
diff --git a/chrome/content/scaffold/scaffold.xul b/chrome/content/scaffold/scaffold.xul
index ecc7d6e450..457fdda201 100644
--- a/chrome/content/scaffold/scaffold.xul
+++ b/chrome/content/scaffold/scaffold.xul
@@ -1,58 +1,113 @@
+
+
-
+
+
+
- %globalDTD;
+ %textcontextDTD;
+ %standaloneDTD;
+ %editMenuOverlayDTD;
+ %brandDTD;
+ %zoteroDTD;
+ %scaffoldDTD;
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
- &scaffold.testing.description;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -247,14 +376,24 @@
+
-
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/chrome/content/scaffold/templates/newWeb.js b/chrome/content/scaffold/templates/newWeb.js
index 4fb4bc34e4..6007816f61 100644
--- a/chrome/content/scaffold/templates/newWeb.js
+++ b/chrome/content/scaffold/templates/newWeb.js
@@ -1,34 +1,34 @@
/*
- ***** BEGIN LICENSE BLOCK *****
+ ***** BEGIN LICENSE BLOCK *****
- Copyright © 2021 YOUR_NAME <- TODO
-
- This file is part of Zotero.
+ Copyright © 2022 YOUR_NAME <- TODO
- Zotero is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ This file is part of Zotero.
- Zotero is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- You should have received a copy of the GNU Affero General Public License
- along with Zotero. If not, see .
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
- ***** END LICENSE BLOCK *****
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
*/
-function detectWeb(doc, url) {
+async function detectWeb(doc, url) {
// TODO: adjust the logic here
- if (url.includes('/article/')) {
- return "newspaperArticle";
+ if ($$CURSOR$$url.includes('/article/')) {
+ return 'newspaperArticle';
}
else if (getSearchResults(doc, true)) {
- return "multiple";
+ return 'multiple';
}
return false;
}
@@ -51,13 +51,17 @@ function getSearchResults(doc, checkOnly) {
return found ? items : false;
}
-function doWeb(doc, url) {
- if (detectWeb(doc, url) == "multiple") {
- Zotero.selectItems(getSearchResults(doc, false), function (items) {
- if (items) ZU.processDocuments(Object.keys(items), scrape);
- });
+async function doWeb(doc, url) {
+ if (await detectWeb(doc, url) == 'multiple') {
+ let items = await Zotero.selectItems(getSearchResults(doc, false));
+ if (items) {
+ await Promise.all(
+ Object.keys(items)
+ .map(url => requestDocument(url).then(doc => scrape(doc, url)))
+ );
+ }
}
else {
- scrape(doc, url);
+ await scrape(doc, url);
}
}
diff --git a/chrome/content/scaffold/templates/scrapeBibTeX.js b/chrome/content/scaffold/templates/scrapeBibTeX.js
index 575c0b5303..3d99ddfa72 100644
--- a/chrome/content/scaffold/templates/scrapeBibTeX.js
+++ b/chrome/content/scaffold/templates/scrapeBibTeX.js
@@ -1,21 +1,20 @@
-function scrape(doc, url) {
+async function scrape(doc, url) {
// TODO adjust the url building
- var m = url.match(/FId=([\w\d]+)&/);
+ let m = url.match(/FId=([\w\d]+)&/);
if (m) {
// e.g. http://www.fachportal-paedagogik.de/fis_bildung/suche/fis_ausg.html?FId=A18196&lart=BibTeX&Speichern=Speichern&senden_an=+E-Mail-Adresse
- var bibUrl = "/fis_bildung/suche/fis_ausg.html?FId=" + m[1] + "&lart=BibTeX";
- ZU.doGet(bibUrl, function (text) {
- var translator = Zotero.loadTranslator("import");
- translator.setTranslator("9cb70025-a888-4a29-a210-93ec52da40d4");
- translator.setString(text);
- translator.setHandler("itemDone", function (obj, item) {
- item.attachments.push({
- title: "Snapshot",
- document: doc
- });
- item.complete();
+ let bibUrl = '/fis_bildung/suche/fis_ausg.html?FId=' + m[1] + '&lart=BibTeX';
+ let bibText = await requestText(bibUrl);
+ let translator = Zotero.loadTranslator("import");
+ translator.setTranslator('9cb70025-a888-4a29-a210-93ec52da40d4');
+ translator.setString(bibText);
+ translator.setHandler('itemDone', (_obj, item) => {
+ item.attachments.push({
+ title: 'Snapshot',
+ document: doc
});
- translator.translate();
+ item.complete();
});
+ await translator.translate();
}
}
diff --git a/chrome/content/scaffold/templates/scrapeEM.js b/chrome/content/scaffold/templates/scrapeEM.js
index e441dd5a3b..44dedb1082 100644
--- a/chrome/content/scaffold/templates/scrapeEM.js
+++ b/chrome/content/scaffold/templates/scrapeEM.js
@@ -1,21 +1,20 @@
-function scrape(doc, url) {
- var translator = Zotero.loadTranslator('web');
+async function scrape(doc, url) {
+ let translator = Zotero.loadTranslator('web');
// Embedded Metadata
translator.setTranslator('951c027d-74ac-47d4-a107-9c3069ab7b48');
- // translator.setDocument(doc);
+ translator.setDocument(doc);
- translator.setHandler('itemDone', function (obj, item) {
+ translator.setHandler('itemDone', (_obj, item) => {
// TODO adjust if needed:
- item.section = "News";
+ item.section = 'News';
item.complete();
});
- translator.getTranslatorObject(function (trans) {
- trans.itemType = "newspaperArticle";
- // TODO map additional meta tags here, or delete completely
- trans.addCustomFields({
- 'twitter:description': 'abstractNote'
- });
- trans.doWeb(doc, url);
+ let em = await translator.getTranslatorObject();
+ em.itemType = 'newspaperArticle';
+ // TODO map additional meta tags here, or delete completely
+ em.addCustomFields({
+ 'twitter:description': 'abstractNote'
});
+ await em.doWeb(doc, url);
}
diff --git a/chrome/content/scaffold/templates/scrapeMARC.js b/chrome/content/scaffold/templates/scrapeMARC.js
index 9b3a4f55d8..edcc6407dd 100644
--- a/chrome/content/scaffold/templates/scrapeMARC.js
+++ b/chrome/content/scaffold/templates/scrapeMARC.js
@@ -1,36 +1,35 @@
-function scrape(doc, url) {
+async function scrape(doc, url) {
// TODO adjust the selector for the lines here
- var lines = doc.querySelectorAll('#cntPlcPortal_grdMrc tr');
-
- // call MARC translator
- var translator = Zotero.loadTranslator("import");
- translator.setTranslator("a6ee60df-1ddc-4aae-bb25-45e0537be973");
- translator.getTranslatorObject(function (marc) {
- var record = new marc.record();
- var newItem = new Zotero.Item();
- // ignore the table headings in lines[0]
- record.leader = text(lines[1], 'td', 4);
- var fieldTag, indicators, fieldContent;
- for (let line of Array.from(lines).slice(2)) {
- // multiple lines with same fieldTag do not repeat the tag
- // i.e. in these cases we will just take same value as before
- if (text(line, 'td', 0).trim().length > 0) {
- fieldTag = text(line, 'td', 0);
- }
- indicators = text(line, 'td', 1) + text(line, 'td', 2);
- fieldContent = '';
- if (text(line, 'td', 3).trim().length > 0) {
- fieldContent = marc.subfieldDelimiter + text(line, 'td', 3);
- }
- fieldContent += text(line, 'td', 4);
-
- record.addField(fieldTag, indicators, fieldContent);
+ let lines = doc.querySelectorAll('table#marcData tr');
+
+ let translator = Zotero.loadTranslator('import');
+ translator.setTranslator('a6ee60df-1ddc-4aae-bb25-45e0537be973'); // MARC
+ let MARC = await translator.getTranslatorObject();
+
+ let record = new MARC.Record();
+ let item = new Zotero.Item();
+ // ignore the table headings in lines[0]
+ record.leader = text(lines[1], 'td', 4);
+ let fieldTag;
+ for (let line of Array.from(lines).slice(2)) {
+ // multiple lines with same fieldTag do not repeat the tag
+ // i.e. in these cases we will just take same value as before
+ if (text(line, 'td', 0)) {
+ fieldTag = text(line, 'td', 0);
}
-
- record.translate(newItem);
-
- // possibly clean newItem further here
-
- newItem.complete();
- });
+ let indicators = text(line, 'td', 1) + text(line, 'td', 2);
+ let fieldContent = '';
+ if (text(line, 'td', 3)) {
+ fieldContent = MARC.subfieldDelimiter + text(line, 'td', 3);
+ }
+ fieldContent += text(line, 'td', 4);
+
+ record.addField(fieldTag, indicators, fieldContent);
+ }
+
+ record.translate(item);
+
+ // possibly clean item further here
+
+ item.complete();
}
diff --git a/chrome/content/scaffold/templates/scrapeRIS.js b/chrome/content/scaffold/templates/scrapeRIS.js
index 46608844ec..cb480af1bc 100644
--- a/chrome/content/scaffold/templates/scrapeRIS.js
+++ b/chrome/content/scaffold/templates/scrapeRIS.js
@@ -1,31 +1,33 @@
-function scrape(doc, url) {
- var DOI = url.match(/\/(10\.[^#?]+)/)[1];
- // TODO adjust the url here
- var risURL = "http://citation-needed.services.springer.com/v2/references/" + DOI + "?format=refman&flavour=citation";
+async function scrape(doc, url) {
+ let DOI = url.match(/\/(10\.[^#?]+)/)[1];
+ // TODO adjust the URL here
+ let risURL = `http://citation-needed.services.springer.com/v2/references/${DOI}?format=refman&flavour=citation`;
// Z.debug(risURL)
- // TODO adjust the url here
- var pdfURL = doc.getElementById("articlePdf");
+ // TODO adjust this
+ let pdfLink = doc.querySelector('#articlePDF');
// Z.debug("pdfURL: " + pdfURL);
- ZU.doGet(risURL, function (text) {
- var translator = Zotero.loadTranslator("import");
- translator.setTranslator("32d59d2d-b65a-4da4-b0a3-bdd3cfb979e7");
- translator.setString(text);
- translator.setHandler("itemDone", function (obj, item) {
- // TODO tweak some of the output here
- if (pdfURL) {
- item.attachments.push({
- url: pdfURL.href,
- title: "Full Text PDF",
- mimeType: "application/pdf"
- });
- }
+
+ let risText = await requestText(risURL);
+ let translator = Zotero.loadTranslator('import');
+ translator.setTranslator('32d59d2d-b65a-4da4-b0a3-bdd3cfb979e7'); // RIS
+ translator.setString(risText);
+ translator.setHandler('itemDone', (_obj, item) => {
+ // TODO tweak some of the output here
+ if (pdfLink) {
item.attachments.push({
- title: "Snapshot",
- document: doc
+ url: pdfLink.href,
+ title: 'Full Text PDF',
+ mimeType: 'application/pdf'
});
- item.complete();
+ }
+
+ item.attachments.push({
+ title: 'Snapshot',
+ document: doc
});
- translator.translate();
+
+ item.complete();
});
+ await translator.translate();
}
diff --git a/chrome/content/scaffold/templates/shortcuts.txt b/chrome/content/scaffold/templates/shortcuts.txt
deleted file mode 100644
index d1b5e2858a..0000000000
--- a/chrome/content/scaffold/templates/shortcuts.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-Keyboard Shortcuts
-=============
-
-Ctrl/Cmd S : Save
-
-Ctrl/Cmd R : Run doWeb/doImport or selected tests
-Ctrl/Cmd T : Run detectWeb/detectImport
-
-Ctrl/Cmd + : Increase Font Size
-Ctrl/Cmd - : Decrease Font Size
-Ctrl/Cmd 0 : Restore Font Size
diff --git a/chrome/content/scaffold/translators.js b/chrome/content/scaffold/translators.js
index fcb7b31312..fc0003c123 100644
--- a/chrome/content/scaffold/translators.js
+++ b/chrome/content/scaffold/translators.js
@@ -7,16 +7,16 @@ var Scaffold_Translators = {
_provider: null,
_translators: new Map(),
_translatorFiles: new Map(),
+ _onLoadBeginListener: null,
+ _onLoadCompleteListener: null,
- load: Zotero.serial(async function (reload, filenames) {
+ load: Zotero.serial(async function (reload) {
if (this._translators.size && !reload) {
Zotero.debug("Scaffold: Translators already loaded");
- return;
- }
-
- if (filenames) {
-
+ return { numLoaded: 0, numDeleted: 0 };
}
+
+ if (this._onLoadBeginListener) this._onLoadBeginListener();
var t = new Date();
var dir = this.getDirectory();
@@ -77,6 +77,13 @@ var Scaffold_Translators = {
this._translators.delete(id);
}
}
+
+ if (this._onLoadCompleteListener) this._onLoadCompleteListener();
+
+ return {
+ numLoaded,
+ numDeleted: deletedTranslators.size
+ };
}),
deleteByID: async function (translatorID) {
@@ -93,6 +100,10 @@ var Scaffold_Translators = {
getDirectory: function () {
return Zotero.Prefs.get('scaffold.translatorsDir');
},
+
+ getModifiedTime: function (translatorID) {
+ return this._translators.get(translatorID)?.mtime;
+ },
getProvider: function () {
if (this._provider) {
@@ -139,5 +150,10 @@ var Scaffold_Translators = {
}.bind(this)
});
return this._provider;
+ },
+
+ setLoadListener({ onLoadBegin, onLoadComplete }) {
+ this._onLoadBeginListener = onLoadBegin;
+ this._onLoadCompleteListener = onLoadComplete;
}
-};
\ No newline at end of file
+};
diff --git a/chrome/locale/en-US/scaffold/scaffold.dtd b/chrome/locale/en-US/scaffold/scaffold.dtd
index f43d623383..7b1264d7e1 100644
--- a/chrome/locale/en-US/scaffold/scaffold.dtd
+++ b/chrome/locale/en-US/scaffold/scaffold.dtd
@@ -1,78 +1,83 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/skin/default/scaffold/checkSyntax.png b/chrome/skin/default/scaffold/checkSyntax.png
deleted file mode 100644
index a9925a06ab..0000000000
Binary files a/chrome/skin/default/scaffold/checkSyntax.png and /dev/null differ
diff --git a/chrome/skin/default/scaffold/detectImport.png b/chrome/skin/default/scaffold/detectImport.png
deleted file mode 100644
index d3670d196d..0000000000
Binary files a/chrome/skin/default/scaffold/detectImport.png and /dev/null differ
diff --git a/chrome/skin/default/scaffold/detectWeb.png b/chrome/skin/default/scaffold/detectWeb.png
index 564a1a9714..9d8b23d95b 100644
Binary files a/chrome/skin/default/scaffold/detectWeb.png and b/chrome/skin/default/scaffold/detectWeb.png differ
diff --git a/chrome/skin/default/scaffold/doImport.png b/chrome/skin/default/scaffold/doImport.png
deleted file mode 100644
index 8eee99e508..0000000000
Binary files a/chrome/skin/default/scaffold/doImport.png and /dev/null differ
diff --git a/chrome/skin/default/scaffold/new.png b/chrome/skin/default/scaffold/new.png
new file mode 100644
index 0000000000..75a6954797
Binary files /dev/null and b/chrome/skin/default/scaffold/new.png differ
diff --git a/chrome/skin/default/scaffold/reference.png b/chrome/skin/default/scaffold/reference.png
deleted file mode 100644
index 0905f933b8..0000000000
Binary files a/chrome/skin/default/scaffold/reference.png and /dev/null differ
diff --git a/chrome/skin/default/scaffold/scaffold.css b/chrome/skin/default/scaffold/scaffold.css
index 77466df941..ff47e96acd 100644
--- a/chrome/skin/default/scaffold/scaffold.css
+++ b/chrome/skin/default/scaffold/scaffold.css
@@ -1,74 +1,58 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");
-toolbar {
- height: 32px;
- padding-left: 6px;
-}
-
-.toolbarbutton-text {
- margin:0 2px;
-}
-.toolbarbutton-menu-dropmarker {
- margin-right:4px;
-}
-.toolbarbutton-text {
- margin:0;
-}
-.toolbarbutton-icon {
- height: 16px;
-}
-
-toolbarseparator {
- height: 24px;
+#tb-new {
+ list-style-image: url('chrome://scaffold/skin/new.png');
}
#tb-load {
list-style-image: url('chrome://scaffold/skin/load.png');
}
+
#tb-save {
list-style-image: url('chrome://scaffold/skin/save.png');
}
+
#tb-saveToZotero {
list-style-image: url('chrome://scaffold/skin/saveToZotero.png');
}
+
#tb-template {
list-style-image: url('chrome://scaffold/skin/add.png');
}
+
#tb-import {
list-style-image: url('chrome://scaffold/skin/import.png');
}
+
#tb-export {
list-style-image: url('chrome://scaffold/skin/export.png');
}
+
#tb-copy {
list-style-image: url('chrome://scaffold/skin/copy.png');
}
-#tb-checkSyntax {
- list-style-image: url('chrome://scaffold/skin/checkSyntax.png');
-}
-#tb-detectWeb {
+
+#tb-detect {
list-style-image: url('chrome://scaffold/skin/detectWeb.png');
}
-#tb-doWeb {
+
+#tb-do {
list-style-image: url('chrome://scaffold/skin/doWeb.png');
}
-#tb-detectImport {
- list-style-image: url('chrome://scaffold/skin/detectImport.png');
-}
-#tb-doImport {
- list-style-image: url('chrome://scaffold/skin/doImport.png');
-}
+
#tb-setTranslatorsDir {
list-style-image: url('chrome://scaffold/skin/setTranslatorsDir.png');
}
-#tb-reference {
- list-style-image: url('chrome://scaffold/skin/reference.png');
-}
+
#tb-fields {
list-style-image: url('chrome://scaffold/skin/fields.png');
}
+#tb-template {
+ list-style-image: url('chrome://scaffold/skin/add.png');
+}
+
#tabpanel-metadata textbox {
height:2em;
}
@@ -85,18 +69,44 @@ listbox {
min-width:200px;
}
-#editor-toolbar {
+#zotero-toolbar {
+ border-top: .5px solid darkgray;
margin-bottom: 3px;
}
-#editor-external-box {
- margin-top: 5px;
-}
-
-#editor-external-box label {
- margin-left: 1px;
+#zotero-toolbar toolbarseparator {
+ height: 18px;
}
browser {
background: white;
}
+
+vbox > splitter {
+ cursor: row-resize;
+}
+
+listbox[disabled], toolbarbutton[disabled] {
+ opacity: 0.6;
+ pointer-events: none;
+}
+
+dialog listbox > listitem[disabled] {
+ font-weight: 600;
+}
+
+#left-tabbox {
+ margin-top: 5px;
+}
+
+#checkboxes-translatorType checkbox {
+ margin-right: 10px;
+}
+
+#tabpanel-metadata label:first-child {
+ text-align: right;
+}
+
+.listcol-testMessage {
+ width: 200px;
+}
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
index cf72994e82..d33a629844 100644
--- a/defaults/preferences/zotero.js
+++ b/defaults/preferences/zotero.js
@@ -202,3 +202,6 @@ pref("extensions.zotero.retractions.recentItems", "[]");
pref("extensions.zotero.annotations.noteTemplates.title", "{{title}}
({{date}})
");
pref("extensions.zotero.annotations.noteTemplates.highlight", "{{highlight quotes='true'}} {{citation}} {{comment}}
");
pref("extensions.zotero.annotations.noteTemplates.note", "{{citation}} {{comment}}
");
+
+// Scaffold
+pref("extensions.zotero.scaffold.eslint.enabled", true);
diff --git a/package-lock.json b/package-lock.json
index 2a54ec79fa..44886694c2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"ace-builds": "^1.4.12",
"bluebird": "^3.5.1",
"classnames": "^2.2.6",
+ "monaco-editor": "^0.15.6",
"prop-types": "^15.8.0",
"react": "^17.0.2",
"react-autosuggest": "^10.1.0",
@@ -5164,6 +5165,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/monaco-editor": {
+ "version": "0.15.6",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.15.6.tgz",
+ "integrity": "sha512-JoU9V9k6KqT9R9Tiw1RTU8ohZ+Xnf9DMg6Ktqqw5hILumwmq7xqa/KLXw513uTUsWbhtnHoSJYYR++u3pkyxJg=="
+ },
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -11332,6 +11338,11 @@
"xtend": "^4.0.0"
}
},
+ "monaco-editor": {
+ "version": "0.15.6",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.15.6.tgz",
+ "integrity": "sha512-JoU9V9k6KqT9R9Tiw1RTU8ohZ+Xnf9DMg6Ktqqw5hILumwmq7xqa/KLXw513uTUsWbhtnHoSJYYR++u3pkyxJg=="
+ },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
diff --git a/package.json b/package.json
index a734f1e47e..6dcbf1426a 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"ace-builds": "^1.4.12",
"bluebird": "^3.5.1",
"classnames": "^2.2.6",
+ "monaco-editor": "^0.15.6",
"prop-types": "^15.8.0",
"react": "^17.0.2",
"react-autosuggest": "^10.1.0",
diff --git a/resource/schema/repotime.txt b/resource/schema/repotime.txt
index b8317a80c5..24503d0931 100644
--- a/resource/schema/repotime.txt
+++ b/resource/schema/repotime.txt
@@ -1 +1 @@
-2022-02-19 18:49:41
+2022-03-03 03:33:12
diff --git a/resource/vs b/resource/vs
new file mode 120000
index 0000000000..f8c8669726
--- /dev/null
+++ b/resource/vs
@@ -0,0 +1 @@
+../node_modules/monaco-editor/min/vs
\ No newline at end of file
diff --git a/scripts/config.js b/scripts/config.js
index 22ff9fcc68..41018bdf99 100644
--- a/scripts/config.js
+++ b/scripts/config.js
@@ -14,7 +14,8 @@ const symlinkDirs = [
'chrome/content/zotero/xpcom/rdf',
'chrome/content/zotero/xpcom/translate/src',
'styles',
- 'translators'
+ 'translators',
+ 'resource/vs'
];
// list of folders which are copied to the build folder
diff --git a/translators b/translators
index 827898b47a..9d3c8688ea 160000
--- a/translators
+++ b/translators
@@ -1 +1 @@
-Subproject commit 827898b47a297859d0613fcdf09231170d41fc18
+Subproject commit 9d3c8688ea34d5217d06e701b6dbfc971652c23e