Tutorial queries can now be unavailable for certain corpora

Change-Id: Ib52cf6dfc2a020893dd6b252a3d1f5ddd53e936c
diff --git a/Changes b/Changes
index b76c4ee..cd3a383 100755
--- a/Changes
+++ b/Changes
@@ -1,3 +1,11 @@
+0.25 2018-01-09
+        - Make annotation helper configurable.
+        - Support multiple prefixes in menus.
+        - Show annotation tooltips in table views.
+        - Improve result display when JavaScript is disabled.
+        - Improve VC creation tool by supporting "blurring".
+        - Make tutorial queries localizable for different default corpora.
+
 0.24 2017-11-22
         - Added support for relation visualization.
         - Added support for FCSQL.
diff --git a/README.md b/README.md
index 1a39dbb..29120a5 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,7 @@
 
 ### Original Software
 
-Copyright (C) 2015-2017, [IDS Mannheim](http://www.ids-mannheim.de/)<br>
+Copyright (C) 2015-2018, [IDS Mannheim](http://www.ids-mannheim.de/)<br>
 Author: [Nils Diewald](http://nils-diewald.de/)
 Contributor: Susanne Feix (Translations)
 
diff --git a/dev/js/src/tutorial.js b/dev/js/src/tutorial.js
index aa18c34..fbb33ee 100644
--- a/dev/js/src/tutorial.js
+++ b/dev/js/src/tutorial.js
@@ -94,7 +94,7 @@
      * Decorate a page with query event handler.
      */
     initQueries : function (d) {
-      var qs = d.querySelectorAll('pre.query.tutorial');
+      var qs = d.querySelectorAll('pre.query.tutorial:not(.unsupported)');
       var that = this;
       for (var i = 0; i < qs.length; i++) {
 	      qs[i].onclick = function (e) {
diff --git a/dev/scss/main/query.scss b/dev/scss/main/query.scss
index ab45238..7f99696 100644
--- a/dev/scss/main/query.scss
+++ b/dev/scss/main/query.scss
@@ -4,7 +4,6 @@
 
 pre.query {
   overflow-x: hidden; 
-  cursor: pointer;
   padding: .5em;
   @include choose-item;
   position:relative;
@@ -14,11 +13,20 @@
     radius: $standard-border-radius;
   }
   box-shadow: $choose-box-shadow;
-  
-  &:hover {
-    @include choose-hover;
+  &:not(.unsupported) {
+    cursor: pointer;
+    &:hover {
+      @include choose-hover;
+    }
+    &:active {
+      @include choose-active;
+    }
   }
-  &:active {
-    @include choose-active;
+  &.unsupported span {
+    vertical-align: super;
+    color: $dark-blue;
+    font-size: 90%;
+    font-weight: bold;
+    cursor: help;
   }
 }
diff --git a/kalamar.dict b/kalamar.dict
index dee9e29..c3256e9 100644
--- a/kalamar.dict
+++ b/kalamar.dict
@@ -41,6 +41,7 @@
     activateJS => 'Für den vollen Funktionsumfang aktivieren Sie bitte JavaScript!',
     faq => 'Häufige Fragen',
     tutorial => 'Einführung',
+    notAvailInCorpus => 'Im vorliegenden Korpus nicht verfügbar',
     pubOn => 'veröffentlicht am',
     matchCount => 'Treffer',
     noMatches => 'Es wurden keine Treffer für <%== loc("searchjob") %> gefunden.',
@@ -120,6 +121,7 @@
     email => 'Email',
     userormail => 'Username or Email',
     with => 'with',
+    notAvailInCorpus => 'Not available in the current corpus',
     pubOn => 'published on',
     matchCount => '<%= quant($found, "match", "matches") %>',
     noMatches => 'There were no matches found for <%== loc("searchjob") %>.',
diff --git a/kalamar.queries.dict b/kalamar.queries.dict
index 0a3eacf..57e3dd8 100644
--- a/kalamar.queries.dict
+++ b/kalamar.queries.dict
@@ -1,3 +1,5 @@
+# To mark queries as 'not available in the specific corpus',
+# prepend the string '** ' to the query.
 {
   Q => {
     _ => sub { shift->config('Kalamar')->{'examplecorpus'} },
@@ -5,7 +7,8 @@
       poliqarp => {
         simple => 'Baum',
         simpleseq => 'der Baum',
-        simpleci => 'laufen/i'
+        simpleci => 'laufen/i',
+        complexpos => '[pos=ADJA]'
       }
     }
   }
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index b89b968..485c14a 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -7,14 +7,14 @@
 use File::Temp qw/tmpnam/;
 
 # Minor version - may be patched from package.json
-our $VERSION = '0.24';
+our $VERSION = '0.25';
 
 # TODO: Use CSRF!!!
 # TODO: The FAQ-Page has a contact form for new questions
 # TODO: Embed query serialization
 # TODO: Embed collection statistics
+# TODO: Show further meta data per click
 # TODO: Implement tab opener for matches and the tutorial
-# TODO: Make the tutorial ql sensitive
 # TODO: Implement a "projects" system
 
 # Start the application and register all routes and plugins
@@ -220,7 +220,7 @@
 
 =head2 COPYRIGHT AND LICENSE
 
-Copyright (C) 2015-2017, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Copyright (C) 2015-2018, L<IDS Mannheim|http://www.ids-mannheim.de/>
 Author: L<Nils Diewald|http://nils-diewald.de/>
 
 Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
diff --git a/lib/Kalamar/Plugin/KalamarHelpers.pm b/lib/Kalamar/Plugin/KalamarHelpers.pm
index 855a92b..4ef68a7 100644
--- a/lib/Kalamar/Plugin/KalamarHelpers.pm
+++ b/lib/Kalamar/Plugin/KalamarHelpers.pm
@@ -191,6 +191,18 @@
     doc_query => sub {
       my ($c, $ql, $q, %param) = @_;
 
+      # Query is not supported in the corpus
+      if ($q =~ s/^\*\*\s*//) {
+        # Escape query for html embedding
+        $q = xml_escape $q;
+
+        return b(
+          '<pre class="query tutorial unsupported">' .
+            "<code>$q</code>" .
+            '<span title="' . $c->loc('notAvailInCorpus') . '">*</span>' .
+            '</pre>');
+      };
+
       # Escape query for html embedding
       $q = xml_escape $q;
 
diff --git a/package.json b/package.json
index c3ecd87..db70bb7 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.24.1",
+  "version": "0.25.1",
   "repository" : {
     "type": "git",
     "url": "https://github.com/KorAP/Kalamar.git"
diff --git a/t/doc.t b/t/doc.t
index b16e1d9..55ac2a5 100644
--- a/t/doc.t
+++ b/t/doc.t
@@ -54,5 +54,36 @@
   ->status_is(200)
   ->text_is("title", "KorAP: Annis QL");
 
+# Check corpus examples
+$t->get_ok('/doc/ql/poliqarp-plus')
+  ->status_is(200)
+  ->text_is('#segments pre.query.tutorial:nth-of-type(1) code', 'Baum');
+
+my $app = $t->app;
+
+$app->plugin(
+  'Localize' => {
+    dict => {
+      Q => {
+        newexample => {
+          poliqarp => {
+            simple => '** Beispiel'
+          }
+        }
+      }
+    }
+  }
+);
+
+# Set other example query
+$app->config('Kalamar')->{examplecorpus} = 'newexample';
+
+is($app->loc('Q_poliqarp_simple'), '** Beispiel');
+
+# Check corpus examples
+$t->get_ok('/doc/ql/poliqarp-plus')
+  ->status_is(200)
+  ->text_is('#segments pre.query.tutorial:nth-of-type(1) code', 'Beispiel')
+  ->text_is('#segments pre.query.tutorial:nth-of-type(1) span', '*');
 
 done_testing();
diff --git a/templates/doc/ql.html.ep b/templates/doc/ql.html.ep
index 5094811..f93ce8b 100644
--- a/templates/doc/ql.html.ep
+++ b/templates/doc/ql.html.ep
@@ -15,39 +15,32 @@
 
 <section id="examples">
   <h3>Example Queries</h3>
-
-  %# Tests:
-  %# [is => 'json_pointer', 'result']
-  %# [ok => 'json_pointer']
-  %# [isnt => 'json_pointer', 'result']
-  %# [not_ok => 'json_pointer']
   
   <p><strong><%= doc_link_to 'Poliqarp', 'ql', 'poliqarp-plus' %></strong>: Find all occurrences of the lemma &quot;baum&quot; as annotated by the <%= doc_link_to 'default foundry', 'data', 'annotation' %>.</p>
-  %= doc_query poliqarp => '[base=Baum]', 'tests' => [[is => '/query', 'tokens:tt/l:Baum'],[is => '/request/query/wrap/layer', 'lemma'],[is => '/request/query/wrap/foundry', 'tt'], [ok => '/matches/10']]
+  %= doc_query poliqarp => '[base=Baum]'
 
   <p><strong><%= doc_link_to 'Poliqarp', 'ql', 'poliqarp-plus' %></strong>: Find all sequences of adjectives as annotated by Treetagger, that are repeated 3 to 5 times in a row.</p>
-  %= doc_query poliqarp => '[tt/p=ADJA]{3,5}', 'tests' => [[is => '/query', 'spanRepetition(tokens:tt/p:ADJA{3,5})'], [is => '/request/query/operation', 'operation:repetition'],[is => '/request/query/operands/0/wrap/foundry', 'tt'], [ok => '/matches/5']]
+  %= doc_query poliqarp => '[tt/p=ADJA]{3,5}'
 
   <p><strong><%= doc_link_to 'Cosmas-II', 'ql', 'cosmas-2' %></strong>: Find all occurrences of the words &quot;der&quot; and &quot;Baum&quot;, in case they are in a maximum distance of 5 tokens. The order is not relevant.</p>
-  %= doc_query cosmas2 => 'der /w5 Baum', 'tests' => [[is => '/query', 'shrink(129: spanDistance({129: tokens:s:der}, {129: tokens:s:Baum}, [(w[0:5], notOrdered, notExcluded)]))'], [is => '/request/query/operation', 'operation:focus'], [is => '/request/query/@type', 'korap:reference'],[is => '/request/query/operands/0/operands/1/operation', 'operation:class'], [is => '/itemsPerPage', 25], [ok => '/matches/20'], [is => '/matches/4/corpusID', 'WPD'], [is => '/matches/12/corpusID', 'WPD']]
+  %= doc_query cosmas2 => 'der /w5 Baum'
 
   <p><strong><%= doc_link_to 'Cosmas-II', 'ql', 'cosmas-2' %></strong>: Find all sequences of a word starting with a &quot;d&quot; (using a wildcard) followed by an adjective as annotated in the mate foundry, followed by the word &quot;Baum&quot; (ignore the case), that is in a sentence element annotated by the <%= doc_link_to 'default foundry', 'data', 'annotation' %>.</p>
   <p><em>Be aware</em>: Minor incompatibilities with implemented languages may be announced with warnings.</p>
-  %= doc_query cosmas2 => 'd* MORPH(mate/p=ADJA) $Baum #IN #ELEM(s)', 'tests' => [[ok => '/matches/3'], [is => '/query', 'shrink(130: {131: spanContain({129: <tokens:s />}, {130: spanNext(spanNext(SpanMultiTermQueryWrapper(tokens:s:d*), tokens:mate/p:ADJA), tokens:i:baum)})})'], [is => '/request/query/@type', 'korap:reference'], [is => '/request/query/operation', 'operation:focus'], [is => '/request/query/operands/0/operands/0/operation', 'operation:position'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operation', 'operation:sequence'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/0/wrap/type', 'type:wildcard'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/1/wrap/key', 'ADJA'], [is => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/1/wrap/foundry', 'mate'], [ok => '/request/query/operands/0/operands/0/operands/1/operands/0/operands/2/wrap/caseInsensitive'], [ok => '/matches/5']]
+  %= doc_query cosmas2 => 'd* MORPH(mate/p=ADJA) $Baum #IN #ELEM(s)'
 
   <p><strong><%= doc_link_to 'Poliqarp+', 'ql', 'poliqarp-plus' %></strong>: Find all nominal phrases as annotated using Connexor, that contain an adverb as annotated by OpenNLP, that is annotated as something starting with an &quot;A&quot; using regular expressions in Treetagger.</p>
-  %= doc_query poliqarp => 'contains(<cnx/c=np>,{[opennlp/p=ADV & tt/p="A.*"]})', cutoff => 1, 'tests' => [[is => '/query', 'spanContain(<tokens:cnx/c:np />, {1: spanSegment(tokens:opennlp/p:ADV, SpanMultiTermQueryWrapper(tokens:/tt/p:A.*/))})'], [is => '/request/query/operation', 'operation:position'], [is => '/request/query/frames/0', 'frames:contains'], [is => '/request/query/operands/0/foundry', 'cnx'], [is => '/request/query/operands/0/layer', 'c'], [is => '/request/query/operands/0/foundry', 'cnx'], [is => '/request/query/operands/0/key', 'np'], [is => '/request/query/operands/1/operands/0/wrap/operands/0/foundry', 'opennlp'], [is => '/request/query/operands/1/operands/0/wrap/operands/0/layer', 'p'],  [is => '/request/query/operands/1/operands/0/wrap/operands/1/foundry', 'tt'],  [is => '/request/query/operands/1/operands/0/wrap/operands/1/type', 'type:regex'], [is => '/request/query/operands/1/operands/0/wrap/operands/1/key', 'A.*'], [ok => '/matches/5']]
+  %= doc_query poliqarp => 'contains(<cnx/c=np>,{[opennlp/p=ADV & tt/p="A.*"]})', cutoff => 1
 
   <p><strong><%= doc_link_to 'Poliqarp+', 'ql', 'poliqarp-plus' %></strong>: Find all sentences as annotated by the base foundry that start with a sequence of one token in present tense as annotated by Connexor and the lemma &quot;die&quot; annotated by the <%= doc_link_to 'default foundry', 'data', 'annotation' %>. Highlight both terms of the sequence.</p>
-  %= doc_query poliqarp => 'startswith(<base/s=s>, {1:[cnx/m=PRES]}{2:[base=die]})', cutoff => 1, tests => [[is => '/query', 'spanStartsWith(<tokens:s />, spanNext({1: tokens:cnx/m:PRES}, {2: tokens:tt/l:die}))'], [is => '/request/meta/startPage', 1], [is => '/request/query/operation', 'operation:position'], [is => '/request/query/operands/0/@type','korap:span'], [is => '/request/query/operands/1/operands/0/operation', 'operation:class'], [is => '/request/query/operands/1/operands/1/operation', 'operation:class'], [is => '/request/query/operands/1/operands/1/operands/0/wrap/foundry', 'tt'], [ok => '/matches/4']]
+  %= doc_query poliqarp => 'startswith(<base/s=s>, {1:[cnx/m=PRES]}{2:[base=die]})', cutoff => 1
 
   <p><strong><%= doc_link_to 'Poliqarp+', 'ql', 'poliqarp-plus' %></strong>: Find all sequences of an article, followed by three to four adjectives and a noun as annotated by the Treetagger foundry, that finish a sentence. Highlight all parts of the sequence.</p>
-  %= doc_query poliqarp => 'focus(3:endswith(<base/s=s>,{3:[tt/p=ART]{1:{2:[tt/p=ADJA]{3,4}}[tt/p=NN]}}))', cutoff => 1, 'tests' => [[is => '/query', 'shrink(3: spanEndsWith(<tokens:s />, {3: spanNext(tokens:tt/p:ART, {1: spanNext({2: spanRepetition(tokens:tt/p:ADJA{3,4})}, tokens:tt/p:NN)})}))'], [is => '/request/query/operation', 'operation:focus'], [is => '/request/query/operands/0/frames/0', 'frames:endswith'], [ok => '/matches/3'], [is => '/matches/4/corpusID', 'WPD']]
+  %= doc_query poliqarp => 'focus(3:endswith(<base/s=s>,{3:[tt/p=ART]{1:{2:[tt/p=ADJA]{3,4}}[tt/p=NN]}}))', cutoff => 1
 
   <p><strong><%= doc_link_to 'Annis', 'ql', 'annis' %></strong>: Find all occurrences of the sequence of two tokens annotated as adverbs by the <%= doc_link_to 'default foundry', 'data', 'annotation' %>.</p>
-  %= doc_query annis => 'pos="ADV" & pos="ADV" & #1 . #2', 'tests' => [[is => '/query', 'spanNext(tokens:tt/p:ADV, tokens:tt/p:ADV)'], [is => '/request/query/operands/0/wrap/foundry', 'tt'], [is => '/request/query/operands/1/wrap/foundry', 'tt'], [ok => '/matches/5'], [ok => '/matches/15'], [is => '/matches/15/corpusID', 'WPD']]
+  %= doc_query annis => 'pos="ADV" & pos="ADV" & #1 . #2'
 
   <p><strong><%= doc_link_to 'CQL', 'ql', 'cql' %></strong>: Find all occurrences of the sequence &quot;der alte Mann&quot;.</p>
-  %= doc_query cql => '"der alte Mann"', 'tests' => [[is => '/query', 'spanNext(spanNext(tokens:s:der, tokens:s:alte), tokens:s:Mann)'], [is => '/request/query/operation', 'operation:sequence'],[is => '/request/query/operands/0/wrap/key', 'der'],[is => '/request/query/operands/1/wrap/key', 'alte'],[is => '/request/query/operands/2/wrap/key', 'Mann'], [ok => '/matches/5'], [ok => '/matches/5']]
-	
+  %= doc_query cql => '"der alte Mann"'	
 </section>
diff --git a/templates/doc/ql/poliqarp-plus.html.ep b/templates/doc/ql/poliqarp-plus.html.ep
index 68b079d..7535627 100644
--- a/templates/doc/ql/poliqarp-plus.html.ep
+++ b/templates/doc/ql/poliqarp-plus.html.ep
@@ -77,7 +77,7 @@
   <p>The third special key is <code>pos</code>, refering to the part-of-speech annotation of the <%= doc_link_to 'default foundry', 'data', 'annotation' %>.
     The following query finds all attributive adjectives:</p>
 
-  %= doc_query poliqarp => '[pos=ADJA]'
+  %= doc_query poliqarp => loc('Q_poliqarp_complexpos', '[pos=ADJA]')
 
   <p>Complex segments requesting further token annotations can have keys following the <code>foundry/layer</code> notation.
     For example to find all occurrences of plural words in the <code>mate</code> foundry, you can search using the following query:</p>