Merge "Improve note regarding opt-out"
diff --git a/Changes b/Changes
index 8d88e0a..a0c1aca 100755
--- a/Changes
+++ b/Changes
@@ -1,6 +1,9 @@
-0.32 2018-12-19
+0.32 2019-01-29
- Support attachements in metadata fields (#77).
- Added ping request option to Piwik.
+ - Fix handling of login errors.
+ - Added Statistics reload option (hebasta, #66).
+ - Fixed VC query serialization.
0.31 2018-11-30
- Update to Mojolicious >= 8.06.
diff --git a/dev/demo/vcdemo.js b/dev/demo/vcdemo.js
index 0c66dd3..b9a7b1a 100644
--- a/dev/demo/vcdemo.js
+++ b/dev/demo/vcdemo.js
@@ -122,32 +122,36 @@
// Create a new virtual collection by passing a based json object and
// field menu information
- var vc = vcClass.create([
+ KorAP.vc = vcClass.create([
['title', 'string'],
['subTitle', 'string'],
['pubDate', 'date'],
['author', 'text']
]).fromJson(json);
- document.getElementById('vc-view').appendChild(vc.element());
+ document.addEventListener('vcChange', function (e) {
+ KorAP.vc.checkStatActive(e.detail);
+ }, false);
+
+ document.getElementById('vc-view').appendChild(KorAP.vc.element());
- vc.open();
+ KorAP.vc.open();
// show the current JSON serialization
KorAP.showJSON = function () {
var json = document.getElementById("json");
- json.innerHTML = JSON.stringify(vc.root().toJson(), null, ' ');
+ json.innerHTML = JSON.stringify(KorAP.vc.root().toJson(), null, ' ');
hljs.highlightBlock(json);
};
// show the current query serialization
KorAP.showQuery = function () {
- document.getElementById("query").innerHTML = vc.root().toQuery();
+ document.getElementById("query").innerHTML = KorAP.vc.root().toQuery();
};
// make the current vc persistant
KorAP.makeVCPersistant = function () {
- vc.makePersistant();
+ KorAP.vc.makePersistant();
};
//get the corpus statistic (demo function)
diff --git a/dev/js/spec/statSpec.js b/dev/js/spec/statSpec.js
index cb9d713..25e55a2 100644
--- a/dev/js/spec/statSpec.js
+++ b/dev/js/spec/statSpec.js
@@ -7,7 +7,7 @@
define(['vc', 'vc/statistic', 'view/corpstatv'], function(vcClass, statClass, corpStatVClass){
-
+
var json = {
"@type":"koral:docGroup",
"operation":"operation:or",
@@ -77,8 +77,118 @@
return cb(preDefinedStat);
};
-
- describe('KorAP.CorpusStat', function(){
+
+ generateCorpusDocGr = function(){
+ let vc = vcClass.create().fromJson({
+ "@type" : 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'title',
+ 'match': 'match:eq',
+ 'value' : 'Hello World!'
+ },
+ {
+ '@type' : 'koral:doc',
+ 'match': 'match:eq',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ });
+ return vc;
+ }
+
+ /**
+ * Generate corpus doc group with more entries
+ */
+ generateBCorpusDocGr = function(){
+ let vc = vcClass.create().fromJson({
+ "@type": "koral:docGroup",
+ "operation": "operation:or",
+ "operands": [{
+ "@type" : 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'title',
+ 'match': 'match:eq',
+ 'value' : 'Hello World!'
+ },
+ {
+ '@type' : 'koral:doc',
+ 'match': 'match:eq',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ },
+ {
+ '@type' : 'koral:doc',
+ 'match': 'match:eq',
+ 'key' : 'author',
+ 'value' : 'Goethe'
+ }
+ ]
+ });
+
+ return vc;
+ }
+
+ generateCorpusDoc = function(){
+ let vc= vcClass.create().fromJson({
+ '@type' : 'koral:doc',
+ 'key' : 'title',
+ 'match': 'match:eq',
+ 'value' : 'Hello World!',
+ 'type' : 'type:string'
+ });
+ return vc;
+ };
+
+
+ /**
+ * Generate vc with docgroupref
+ */
+ generateCorpusRef = function(){
+ let vc = vcClass.create().fromJson({
+ "@type" : "koral:docGroupRef",
+ "ref" : "@max/myCorpus"
+ });
+ return vc;
+ };
+
+
+ /**
+ * Checks is corpus statistic is active
+ */
+ checkStatActive = function(view, div){
+ // corpus statistic exists, it is active and reloadStatButton is shown
+ if ((view.firstChild.classList.contains("stattable") === true)
+ && (view.firstChild.classList.contains("stdisabled") === false)
+ && (div.getElementsByClassName("reloadStatB").length == 0) ) {
+ return true;
+ }
+ return false;
+ };
+
+
+ /**
+ * Checks if corpus statistic is disabled
+ */
+ checkStatDisabled = function(view, div){
+ if ((view.firstChild.classList.contains("stattable") === true)
+ && (view.firstChild.classList.contains("stdisabled") === true)
+ && (div.getElementsByClassName("reloadStatB").length === 1) ) {
+ return true;
+ }
+ return false;
+ };
+
+
+ describe('KorAP.CorpusStat', function(){
it('should be initiable', function(){
var stat = statClass.create(preDefinedStat);
@@ -88,7 +198,7 @@
it('should be parsed in a statistic view and displayed as HTML Description List', function(){
var stat = statClass.create(preDefinedStat);
- var descL = stat.element();
+ var descL = stat.element();
expect(descL.tagName).toEqual('DL');
expect(descL.children[0].tagName).toEqual('DIV');
expect(descL.children[0].children[0].tagName).toEqual('DT');
@@ -113,12 +223,14 @@
['author', 'text']
]).fromJson(json);
+ KorAP.vc = vc;
+
statView = corpStatVClass.create(vc);
- //corpStatVClass.show(vc);
+ // corpStatVClass.show(vc);
var testDiv = document.createElement('div');
testDiv.appendChild(statView.show());
- //statClass.showCorpStat(testDiv, vc);
+ // statClass.showCorpStat(testDiv, vc);
expect(testDiv.children[0].tagName).toEqual('DIV');
expect(testDiv.children[0].getAttribute("class")).toEqual('stattable');
@@ -232,5 +344,217 @@
panel.lastChild.children[0].click();
expect(panel.firstChild.children.length).toEqual(1);
});
- });
+
+
+ });
+
+
+
+ /**
+ * Test disabling and reload of corpus statistic if vc is changed
+ * in vc builder through user-interaction
+ */
+ describe ('KorAP.CorpusStat.Disable', function(){
+
+ document.addEventListener('vcChange', function (e) {
+ if(KorAP.vc){
+ KorAP.vc.checkStatActive(e.detail);
+ }
+ }, false);
+
+ /**
+ * If the user defines a new vc, the statistic should be disabled,
+ * because it is out-of-date.
+ *
+ * The user can choose to display an up-to-date corpus statistic. Here it is tested
+ * if corpus statistic is disabled after a valid change of corpus statistic and if the corpus statistic is updatable.
+ */
+ it ('should disable the corpus statistic if corpus definition is changed and display a functional reload button', function(){
+
+ KorAP.vc = generateCorpusDocGr();
+
+ //Show corpus statistic
+ let show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ let panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ let view = panel.firstChild.firstChild;
+
+ //corpus statistic is active
+ expect(checkStatActive(view, show)).toBe(true);
+
+ //change vc, a line in vc builder is deleted
+ KorAP._delete.apply(KorAP.vc.root().getOperand(0));
+ expect(checkStatDisabled(view,show)).toBe(true);
+
+ //click at reload button
+ let rlbutton = show.getElementsByClassName("refresh").item(0);
+ rlbutton.click();
+
+ expect(checkStatActive(view,show)).toBe(true);
+ });
+
+
+ it('should disable corpus statistic if entries in vc builder are deleted', function(){
+ KorAP.vc = generateCorpusDocGr();
+
+ // create corpus builder and corpus statistic;
+ let show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ let panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ let view = panel.firstChild.firstChild;
+
+ expect(checkStatActive(view, show)).toBe(true);
+
+ //delete foo=bar
+ KorAP._delete.apply(KorAP.vc.root().getOperand(1));
+ expect(checkStatDisabled(view, show)).toBe(true);
+
+ //refresh corpus statistic
+ let rlbutton = show.getElementsByClassName("refresh").item(0);
+ rlbutton.click();
+ expect(checkStatActive(view,show)).toBe(true);
+
+ KorAP._delete.apply(KorAP.vc.root());
+ view = panel.firstChild.firstChild;
+ expect(checkStatDisabled(view, show)).toBe(true);
+
+ KorAP.vc = generateBCorpusDocGr();
+ // create corpus builder and corpus statistic;
+ show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ view = panel.firstChild.firstChild;
+
+ expect(checkStatActive(view, show)).toBe(true);
+ KorAP._delete.apply(KorAP.vc.root().getOperand(1));
+ view = panel.firstChild.firstChild;
+ expect(checkStatDisabled(view, show)).toBe(true);
+
+ });
+
+
+ it('should disable corpus statistic if key, matchoperator or value is changed', function(){
+ /*
+ * Doc change of key, match operator and value
+ */
+ KorAP.vc= generateCorpusDoc();
+ // show vc builder and open corpus statistic
+ let show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ let panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ let view = panel.firstChild.firstChild;
+ expect(checkStatActive(view, show)).toBe(true);
+
+ KorAP.vc.root().matchop("ne").update();
+ expect(checkStatDisabled(view, show)).toBe(true);
+
+ let rlbutton = show.getElementsByClassName("refresh").item(0);
+ rlbutton.click();
+
+ view = panel.firstChild.firstChild;
+ expect(checkStatActive(view, show)).toBe(true);
+ KorAP.vc.root().value("Hello tester").update();
+ expect(checkStatDisabled(view, show)).toBe(true);
+
+ //refresh corpus statistic
+ rlbutton = show.getElementsByClassName("refresh").item(0);
+ rlbutton.click();
+ view = panel.firstChild.firstChild;
+ expect(checkStatActive(view, show)).toBe(true);
+
+ KorAP.vc.root().key("author").update();
+ expect(checkStatDisabled(view, show)).toBe(true);
+
+
+ /*
+ * DocGroupRef change of value...
+ */
+ KorAP.vc = generateCorpusRef();
+ show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ view = panel.firstChild.firstChild;
+ expect(checkStatActive(view, show)).toBe(true);
+
+ KorAP.vc.root().ref("@anton/secondCorpus").update();
+ expect(checkStatDisabled(view, show)).toBe(true);
+ });
+
+
+ it('should not disable corpus statistic if docgroup definition is incomplete', function(){
+
+ KorAP.vc = generateCorpusDocGr();
+
+ //Show corpus statistic
+ let show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ let panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ let view = panel.firstChild.firstChild;
+
+ expect(checkStatActive(view, show)).toBe(true);
+
+ KorAP._and.apply(KorAP.vc.root());
+
+ let andbuild = show.getElementsByClassName("builder");
+ expect(andbuild[0].firstChild.classList.contains('docGroup')).toBeTruthy();
+ expect(andbuild[0].firstChild.getAttribute("data-operation")).toEqual("and");
+ expect(checkStatActive(view, show)).toBe(true);
+ });
+
+
+ it('should not disable corpus statistic if doc/docref definition is incomplete', function(){
+
+ /*
+ * DOC incomplete
+ */
+ KorAP.vc = vcClass.create().fromJson();
+ expect(KorAP.vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
+
+ // show vc builder and open corpus statistic
+ let show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ let panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ let view = panel.firstChild.firstChild;
+
+ // corpus statistic should be shown and be up-to-date, reload button is not shown
+ expect(checkStatActive(view, show)).toBe(true);
+
+ // open the menu
+ KorAP.vc.builder().firstChild.firstChild.click();
+ KorAP._vcKeyMenu._prefix.add("author");
+ let prefElement = KorAP.vc.builder().querySelector('span.pref');
+ // add key 'author' to VC
+ prefElement.click();
+
+ expect(checkStatActive(view, show)).toBe(true);
+
+
+ /*
+ * DOCREF incomplete
+ */
+ KorAP.vc = vcClass.create().fromJson();
+ expect(KorAP.vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
+
+ // show vc builder and open corpus statistic
+ show = document.createElement('div');
+ show.appendChild(KorAP.vc.element());
+ panel = show.firstChild.lastChild.firstChild;
+ panel.lastChild.children[0].click();
+ view = panel.firstChild.firstChild;
+ expect(checkStatActive(view, show)).toBe(true);
+
+ KorAP.vc.builder().firstChild.firstChild.click();
+ KorAP._vcKeyMenu._prefix.add("referTo");
+ prefElement = KorAP.vc.builder().querySelector('span.pref');
+ prefElement.click();
+ expect(checkStatActive(view, show)).toBe(true);
+ });
+ });
});
diff --git a/dev/js/spec/vcSpec.js b/dev/js/spec/vcSpec.js
index f657ee9..e7cc4fb 100644
--- a/dev/js/spec/vcSpec.js
+++ b/dev/js/spec/vcSpec.js
@@ -27,7 +27,7 @@
KorAP._vcKeyMenu = undefined;
-
+
// Helper method for building factories
buildFactory = function (objClass, defaults) {
return {
@@ -146,6 +146,7 @@
expect(doc.key()).toBeUndefined();
expect(doc.value()).toBeUndefined();
expect(doc.type()).toEqual("string");
+ expect(doc.incomplete()).toBeTruthy();
});
it('should be definable', function () {
@@ -161,6 +162,7 @@
expect(doc.key()).toEqual("title");
expect(doc.type()).toEqual("string");
expect(doc.value()).toEqual("Der alte Mann");
+ expect(doc.incomplete()).toBeFalsy();
});
@@ -173,6 +175,7 @@
expect(doc.key()).toEqual("author");
expect(doc.type()).toEqual("string");
expect(doc.value()).toEqual("Max Birkendale");
+ expect(doc.incomplete()).toBeFalsy();
// No valid string
doc = stringFactory.create({
@@ -195,6 +198,7 @@
expect(doc.key()).toEqual("author");
expect(doc.type()).toEqual("string");
expect(doc.value()).toEqual("Max Birkendale");
+ expect(doc.incomplete()).toBeFalsy();
// Invalid match type
doc = stringFactory.create({
@@ -216,6 +220,7 @@
});
expect(doc.matchop()).toEqual('ne');
expect(doc.rewrites()).toBeUndefined();
+ expect(doc.incomplete()).toBeFalsy();
// Invalid matcher
doc = regexFactory.create({
@@ -345,6 +350,12 @@
doc = stringFactory.create();
expect(doc.toQuery()).toEqual('author = "Max Birkendale"');
+ // Check for incompletion
+ expect(doc.incomplete()).toBeFalsy();
+ doc.value("");
+ expect(doc.incomplete()).toBeTruthy();
+ expect(doc.toQuery()).toEqual('');
+
// Serialize string with quotes
doc = stringFactory.create({ "value" : 'Max "Der Coole" Birkendate'});
expect(doc.toQuery()).toEqual('author = "Max \\"Der Coole\\" Birkendate"');
@@ -570,6 +581,17 @@
'(pubDate since 2014-05-12 & ' +
'pubDate until 2014-12-05 & foo != /[a]?bar/)'
);
+
+
+ // Check for incompletion and only serialize complete operands
+ expect(docGroup.incomplete()).toBeFalsy();
+ var op1 = docGroup.getOperand(0);
+ op1.value("");
+ expect(docGroup.incomplete()).toBeFalsy();
+ expect(docGroup.toQuery()).toEqual(
+ '(pubDate since 2014-05-12 & ' +
+ 'pubDate until 2014-12-05 & foo != /[a]?bar/)'
+ );
});
});
@@ -625,6 +647,12 @@
expect(vcRef.toQuery()).toEqual(
"referTo \"@peter/myCorpus2\""
);
+
+ // Check for incompletion and only serialize complete operands
+ expect(vcRef.incomplete()).toBeFalsy();
+ vcRef.ref("");
+ expect(vcRef.incomplete()).toBeTruthy();
+ expect(vcRef.toQuery()).toEqual("");
});
});
@@ -637,6 +665,7 @@
expect(docElement.firstChild.firstChild.data).toEqual(KorAP.Locale.EMPTY);
expect(docElement.lastChild.lastChild.data).toEqual(KorAP.Locale.EMPTY);
expect(doc.toQuery()).toEqual('');
+ expect(doc.incomplete()).toBeTruthy();
// Only removable
expect(docElement.lastChild.children.length).toEqual(0);
@@ -731,7 +760,9 @@
});
it('should be replaceable on root', function () {
+
var vc = vcClass.create();
+ KorAP.vc = vc;
expect(vc.toQuery()).toEqual("");
expect(vc.root().ldType()).toEqual("non");
@@ -756,6 +787,8 @@
var vc = vcClass.create([
["pubDate", "date"]
]);
+ KorAP.vc = vc;
+
expect(vc.toQuery()).toEqual("");
expect(vc.builder().firstChild.textContent).toEqual(KorAP.Locale.EMPTY);
expect(vc.builder().firstChild.classList.contains('unspecified')).toEqual(true);
@@ -805,6 +838,7 @@
"value":"Baum",
"match":"match:eq"
});
+ KorAP.vc = vc;
expect(vc.toQuery()).toEqual("Titel = \"Baum\"");
var vcE = vc.builder();
@@ -837,6 +871,7 @@
"match":"match:eq"
});
+ KorAP.vc = vc;
expect(vc.toQuery()).toEqual("Titel = \"Baum\"");
var vcE = vc.builder();
@@ -870,6 +905,7 @@
"match":"match:eq"
});
+ KorAP.vc = vc;
expect(vc.toQuery()).toEqual("Titel = \"Baum\"");
var vcE = vc.builder();
@@ -911,6 +947,7 @@
]
});
+ KorAP.vc = vc;
expect(vc.toQuery()).toEqual("author = \"Max Birkendale\" & pubDate in 2014-12-05");
var vcE = vc.builder();
@@ -2936,29 +2973,32 @@
it('should be clickable', function () {
- var vc = vcClass.create([
+ KorAP.vc = vcClass.create([
['a', null],
['b', null],
['c', null]
]).fromJson();
- expect(vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
+
+ //vc = KorAP.vc;
+
+ expect(KorAP.vc.builder().firstChild.classList.contains('unspecified')).toBeTruthy();
// This should open up the menu
- vc.builder().firstChild.firstChild.click();
- expect(vc.builder().firstChild.firstChild.tagName).toEqual('UL');
+ KorAP.vc.builder().firstChild.firstChild.click();
+ expect(KorAP.vc.builder().firstChild.firstChild.tagName).toEqual('UL');
KorAP._vcKeyMenu._prefix.clear();
KorAP._vcKeyMenu._prefix.add('x');
- var prefElement = vc.builder().querySelector('span.pref');
+ var prefElement = KorAP.vc.builder().querySelector('span.pref');
expect(prefElement.innerText).toEqual('x');
// This should add key 'x' to VC
prefElement.click();
- expect(vc.builder().firstChild.classList.contains('doc')).toBeTruthy();
- expect(vc.builder().firstChild.firstChild.className).toEqual('key');
- expect(vc.builder().firstChild.firstChild.innerText).toEqual('x');
+ expect(KorAP.vc.builder().firstChild.classList.contains('doc')).toBeTruthy();
+ expect(KorAP.vc.builder().firstChild.firstChild.className).toEqual('key');
+ expect(KorAP.vc.builder().firstChild.firstChild.innerText).toEqual('x');
});
});
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index 34a838a..d94b1b5 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -90,6 +90,10 @@
var input = d.getElementById('collection');
var vc = KorAP.vc;
+
+ document.addEventListener('vcChange', function (e) {
+ vc.checkStatActive(e.detail);
+ }, false);
// Add vc name object
if (input) {
diff --git a/dev/js/src/panel/vcpanel.js b/dev/js/src/panel/vcpanel.js
index 98a9b4b..e12c20c 100644
--- a/dev/js/src/panel/vcpanel.js
+++ b/dev/js/src/panel/vcpanel.js
@@ -43,10 +43,19 @@
if (this.statView === undefined || !this.statView.shown()) {
this.statView = corpStatVClass.create(this.vc, this);
this.add(this.statView);
+ this.vc.oldvcQuery = KorAP.vc.toQuery();
}
},
+ /**
+ * Reload corpus statistic
+ *
+ */
+ reloadCorpStat: function(){
+ this.statView.close();
+ this.addCorpStat();
+ }
}
});
diff --git a/dev/js/src/vc.js b/dev/js/src/vc.js
index 711008f..4bc5e13 100644
--- a/dev/js/src/vc.js
+++ b/dev/js/src/vc.js
@@ -81,6 +81,8 @@
// KorAP._validDateMatchRE is defined in datepicker.js!
const loc = KorAP.Locale;
+ loc.SHOW_STAT = loc.SHOW_STAT || 'Statistics';
+ loc.VERB_SHOWSTAT = loc.VERB_SHOWSTAT || 'Corpus Statistics';
loc.VC_allCorpora = loc.VC_allCorpora || 'all corpora';
loc.VC_oneCollection = loc.VC_oneCollection || 'a virtual corpus';
loc.MINIMIZE = loc.MINIMIZE || 'Minimize';
@@ -240,13 +242,15 @@
/**
- * Clean the virtual document to uspecified doc.
+ * Clean the virtual document to unspecified doc.
*/
clean : function() {
if (this._root.ldType() !== "non") {
this._root.destroy();
this.root(unspecDocClass.create(this));
};
+ //update for graying corpus statistic by deleting the first line of the vc builder
+ this.update();
return this;
},
@@ -360,13 +364,14 @@
/**
* Update the whole object based on the underlying data structure
- */
+ */
update : function() {
this._root.update();
+ var vcchevent = new CustomEvent('vcChange', {'detail':this});
+ document.dispatchEvent(vcchevent);
+
return this;
},
-
-
/**
* Make the vc persistant by injecting the current timestamp as a
* creation date limit criterion.
@@ -478,8 +483,17 @@
//Create panel
this.panel = vcPanelClass.create(this);
dv.appendChild(this.panel.element());
+
},
-
+ /**
+ * Checks if corpus statistic has to be disabled,
+ * and to be updated after clicking at the "reload-button"
+ */
+ checkStatActive : function(){
+ if(this.panel !== undefined && this.panel.statView !==undefined){
+ this.panel.statView.checkStatActive();
+ }
+ }
};
});
diff --git a/dev/js/src/vc/doc.js b/dev/js/src/vc/doc.js
index 996c347..132c4a8 100644
--- a/dev/js/src/vc/doc.js
+++ b/dev/js/src/vc/doc.js
@@ -149,6 +149,13 @@
e.appendChild(op.element());
};
+ if(KorAP.vc){
+ //Replaced through Event
+ //KorAP.vc.checkGrayingStat(this);
+ var vcchevent = new CustomEvent('vcChange', {'detail':this});
+ document.dispatchEvent(vcchevent);
+ }
+
return e;
},
@@ -616,9 +623,12 @@
};
},
+ incomplete : function () {
+ return !(this.matchop() && this.key() && this.value());
+ },
toQuery : function () {
- if (!this.matchop() || !this.key())
+ if (this.incomplete())
return "";
// Build doc string based on key
diff --git a/dev/js/src/vc/docgroup.js b/dev/js/src/vc/docgroup.js
index 7ce2fed..858e847 100644
--- a/dev/js/src/vc/docgroup.js
+++ b/dev/js/src/vc/docgroup.js
@@ -210,6 +210,9 @@
group.appendChild(op.element());
+ var vcchevent = new CustomEvent('vcChange', {'detail':this});
+ document.dispatchEvent(vcchevent);
+
return this;
},
@@ -356,7 +359,7 @@
toQuery : function (brackets) {
var list = this._operands
.filter(function (op) {
- return op.ldType() !== 'non';
+ return !op.incomplete();
})
.map(function (op) {
return (op.ldType() === 'docGroup') ?
diff --git a/dev/js/src/vc/docgroupref.js b/dev/js/src/vc/docgroupref.js
index 7d3ecea..f72e27a 100644
--- a/dev/js/src/vc/docgroupref.js
+++ b/dev/js/src/vc/docgroupref.js
@@ -109,7 +109,10 @@
// Append new operators
e.appendChild(op.element());
};
-
+
+ var vcchevent = new CustomEvent('vcChange', {'detail':this});
+ document.dispatchEvent(vcchevent);
+
return this.element();
},
@@ -207,7 +210,6 @@
return this;
},
-
/**
* Click on the unspecified object
*/
@@ -261,9 +263,13 @@
};
},
+
+ incomplete : function () {
+ return this.ref() ? false : true
+ },
toQuery : function () {
- if (!this.ref())
+ if (this.incomplete())
return "";
// Build doc string based on key
diff --git a/dev/js/src/vc/jsonld.js b/dev/js/src/vc/jsonld.js
index 600a5bb..21260d4 100644
--- a/dev/js/src/vc/jsonld.js
+++ b/dev/js/src/vc/jsonld.js
@@ -99,6 +99,10 @@
return null;
},
+ incomplete : function () {
+ return false;
+ },
+
toQuery : function () {
return '';
}
diff --git a/dev/js/src/vc/unspecified.js b/dev/js/src/vc/unspecified.js
index 6ffa0e3..416ad85 100644
--- a/dev/js/src/vc/unspecified.js
+++ b/dev/js/src/vc/unspecified.js
@@ -121,6 +121,11 @@
return this._element;
},
+
+ incomplete : function () {
+ return true;
+ },
+
/**
* Click on the unspecified object
*/
diff --git a/dev/js/src/view/corpstatv.js b/dev/js/src/view/corpstatv.js
index 1cc6f0c..fa9a135 100644
--- a/dev/js/src/view/corpstatv.js
+++ b/dev/js/src/view/corpstatv.js
@@ -3,18 +3,20 @@
*
* @author Helge Stallkamp
*/
+
define([ 'view', 'vc/statistic' ], function(viewClass, statClass) {
const d = document;
return {
- create : function(vc) {
+ create : function(vc, panel) {
return Object.create(viewClass)._init([ 'vcstatistic' ]).upgradeTo(this)
- ._init(vc);
+ ._init(vc, panel);
},
- _init : function(vc) {
+ _init : function(vc, panel) {
this.vc = vc;
+ this.panel = panel;
return this;
},
@@ -71,9 +73,10 @@
* Show corpus statistic view
*/
show : function() {
+
if (this._show)
- return this._show;
-
+ return this._show;
+
var statTable = document.createElement('div');
statTable.classList.add('stattable', 'loading');
@@ -91,13 +94,71 @@
statisticobj = statClass.create(statistic);
statTable.appendChild(statisticobj.element());
+
});
this._show = statTable;
return statTable;
},
+
+
+ /**
+ * Checks if statistic has to be disabled
+ */
+ checkStatActive : function (){
+ var newString = KorAP.vc.toQuery();
+ var oldString = this.vc.oldvcQuery;
+ /*
+ * Do ignore surrounding round brackets
+ * Definining an incomplete docGroup in the vc builder:
+ * (foo = bar and author = Goethe) and ...
+ * leads to
+ * vc.toQuery() -> (foo = bar and author=Goethe)
+ */
+
+ if(newString || newString === ''){
+ if(newString.startsWith('(')){
+ newString = newString.slice(1, newString.length-1);
+ }
+
+ if(newString != oldString) {
+ this.disableStat();
+ }
+ }
+
+ },
+
+ /**
+ * Disabling corpus statistic if in vc builder a different vc is choosen.
+ * After clicking at the reload-button the up-to-date corpus statistic is displayed.
+ */
+
+ disableStat : function(){
+ var statt = this._show;
+
+ if(statt.getElementsByClassName('reloadStatB').length == 0){
+ var reloadspan = document.createElement('div');
+ reloadspan.classList.add('reloadStatB');
+ reloadspan.classList.add('button-group');
+ reloadspan.classList.add('button-panel');
+ reloadb = reloadspan.addE('span');
+ reloadb.classList.add('refresh');
+
+ var that = this;
+ reloadb.addEventListener("click", function (e){
+ statt.classList.remove('stdisabled');
+ that.panel.reloadCorpStat();
+ });
+
+
+ statt.appendChild(reloadspan);
+ statt.classList.add('stdisabled');
+ }
+ },
+
+
/**
* Close the view.
*/
diff --git a/dev/scss/header/statistics.scss b/dev/scss/header/statistics.scss
index a4d3f1e..3952e66 100644
--- a/dev/scss/header/statistics.scss
+++ b/dev/scss/header/statistics.scss
@@ -1,31 +1,65 @@
@charset "utf-8";
@import "../util";
+
+/*
+Corpus statistic
+Graying corpus statistic
+*/
div.stattable {
- display: flex;
- flex-direction: row;
+ display: flex;
+ flex-direction: row;
+}
+div.stattable {
+ > dl {
+ display: flex;
+ flex-flow: row wrap;
+ margin-top:4px;
+ margin-bottom:4px;
+ padding-bottom: 1px;
+ > div {
+ border-color: $dark-green;
+ > dt {
+ background-color: $middle-green;
+ width: 15em;
+ margin: 0;
+ &:after {
+ content: ":";
+ }
+ }
+ > dd {
+ background-color: $lightest-green;
+ color: $dark-grey;
+ }
+ }
+ }
+ &.stdisabled {
+ > dl
+ > div {
+ > dt {
+ @include vcinfo-inactive;
+ }
+ > dd {
+ @include vcinfo-inactive;
+ }
+ }
+ }
}
-div.stattable > dl {
- display: flex;
- flex-flow: row wrap;
- //margin-top: 4 * $border-size !important;
- margin-top:4px;
- margin-bottom:4px;
- padding-bottom: 1px;
- > div {
- border-color: $dark-green;
- > dt {
- background-color: $middle-green;
- width: 15em;
- margin: 0;
- &:after {
- content: ":";
- }
+
+/* Corpus statistic reload button */
+div.reloadStatB {
+ font-family: 'FontAwesome';
+ padding-left: 2px;
+ z-index: 30;
}
- > dd {
- background-color: $lightest-green;
- color: $dark-grey;
- }
- }
-}
+span.refresh::after{
+ line-height: normal;
+ content : $fa-redo;
+ font-size: 15pt;
+ }
+
+/* Close-button should always be seen next to or above reload-button */
+.button-view.vcstatistic {
+ z-index: 30;
+ }
diff --git a/dev/scss/util.scss b/dev/scss/util.scss
index bb82ca0..90e20b1 100644
--- a/dev/scss/util.scss
+++ b/dev/scss/util.scss
@@ -38,6 +38,7 @@
$middle-green: lighten($ids-green-1, 9%);
$light-green: lighten($ids-green-1, 13%); // #7ba400;
$lightest-green: #d8e38c; // lighten($ids-green-1, 26%);
+$grey-green: #bcc387;
/**
* Blue Colors
@@ -50,10 +51,10 @@
* Grey Colors
*/
$middle-grey: $ids-grey-1; // #999;
+$semilight-grey: #8d8d8d;
$light-grey: $ids-grey-2; // #ddd;
$dark-grey: darken($middle-grey, 15%);
$nearly-white: #fefefe;
-
/**
* Red Colors (no IDS relation)
*/
@@ -234,7 +235,15 @@
appearance:none;
}
-
+/**
+* Mixin for the appearance of inactive elements in the vcinfo panel
+*/
+@mixin vcinfo-inactive{
+ background-color: $grey-green;
+ color: $semilight-grey;
+ border-color: $semilight-grey;
+ text-shadow: none;
+}
/**
* Font Awesome symbol table
*/
@@ -267,4 +276,5 @@
$fa-to-query: "\f102";
$fa-cut: "\f0c4";
$fa-plugin: "\f1e6";
-$fa-referto: "\f0c5";
\ No newline at end of file
+$fa-referto: "\f0c5";
+$fa-redo: "\f01e";
diff --git a/lib/Kalamar/Plugin/Auth.pm b/lib/Kalamar/Plugin/Auth.pm
index 7693b45..640cc8b 100644
--- a/lib/Kalamar/Plugin/Auth.pm
+++ b/lib/Kalamar/Plugin/Auth.pm
@@ -173,9 +173,16 @@
# There is an error here
# Dealing with errors here
- if (my $error = $jwt->{error}) {
+ if (my $error = $jwt->{error} // $jwt->{errors}) {
if (ref $error eq 'ARRAY') {
- $c->notify(error => $c->dumper($_));
+ foreach (@$error) {
+ unless ($_->[1]) {
+ $c->notify(error => $c->loc('Auth_loginFail'));
+ }
+ else {
+ $c->notify(error => $_->[0] . ($_->[1] ? ': ' . $_->[1] : ''));
+ };
+ };
}
else {
$c->notify(error => 'There is an unknown JWT error');
diff --git a/package.json b/package.json
index 542e3b3..df09c86 100755
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "Kalamar",
"description": "Mojolicious-based Frontend for KorAP",
"license": "BSD-2-Clause",
- "version": "0.31.1",
+ "version": "0.32.1",
"pluginVersion": "0.1",
"repository" : {
"type": "git",
diff --git a/t/plugin/auth.t b/t/plugin/auth.t
index 9828baf..b2089a1 100644
--- a/t/plugin/auth.t
+++ b/t/plugin/auth.t
@@ -58,6 +58,7 @@
$t->get_ok('/')
->status_is(200)
->element_exists('div.notify-error')
+ ->text_is('div.notify-error', 'Bad CSRF token')
->element_exists('input[name=handle_or_email][value=test]')
->element_exists_not('div.button.top a')
;
@@ -76,6 +77,42 @@
$t->post_ok('/user/login' => form => {
handle_or_email => 'test',
+ pwd => 'ldaperr',
+ csrf_token => $csrf
+})
+ ->status_is(302)
+ ->content_is('')
+ ->header_is('Location' => '/');
+
+$csrf = $t->get_ok('/')
+ ->status_is(200)
+ ->element_exists('div.notify-error')
+ ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
+ ->element_exists('input[name=handle_or_email][value=test]')
+ ->element_exists_not('div.button.top a')
+ ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
+ ;
+
+$t->post_ok('/user/login' => form => {
+ handle_or_email => 'test',
+ pwd => 'unknown',
+ csrf_token => $csrf
+})
+ ->status_is(302)
+ ->content_is('')
+ ->header_is('Location' => '/');
+
+$csrf = $t->get_ok('/')
+ ->status_is(200)
+ ->element_exists('div.notify-error')
+ ->text_is('div.notify-error', 'Access denied')
+ ->element_exists('input[name=handle_or_email][value=test]')
+ ->element_exists_not('div.button.top a')
+ ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
+ ;
+
+$t->post_ok('/user/login' => form => {
+ handle_or_email => 'test',
pwd => 'pass',
csrf_token => $csrf
})
diff --git a/t/server/mock.pl b/t/server/mock.pl
index 18fd05d..79eeb90 100644
--- a/t/server/mock.pl
+++ b/t/server/mock.pl
@@ -229,6 +229,16 @@
token_type => 'api_token'
})
);
+ }
+
+ elsif ($pwd eq 'ldaperr') {
+ return $c->render(
+ format => 'html',
+ status => 401,
+ json => {
+ "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
+ }
+ );
};
return $c->render(