More documentation and tests
diff --git a/dev/js/runner/datepicker.html b/dev/js/runner/datepicker.html
new file mode 100644
index 0000000..141d7c6
--- /dev/null
+++ b/dev/js/runner/datepicker.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Spec Runner for the DatePicker</title>
+ <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+ <script src="../lib/require.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+ <script src="../lib/jasmine-2.1.1/boot.js"></script>
+ <script>
+ require.config({
+ baseUrl: "../src",
+ paths: {
+ "lib" : "../lib",
+ "spec" : "../spec"
+ }
+ });
+ require([
+ 'lib/domReady!',
+ 'spec/datepickerSpec'
+ ],
+ function () {
+ if (jsApiReporter.finished === true)
+ jasmine.getEnv().execute();
+ });
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/spec/datepickerSpec.js b/dev/js/spec/datepickerSpec.js
new file mode 100644
index 0000000..bcab225
--- /dev/null
+++ b/dev/js/spec/datepickerSpec.js
@@ -0,0 +1,84 @@
+define(['datepicker'], function (dpClass) {
+ describe('KorAP.Datepicker', function () {
+
+ it('should be initializable', function () {
+ var dp = dpClass.create();
+ var e = dp.show();
+ expect(e.nodeName).toEqual('DIV');
+ expect(e.classList.contains('datepicker')).toBeTruthy();
+ expect(e.getAttribute('tabindex')).toEqual('0');
+ });
+
+ it('should generate valid dates', function () {
+ var dp = dpClass.create();
+ expect(dp.today()).toMatch("\\d{4}-[01\\d-[01]\\d");
+ });
+
+ it('should have year and month helpers', function () {
+ var dp = dpClass.create();
+ var e = dp.show(2013, 2);
+ expect(e.nodeName).toEqual('DIV');
+ expect(e.classList.contains('datepicker')).toBeTruthy();
+ expect(e.getAttribute('tabindex')).toEqual('0');
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2013');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+ });
+
+ it('should have modifyable year', function () {
+ var dp = dpClass.create();
+ var e = dp.show(2013, 2);
+ expect(e.nodeName).toEqual('DIV');
+ expect(e.classList.contains('datepicker')).toBeTruthy();
+ expect(e.getAttribute('tabindex')).toEqual('0');
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2013');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.incrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2014');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.incrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2015');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.decrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2014');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ // Max value
+ var e = dp.show(9998, 2);
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('9998');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.incrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('9999');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.incrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('9999');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ // Min value
+ var e = dp.show(2, 2);
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.decrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('1');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.decrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('0');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+ dp.decrYear();
+ expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('0');
+ expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+ });
+
+ it('should have modifyable month', function () {
+ var dp = dpClass.create();
+ var e = dp.show(2013, 6);
+ });
+ });
+});
diff --git a/dev/js/src/datepicker.js b/dev/js/src/datepicker.js
index 8608efe..9882364 100644
--- a/dev/js/src/datepicker.js
+++ b/dev/js/src/datepicker.js
@@ -39,6 +39,7 @@
return this;
},
+
/**
* Get or select a specific date.
*/
@@ -80,6 +81,7 @@
this._click = cb;
},
+
/**
* Show the datepicker.
* Will either show the selected year/month
@@ -111,6 +113,7 @@
return this._element;
},
+
/**
* Get the HTML element associated with the datepicker.
*/
@@ -118,6 +121,7 @@
return this._element;
},
+
/**
* Get the current date in string format.
*/
@@ -131,28 +135,37 @@
return str;
},
+
/**
* Increment the year.
*/
incrYear : function () {
- this._showYear++;
- this._updateYear();
- this._updateMonth();
- this._updateDay();
+ if (this._showYear < 9999) {
+ this._showYear++;
+ this._updateYear();
+ this._updateMonth();
+ this._updateDay();
+ return this;
+ };
return;
},
+
/**
* Decrement the year.
*/
decrYear : function () {
- this._showYear--;
- this._updateYear();
- this._updateMonth();
- this._updateDay();
+ if (this._showYear > 0) {
+ this._showYear--;
+ this._updateYear();
+ this._updateMonth();
+ this._updateDay();
+ return this;
+ };
return;
},
+
/**
* Increment the month.
*/
@@ -166,8 +179,10 @@
this._updateMonth();
this._updateDay();
};
+ return this;
},
+
/**
* Decrement the month.
*/
@@ -181,6 +196,8 @@
this._updateMonth();
this._updateDay();
};
+
+ return this;
},
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index a19a916..58558ff 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -18,7 +18,7 @@
# Add additional plugin path
push(@{$self->plugins->namespaces}, __PACKAGE__ . '::Plugin');
- # korap.ids-mannheim.de specific
+ # korap.ids-mannheim.de specific path prefixing
$self->hook(
before_dispatch => sub {
my $c = shift;
@@ -30,6 +30,7 @@
}) if $self->mode eq 'production';
# Cache static assets
+ # (not necessary, as long as shipped by nginx or Apache)
$self->hook(
after_static => sub {
my $res = shift->res;
@@ -40,9 +41,9 @@
# Set secrets for signed cookies
if (-e (my $secret = $self->home . '/kalamar.secret')) {
- $self->secrets([
- b($secret)->slurp->split("\n")
- ]);
+
+ # Load file and split lines for multiple secrets
+ $self->secrets([b($secret)->slurp->split("\n")]);
}
else {
$self->log->warn('Please create a kalamar.secret file');
@@ -55,9 +56,10 @@
'Notifications', # Client notifications
'Search', # Abstract Search framework
'CHI', # Global caching mechanism
+ 'MailException' # Alert via Email on exception
'TagHelpers::Pagination', # Pagination widget
'TagHelpers::MailToChiffre', # Obfuscate email addresses
- 'KalamarHelpers' # Specific Helpers for Kalamar
+ 'KalamarHelpers', # Specific Helpers for Kalamar
) {
$self->plugin($_);
};
@@ -72,10 +74,10 @@
# Establish routes
my $r = $self->routes;
- # Base query page
+ # Base query route
$r->get('/')->to('search#query')->name('index');
- # Documentation
+ # Documentation routes
$r->get('/doc')->to('documentation#page', page => 'korap')->name('doc_start');
$r->get('/doc/:page')->to('documentation#page', scope => undef);
$r->get('/doc/*scope/:page')->to('documentation#page')->name('doc');
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index 13af8c5..913abc2 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -66,11 +66,12 @@
};
-
# Get information about a match
+# Note: This is called 'match_info' as 'match' is reserved
sub match_info {
my $c = shift;
+ # Old API foundry/layer usage
my $foundry = '*';
my %query = (foundry => '*');
if ($c->param('foundry')) {
@@ -83,6 +84,7 @@
};
};
+ # Async
$c->render_later;
# Use the API for fetching matching information non-blocking
@@ -136,7 +138,8 @@
=head1 DESCRIPTION
L<Kalamar::Controller::Search> is the controller class for
-search related endpoints in Kalamar.
+search related endpoints in Kalamar. Actions are released when routes
+match.
=head1 METHODS
@@ -144,39 +147,85 @@
L<Kalamar::Controller::Search> inherits all methods from
L<Mojolicious::Controller> and implements the following new ones.
-=head2 search
+=head2 query
-Action for all documentation pages.
+ GET /?q=Baum&ql=poliqarp
+
+Action for all queries to the system. Returns C<HTML> only for the moment.
+
+The following parameters are supported.
+
=head3 q
-Query parameter
+The query string. This may any query written in a supported query language.
=head3 ql
-Query language
+The query language. This may be any query language supported by the system,
+written as the API expects the string.
=head3 action
-May be C<inspect>.
+May be C<inspect>. In that case, the serialized request is mirrored instead of
+processed.
+
+B<This switch is experimental and may change without warnings!>
+
=head3 snippet
+If set, the query is returned in the snippet view template.
+
+B<This parameter is experimental and may change without warnings!>
+
+
=head3 cutoff
+If set, the query will be cut off after the matches.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+
=head3 count
+If set, the query will be only return the given number of matches,
+in case the API supports it. Will fallback to the default number of matches defined
+by the API or the backend.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+
=head3 p
-Number of page
+If set, the query will page to the given number of pages in the result set.
+Will default to 1.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
=head2 match
-/:corpus_id/:doc_id/:text_id/:match_id
+ /:corpus_id/:doc_id/:text_id/:match_id?foundry=*
+
+Returns information to a match either as a C<JSON> or an C<HTML> document.
+The path defines the concrete match, by corpus identifier, document identifier,
+text identifier (all information as given by DeReKo), and match identifier
+(essentially the position of the match in the document, including highlight information).
+
+The following parameters are supported.
+
=head3 foundry
+Expects a foundry definition for retrieved information.
+If not given, returns all annotations for the match.
+If given, returns only given layer information for the defined foundry.
+
+B<This parameter is experimental and may change without warnings!>
+
+
=head3 layer
=head3 spans
@@ -201,13 +250,13 @@
Author: L<Nils Diewald|http://nils-diewald.de/>
Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
-Corpus Analysis Platform at the Institute for German Language
-(L<IDS|http://ids-mannheim.de/>),
-funded by the
+Corpus Analysis Platform at the
+L<Institute for the German Language (IDS)|http://ids-mannheim.de/>,
+member of the
L<Leibniz-Gemeinschaft|http://www.leibniz-gemeinschaft.de/en/about-us/leibniz-competition/projekte-2011/2011-funding-line-2/>
and supported by the L<KobRA|http://www.kobra.tu-dortmund.de> project,
-funded by the Federal Ministry of Education and Research
-(L<BMBF|http://www.bmbf.de/en/>).
+funded by the
+L<Federal Ministry of Education and Research (BMBF)|http://www.bmbf.de/en/>.
Kalamar is free software published under the
L<BSD-2 License|https://raw.githubusercontent.com/KorAP/Kalamar/master/LICENSE).