Improved tutorials
diff --git a/Changes b/Changes
index b18dc88..d57e08b 100755
--- a/Changes
+++ b/Changes
@@ -1,5 +1,6 @@
-0.14 2015-04-02
+0.14 2015-04-15
- Redesign of JS and Sass assets
+ - Introduced RequireJS
0.13 2015-03-10
- Project name is now "Kalamar"
diff --git a/Gruntfile.js b/Gruntfile.js
index fa98b1a..bec3fba 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -98,7 +98,11 @@
},
watch: {
css: {
- files: ['dev/scss/{util,fonts,base,header,searchbar,matchinfo,resultinfo,kwic,menu,hint,pagination,logos,alertify,vc,media,kalamar,tutorial,query,sidebar,footer}.scss'],
+ files: ['dev/scss/{util,base,fonts,kalamar,media}.scss',
+ 'dev/scss/footer/footer.scss',
+ 'dev/scss/header/{header,hint,menu,searchbar,vc}.scss',
+ 'dev/scss/main/{alertify,highlight,kwic,logos,main,matchinfo,pagination,query,resultinfo,sidebar,tutorial}.scss'
+ ],
tasks: ['sass'],
options: {
spawn: false
diff --git a/dev/js/lib/alertify.js b/dev/js/lib/alertify.js
new file mode 100644
index 0000000..d422123
--- /dev/null
+++ b/dev/js/lib/alertify.js
@@ -0,0 +1,626 @@
+/*global define*/
+(function (global, undefined) {
+ "use strict";
+
+ var document = global.document,
+ Alertify;
+
+ Alertify = function () {
+
+ var _alertify = {},
+ dialogs = {},
+ isopen = false,
+ keys = { ENTER: 13, ESC: 27, SPACE: 32 },
+ queue = [],
+ $, btnCancel, btnOK, btnReset, btnResetBack, btnFocus, elCallee, elCover, elDialog, elLog, form, input, getTransitionEvent;
+
+ /**
+ * Markup pieces
+ * @type {Object}
+ */
+ dialogs = {
+ buttons : {
+ holder : "<nav class=\"alertify-buttons\">{{buttons}}</nav>",
+ submit : "<button type=\"submit\" class=\"alertify-button alertify-button-ok\" id=\"alertify-ok\">{{ok}}</button>",
+ ok : "<button class=\"alertify-button alertify-button-ok\" id=\"alertify-ok\">{{ok}}</button>",
+ cancel : "<button class=\"alertify-button alertify-button-cancel\" id=\"alertify-cancel\">{{cancel}}</button>"
+ },
+ input : "<div class=\"alertify-text-wrapper\"><input type=\"text\" class=\"alertify-text\" id=\"alertify-text\"></div>",
+ message : "<p class=\"alertify-message\">{{message}}</p>",
+ log : "<article class=\"alertify-log{{class}}\">{{message}}</article>"
+ };
+
+ /**
+ * Return the proper transitionend event
+ * @return {String} Transition type string
+ */
+ getTransitionEvent = function () {
+ var t,
+ type,
+ supported = false,
+ el = document.createElement("fakeelement"),
+ transitions = {
+ "WebkitTransition" : "webkitTransitionEnd",
+ "MozTransition" : "transitionend",
+ "OTransition" : "otransitionend",
+ "transition" : "transitionend"
+ };
+
+ for (t in transitions) {
+ if (el.style[t] !== undefined) {
+ type = transitions[t];
+ supported = true;
+ break;
+ }
+ }
+
+ return {
+ type : type,
+ supported : supported
+ };
+ };
+
+ /**
+ * Shorthand for document.getElementById()
+ *
+ * @param {String} id A specific element ID
+ * @return {Object} HTML element
+ */
+ $ = function (id) {
+ return document.getElementById(id);
+ };
+
+ /**
+ * Alertify private object
+ * @type {Object}
+ */
+ _alertify = {
+
+ /**
+ * Labels object
+ * @type {Object}
+ */
+ labels : {
+ ok : "OK",
+ cancel : "Cancel"
+ },
+
+ /**
+ * Delay number
+ * @type {Number}
+ */
+ delay : 5000,
+
+ /**
+ * Whether buttons are reversed (default is secondary/primary)
+ * @type {Boolean}
+ */
+ buttonReverse : false,
+
+ /**
+ * Which button should be focused by default
+ * @type {String} "ok" (default), "cancel", or "none"
+ */
+ buttonFocus : "ok",
+
+ /**
+ * Set the transition event on load
+ * @type {[type]}
+ */
+ transition : undefined,
+
+ /**
+ * Set the proper button click events
+ *
+ * @param {Function} fn [Optional] Callback function
+ *
+ * @return {undefined}
+ */
+ addListeners : function (fn) {
+ var hasOK = (typeof btnOK !== "undefined"),
+ hasCancel = (typeof btnCancel !== "undefined"),
+ hasInput = (typeof input !== "undefined"),
+ val = "",
+ self = this,
+ ok, cancel, common, key, reset;
+
+ // ok event handler
+ ok = function (event) {
+ if (typeof event.preventDefault !== "undefined") event.preventDefault();
+ common(event);
+ if (typeof input !== "undefined") val = input.value;
+ if (typeof fn === "function") {
+ if (typeof input !== "undefined") {
+ fn(true, val);
+ }
+ else fn(true);
+ }
+ return false;
+ };
+
+ // cancel event handler
+ cancel = function (event) {
+ if (typeof event.preventDefault !== "undefined") event.preventDefault();
+ common(event);
+ if (typeof fn === "function") fn(false);
+ return false;
+ };
+
+ // common event handler (keyup, ok and cancel)
+ common = function (event) {
+ self.hide();
+ self.unbind(document.body, "keyup", key);
+ self.unbind(btnReset, "focus", reset);
+ if (hasOK) self.unbind(btnOK, "click", ok);
+ if (hasCancel) self.unbind(btnCancel, "click", cancel);
+ };
+
+ // keyup handler
+ key = function (event) {
+ var keyCode = event.keyCode;
+ if ((keyCode === keys.SPACE && !hasInput) || (hasInput && keyCode === keys.ENTER)) ok(event);
+ if (keyCode === keys.ESC && hasCancel) cancel(event);
+ };
+
+ // reset focus to first item in the dialog
+ reset = function (event) {
+ if (hasInput) input.focus();
+ else if (!hasCancel || self.buttonReverse) btnOK.focus();
+ else btnCancel.focus();
+ };
+
+ // handle reset focus link
+ // this ensures that the keyboard focus does not
+ // ever leave the dialog box until an action has
+ // been taken
+ this.bind(btnReset, "focus", reset);
+ this.bind(btnResetBack, "focus", reset);
+ // handle OK click
+ if (hasOK) this.bind(btnOK, "click", ok);
+ // handle Cancel click
+ if (hasCancel) this.bind(btnCancel, "click", cancel);
+ // listen for keys, Cancel => ESC
+ this.bind(document.body, "keyup", key);
+ if (!this.transition.supported) {
+ this.setFocus();
+ }
+ },
+
+ /**
+ * Bind events to elements
+ *
+ * @param {Object} el HTML Object
+ * @param {Event} event Event to attach to element
+ * @param {Function} fn Callback function
+ *
+ * @return {undefined}
+ */
+ bind : function (el, event, fn) {
+ if (typeof el.addEventListener === "function") {
+ el.addEventListener(event, fn, false);
+ } else if (el.attachEvent) {
+ el.attachEvent("on" + event, fn);
+ }
+ },
+
+ /**
+ * Use alertify as the global error handler (using window.onerror)
+ *
+ * @return {boolean} success
+ */
+ handleErrors : function () {
+ if (typeof global.onerror !== "undefined") {
+ var self = this;
+ global.onerror = function (msg, url, line) {
+ self.error("[" + msg + " on line " + line + " of " + url + "]", 0);
+ };
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Append button HTML strings
+ *
+ * @param {String} secondary The secondary button HTML string
+ * @param {String} primary The primary button HTML string
+ *
+ * @return {String} The appended button HTML strings
+ */
+ appendButtons : function (secondary, primary) {
+ return this.buttonReverse ? primary + secondary : secondary + primary;
+ },
+
+ /**
+ * Build the proper message box
+ *
+ * @param {Object} item Current object in the queue
+ *
+ * @return {String} An HTML string of the message box
+ */
+ build : function (item) {
+ var html = "",
+ type = item.type,
+ message = item.message,
+ css = item.cssClass || "";
+
+ html += "<div class=\"alertify-dialog\">";
+ html += "<a id=\"alertify-resetFocusBack\" class=\"alertify-resetFocus\" href=\"#\">Reset Focus</a>";
+
+ if (_alertify.buttonFocus === "none") html += "<a href=\"#\" id=\"alertify-noneFocus\" class=\"alertify-hidden\"></a>";
+
+ // doens't require an actual form
+ if (type === "prompt") html += "<div id=\"alertify-form\">";
+
+ html += "<article class=\"alertify-inner\">";
+ html += dialogs.message.replace("{{message}}", message);
+
+ if (type === "prompt") html += dialogs.input;
+
+ html += dialogs.buttons.holder;
+ html += "</article>";
+
+ if (type === "prompt") html += "</div>";
+
+ html += "<a id=\"alertify-resetFocus\" class=\"alertify-resetFocus\" href=\"#\">Reset Focus</a>";
+ html += "</div>";
+
+ switch (type) {
+ case "confirm":
+ html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.ok));
+ html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel);
+ break;
+ case "prompt":
+ html = html.replace("{{buttons}}", this.appendButtons(dialogs.buttons.cancel, dialogs.buttons.submit));
+ html = html.replace("{{ok}}", this.labels.ok).replace("{{cancel}}", this.labels.cancel);
+ break;
+ case "alert":
+ html = html.replace("{{buttons}}", dialogs.buttons.ok);
+ html = html.replace("{{ok}}", this.labels.ok);
+ break;
+ default:
+ break;
+ }
+
+ elDialog.className = "alertify alertify-" + type + " " + css;
+ elCover.className = "alertify-cover";
+ return html;
+ },
+
+ /**
+ * Close the log messages
+ *
+ * @param {Object} elem HTML Element of log message to close
+ * @param {Number} wait [optional] Time (in ms) to wait before automatically hiding the message, if 0 never hide
+ *
+ * @return {undefined}
+ */
+ close : function (elem, wait) {
+ // Unary Plus: +"2" === 2
+ var timer = (wait && !isNaN(wait)) ? +wait : this.delay,
+ self = this,
+ hideElement, transitionDone;
+
+ // set click event on log messages
+ this.bind(elem, "click", function () {
+ hideElement(elem);
+ });
+ // Hide the dialog box after transition
+ // This ensure it doens't block any element from being clicked
+ transitionDone = function (event) {
+ event.stopPropagation();
+ // unbind event so function only gets called once
+ self.unbind(this, self.transition.type, transitionDone);
+ // remove log message
+ elLog.removeChild(this);
+ if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden";
+ };
+ // this sets the hide class to transition out
+ // or removes the child if css transitions aren't supported
+ hideElement = function (el) {
+ // ensure element exists
+ if (typeof el !== "undefined" && el.parentNode === elLog) {
+ // whether CSS transition exists
+ if (self.transition.supported) {
+ self.bind(el, self.transition.type, transitionDone);
+ el.className += " alertify-log-hide";
+ } else {
+ elLog.removeChild(el);
+ if (!elLog.hasChildNodes()) elLog.className += " alertify-logs-hidden";
+ }
+ }
+ };
+ // never close (until click) if wait is set to 0
+ if (wait === 0) return;
+ // set timeout to auto close the log message
+ setTimeout(function () { hideElement(elem); }, timer);
+ },
+
+ /**
+ * Create a dialog box
+ *
+ * @param {String} message The message passed from the callee
+ * @param {String} type Type of dialog to create
+ * @param {Function} fn [Optional] Callback function
+ * @param {String} placeholder [Optional] Default value for prompt input field
+ * @param {String} cssClass [Optional] Class(es) to append to dialog box
+ *
+ * @return {Object}
+ */
+ dialog : function (message, type, fn, placeholder, cssClass) {
+ // set the current active element
+ // this allows the keyboard focus to be resetted
+ // after the dialog box is closed
+ elCallee = document.activeElement;
+ // check to ensure the alertify dialog element
+ // has been successfully created
+ var check = function () {
+ if ((elLog && elLog.scrollTop !== null) && (elCover && elCover.scrollTop !== null)) return;
+ else check();
+ };
+ // error catching
+ if (typeof message !== "string") throw new Error("message must be a string");
+ if (typeof type !== "string") throw new Error("type must be a string");
+ if (typeof fn !== "undefined" && typeof fn !== "function") throw new Error("fn must be a function");
+ // initialize alertify if it hasn't already been done
+ this.init();
+ check();
+
+ queue.push({ type: type, message: message, callback: fn, placeholder: placeholder, cssClass: cssClass });
+ if (!isopen) this.setup();
+
+ return this;
+ },
+
+ /**
+ * Extend the log method to create custom methods
+ *
+ * @param {String} type Custom method name
+ *
+ * @return {Function}
+ */
+ extend : function (type) {
+ if (typeof type !== "string") throw new Error("extend method must have exactly one paramter");
+ return function (message, wait) {
+ this.log(message, type, wait);
+ return this;
+ };
+ },
+
+ /**
+ * Hide the dialog and rest to defaults
+ *
+ * @return {undefined}
+ */
+ hide : function () {
+ var transitionDone,
+ self = this;
+ // remove reference from queue
+ queue.splice(0,1);
+ // if items remaining in the queue
+ if (queue.length > 0) this.setup(true);
+ else {
+ isopen = false;
+ // Hide the dialog box after transition
+ // This ensure it doens't block any element from being clicked
+ transitionDone = function (event) {
+ event.stopPropagation();
+ // unbind event so function only gets called once
+ self.unbind(elDialog, self.transition.type, transitionDone);
+ };
+ // whether CSS transition exists
+ if (this.transition.supported) {
+ this.bind(elDialog, this.transition.type, transitionDone);
+ elDialog.className = "alertify alertify-hide alertify-hidden";
+ } else {
+ elDialog.className = "alertify alertify-hide alertify-hidden alertify-isHidden";
+ }
+ elCover.className = "alertify-cover alertify-cover-hidden";
+ // set focus to the last element or body
+ // after the dialog is closed
+ elCallee.focus();
+ }
+ },
+
+ /**
+ * Initialize Alertify
+ * Create the 2 main elements
+ *
+ * @return {undefined}
+ */
+ init : function () {
+ // ensure legacy browsers support html5 tags
+ document.createElement("nav");
+ document.createElement("article");
+ document.createElement("section");
+ // cover
+ if ($("alertify-cover") == null) {
+ elCover = document.createElement("div");
+ elCover.setAttribute("id", "alertify-cover");
+ elCover.className = "alertify-cover alertify-cover-hidden";
+ document.body.appendChild(elCover);
+ }
+ // main element
+ if ($("alertify") == null) {
+ isopen = false;
+ queue = [];
+ elDialog = document.createElement("section");
+ elDialog.setAttribute("id", "alertify");
+ elDialog.className = "alertify alertify-hidden";
+ document.body.appendChild(elDialog);
+ }
+ // log element
+ if ($("alertify-logs") == null) {
+ elLog = document.createElement("section");
+ elLog.setAttribute("id", "alertify-logs");
+ elLog.className = "alertify-logs alertify-logs-hidden";
+ document.body.appendChild(elLog);
+ }
+ // set tabindex attribute on body element
+ // this allows script to give it focus
+ // after the dialog is closed
+ document.body.setAttribute("tabindex", "0");
+ // set transition type
+ this.transition = getTransitionEvent();
+ },
+
+ /**
+ * Show a new log message box
+ *
+ * @param {String} message The message passed from the callee
+ * @param {String} type [Optional] Optional type of log message
+ * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding the log
+ *
+ * @return {Object}
+ */
+ log : function (message, type, wait) {
+ // check to ensure the alertify dialog element
+ // has been successfully created
+ var check = function () {
+ if (elLog && elLog.scrollTop !== null) return;
+ else check();
+ };
+ // initialize alertify if it hasn't already been done
+ this.init();
+ check();
+
+ elLog.className = "alertify-logs";
+ this.notify(message, type, wait);
+ return this;
+ },
+
+ /**
+ * Add new log message
+ * If a type is passed, a class name "alertify-log-{type}" will get added.
+ * This allows for custom look and feel for various types of notifications.
+ *
+ * @param {String} message The message passed from the callee
+ * @param {String} type [Optional] Type of log message
+ * @param {Number} wait [Optional] Time (in ms) to wait before auto-hiding
+ *
+ * @return {undefined}
+ */
+ notify : function (message, type, wait) {
+ var log = document.createElement("article");
+ log.className = "alertify-log" + ((typeof type === "string" && type !== "") ? " alertify-log-" + type : "");
+ log.innerHTML = message;
+ // append child
+ elLog.appendChild(log);
+ // triggers the CSS animation
+ setTimeout(function() { log.className = log.className + " alertify-log-show"; }, 50);
+ this.close(log, wait);
+ },
+
+ /**
+ * Set properties
+ *
+ * @param {Object} args Passing parameters
+ *
+ * @return {undefined}
+ */
+ set : function (args) {
+ var k;
+ // error catching
+ if (typeof args !== "object" && args instanceof Array) throw new Error("args must be an object");
+ // set parameters
+ for (k in args) {
+ if (args.hasOwnProperty(k)) {
+ this[k] = args[k];
+ }
+ }
+ },
+
+ /**
+ * Common place to set focus to proper element
+ *
+ * @return {undefined}
+ */
+ setFocus : function () {
+ if (input) {
+ input.focus();
+ input.select();
+ }
+ else btnFocus.focus();
+ },
+
+ /**
+ * Initiate all the required pieces for the dialog box
+ *
+ * @return {undefined}
+ */
+ setup : function (fromQueue) {
+ var item = queue[0],
+ self = this,
+ transitionDone;
+
+ // dialog is open
+ isopen = true;
+ // Set button focus after transition
+ transitionDone = function (event) {
+ event.stopPropagation();
+ self.setFocus();
+ // unbind event so function only gets called once
+ self.unbind(elDialog, self.transition.type, transitionDone);
+ };
+ // whether CSS transition exists
+ if (this.transition.supported && !fromQueue) {
+ this.bind(elDialog, this.transition.type, transitionDone);
+ }
+ // build the proper dialog HTML
+ elDialog.innerHTML = this.build(item);
+ // assign all the common elements
+ btnReset = $("alertify-resetFocus");
+ btnResetBack = $("alertify-resetFocusBack");
+ btnOK = $("alertify-ok") || undefined;
+ btnCancel = $("alertify-cancel") || undefined;
+ btnFocus = (_alertify.buttonFocus === "cancel") ? btnCancel : ((_alertify.buttonFocus === "none") ? $("alertify-noneFocus") : btnOK),
+ input = $("alertify-text") || undefined;
+ form = $("alertify-form") || undefined;
+ // add placeholder value to the input field
+ if (typeof item.placeholder === "string" && item.placeholder !== "") input.value = item.placeholder;
+ if (fromQueue) this.setFocus();
+ this.addListeners(item.callback);
+ },
+
+ /**
+ * Unbind events to elements
+ *
+ * @param {Object} el HTML Object
+ * @param {Event} event Event to detach to element
+ * @param {Function} fn Callback function
+ *
+ * @return {undefined}
+ */
+ unbind : function (el, event, fn) {
+ if (typeof el.removeEventListener === "function") {
+ el.removeEventListener(event, fn, false);
+ } else if (el.detachEvent) {
+ el.detachEvent("on" + event, fn);
+ }
+ }
+ };
+
+ return {
+ alert : function (message, fn, cssClass) { _alertify.dialog(message, "alert", fn, "", cssClass); return this; },
+ confirm : function (message, fn, cssClass) { _alertify.dialog(message, "confirm", fn, "", cssClass); return this; },
+ extend : _alertify.extend,
+ init : _alertify.init,
+ log : function (message, type, wait) { _alertify.log(message, type, wait); return this; },
+ prompt : function (message, fn, placeholder, cssClass) { _alertify.dialog(message, "prompt", fn, placeholder, cssClass); return this; },
+ success : function (message, wait) { _alertify.log(message, "success", wait); return this; },
+ error : function (message, wait) { _alertify.log(message, "error", wait); return this; },
+ set : function (args) { _alertify.set(args); },
+ labels : _alertify.labels,
+ debug : _alertify.handleErrors
+ };
+ };
+
+ // AMD and window support
+ if (typeof define === "function") {
+ define([], function () { return new Alertify(); });
+ } else if (typeof global.alertify === "undefined") {
+ global.alertify = new Alertify();
+ }
+
+}(this));
diff --git a/dev/js/src/tutorial.js b/dev/js/src/tutorial.js
index dae5fbf..4f00ee1 100644
--- a/dev/js/src/tutorial.js
+++ b/dev/js/src/tutorial.js
@@ -2,8 +2,9 @@
* Open and close a tutorial page.
* The current page is stored and retrieved in a session cookie.
*/
-// Todo: add query mechanism!
-
+// TODO: Add query mechanism!
+// TODO: Highlight current section:
+// http://stackoverflow.com/questions/24887258/highlight-navigation-link-as-i-scroll-down-the-page
define(['session', 'util'], function (sessionClass) {
"use strict";
@@ -52,7 +53,10 @@
if (this._iframe === null) {
this._iframe = document.createElement('iframe');
- this._iframe.setAttribute('src', this.getPage() || this.start);
+ this._iframe.setAttribute(
+ 'src',
+ (this.getPage() || this.start) + '?embedded=true'
+ );
var ul = document.createElement('ul');
ul.classList.add('action', 'right');
diff --git a/dev/scss/base.scss b/dev/scss/base.scss
index d5a1eac..42aba2c 100644
--- a/dev/scss/base.scss
+++ b/dev/scss/base.scss
@@ -7,14 +7,14 @@
margin: 0;
}
-g > text {
- fill: $dark-grey;
-}
-
* {
@include box-sizing-box;
}
+g > text {
+ fill: $dark-grey;
+}
+
html {
height: 100%;
}
@@ -63,6 +63,7 @@
text-align: justify;
hyphens: auto;
}
+
/*
> section > p, > p {
a {
diff --git a/dev/scss/footer/footer.scss b/dev/scss/footer/footer.scss
new file mode 100644
index 0000000..2cfd773
--- /dev/null
+++ b/dev/scss/footer/footer.scss
@@ -0,0 +1,19 @@
+@charset "utf-8";
+@import "../util";
+
+footer {
+ position: absolute;
+ background-color: $dark-grey;
+ bottom: 0;
+ width: 100%;
+ text-align: center;
+ a:link {
+ color: $light-grey;
+ &:hover {
+ color: $nearly-white;
+ }
+ &:active, &:visited {
+ color: $dark-grey;
+ }
+ }
+}
diff --git a/dev/scss/header.scss b/dev/scss/header/header.scss
similarity index 92%
rename from dev/scss/header.scss
rename to dev/scss/header/header.scss
index 2a49312..60a3bf4 100644
--- a/dev/scss/header.scss
+++ b/dev/scss/header/header.scss
@@ -1,5 +1,9 @@
@charset "utf-8";
-@import "util";
+@import "../util";
+@import "hint"; // Hint specific menu list
+@import "menu"; // Menu list
+@import "searchbar"; // The search bar
+@import "vc"; // Virtual collection builder
header {
position: relative;
diff --git a/dev/scss/hint.scss b/dev/scss/header/hint.scss
similarity index 97%
rename from dev/scss/hint.scss
rename to dev/scss/header/hint.scss
index 18000a1..a373abc 100644
--- a/dev/scss/hint.scss
+++ b/dev/scss/header/hint.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size: 2px;
diff --git a/dev/scss/menu.scss b/dev/scss/header/menu.scss
similarity index 98%
rename from dev/scss/menu.scss
rename to dev/scss/header/menu.scss
index a8e0306..46529bd 100644
--- a/dev/scss/menu.scss
+++ b/dev/scss/header/menu.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size: 2px;
diff --git a/dev/scss/searchbar.scss b/dev/scss/header/searchbar.scss
similarity index 98%
rename from dev/scss/searchbar.scss
rename to dev/scss/header/searchbar.scss
index 00c65e2..883f003 100644
--- a/dev/scss/searchbar.scss
+++ b/dev/scss/header/searchbar.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size: 2px;
diff --git a/dev/scss/vc.scss b/dev/scss/header/vc.scss
similarity index 99%
rename from dev/scss/vc.scss
rename to dev/scss/header/vc.scss
index 3a989ec..992fac0 100644
--- a/dev/scss/vc.scss
+++ b/dev/scss/header/vc.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$left-padding: 28pt; // 2.8em;
$border-size: 2px;
diff --git a/dev/scss/kalamar.scss b/dev/scss/kalamar.scss
index 677a58a..ad7fc9d 100644
--- a/dev/scss/kalamar.scss
+++ b/dev/scss/kalamar.scss
@@ -1,23 +1,9 @@
@charset "utf-8";
// Global variables and mixins
-@import "fonts"; // Font families
-
-@import "base"; // Base styles
-@import "logos"; // Logo images
-@import "header"; // Top
-@import "footer"; // Bottom
-@import "searchbar"; // The search bar
-@import "menu"; // Menu list
-@import "hint"; // Hint specific menu list
-@import "pagination"; // Pagination
-@import "resultinfo"; // Information on results
-@import "matchinfo"; // Match table and tree
-@import "kwic"; // Kwic view information
-@import "vc"; // Virtual collection builder
-@import "tutorial"; // Embedded and non-embedded tutorial
-@import "query"; // View query
-@import "sidebar"; // Navigation on the left side
-@import "highlight"; // Navigation on the left side
-
-@import "media"; // Media queries
+@import "fonts"; // Font families
+@import "base"; // Base styles
+@import "main/main"; // Main frame styles
+@import "header/header"; // Top frame styles
+@import "footer/footer"; // Bottom frame styles
+@import "media"; // Media queries
diff --git a/dev/scss/alertify.scss b/dev/scss/main/alertify.scss
similarity index 85%
rename from dev/scss/alertify.scss
rename to dev/scss/main/alertify.scss
index ee83a5d..0eb3194 100644
--- a/dev/scss/alertify.scss
+++ b/dev/scss/main/alertify.scss
@@ -1,4 +1,4 @@
-@import "util";
+@import "../util";
/*
article.alertify-log {
diff --git a/dev/scss/highlight.scss b/dev/scss/main/highlight.scss
similarity index 100%
rename from dev/scss/highlight.scss
rename to dev/scss/main/highlight.scss
diff --git a/dev/scss/kwic.scss b/dev/scss/main/kwic.scss
similarity index 99%
rename from dev/scss/kwic.scss
rename to dev/scss/main/kwic.scss
index 0c91c7b..f04cf9b 100644
--- a/dev/scss/kwic.scss
+++ b/dev/scss/main/kwic.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size: 2px;
diff --git a/dev/scss/logos.scss b/dev/scss/main/logos.scss
similarity index 98%
rename from dev/scss/logos.scss
rename to dev/scss/main/logos.scss
index eb91e5e..1512a56 100644
--- a/dev/scss/logos.scss
+++ b/dev/scss/main/logos.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
.logo {
&::after {
diff --git a/dev/scss/main/main.scss b/dev/scss/main/main.scss
new file mode 100644
index 0000000..ea569fa
--- /dev/null
+++ b/dev/scss/main/main.scss
@@ -0,0 +1,12 @@
+@import "highlight"; // Navigation on the left side
+@import "kwic"; // Kwic view information
+@import "logos"; // Logo images
+@import "matchinfo"; // Match table and tree
+@import "pagination"; // Pagination
+@import "query"; // View query
+@import "resultinfo"; // Information on results
+@import "sidebar"; // Navigation on the left side
+@import "tutorial"; // Embedded and non-embedded tutorial
+@import "alertify";
+@import "alertify/alertify.core.css";
+@import "alertify/alertify.default.css";
diff --git a/dev/scss/matchinfo.scss b/dev/scss/main/matchinfo.scss
similarity index 99%
rename from dev/scss/matchinfo.scss
rename to dev/scss/main/matchinfo.scss
index 937a022..a0be411 100644
--- a/dev/scss/matchinfo.scss
+++ b/dev/scss/main/matchinfo.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$left-width: 176px;
$border-size: 2px;
diff --git a/dev/scss/pagination.scss b/dev/scss/main/pagination.scss
similarity index 98%
rename from dev/scss/pagination.scss
rename to dev/scss/main/pagination.scss
index 5ceca89..d057dd0 100644
--- a/dev/scss/pagination.scss
+++ b/dev/scss/main/pagination.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size : 2px;
diff --git a/dev/scss/query.scss b/dev/scss/main/query.scss
similarity index 94%
rename from dev/scss/query.scss
rename to dev/scss/main/query.scss
index 4167059..ab45238 100644
--- a/dev/scss/query.scss
+++ b/dev/scss/main/query.scss
@@ -1,5 +1,5 @@
-@import "util";
@charset "utf-8";
+@import "../util";
pre.query {
diff --git a/dev/scss/resultinfo.scss b/dev/scss/main/resultinfo.scss
similarity index 91%
rename from dev/scss/resultinfo.scss
rename to dev/scss/main/resultinfo.scss
index 1f1097f..8717f72 100644
--- a/dev/scss/resultinfo.scss
+++ b/dev/scss/main/resultinfo.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
div.resultinfo {
clear: both;
diff --git a/dev/scss/sidebar.scss b/dev/scss/main/sidebar.scss
similarity index 97%
rename from dev/scss/sidebar.scss
rename to dev/scss/main/sidebar.scss
index 3adc1b4..7c74912 100644
--- a/dev/scss/sidebar.scss
+++ b/dev/scss/main/sidebar.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size: 2px;
@@ -66,20 +66,21 @@
li {
padding: 0;
> a {
- @include choose-item;
padding: $item-padding;
-
+ }
+ > a:link {
+ @include choose-item;
border-right: {
width: $border-size;
style: solid;
}
display: block;
- &:link, &:visited {
+ &:visited {
color: inherited;
}
&:hover {
- color: inherit;
+ // color: inherit;
transition: none;
@include choose-hover;
}
diff --git a/dev/scss/tutorial.scss b/dev/scss/main/tutorial.scss
similarity index 97%
rename from dev/scss/tutorial.scss
rename to dev/scss/main/tutorial.scss
index 4b4345b..3aeebb3 100644
--- a/dev/scss/tutorial.scss
+++ b/dev/scss/main/tutorial.scss
@@ -1,5 +1,5 @@
@charset "utf-8";
-@import "util";
+@import "../util";
$border-size: 2px;
@@ -52,7 +52,7 @@
}
*/
padding-top: 0;
- // @include choose-item;
+ @include choose-item;
background-color: $middle-grey;
> div {
position: relative;
diff --git a/dev/scss/media.scss b/dev/scss/media.scss
index cef3fed..45e3bee 100644
--- a/dev/scss/media.scss
+++ b/dev/scss/media.scss
@@ -89,11 +89,15 @@
}
}
-/*
-
- #sidebar {
- padding-top: 22px;
+ div.intro {
+ width: 100%;
}
+
+ aside ul {
+ font-size: 9pt;
+ line-height: 1em;
+ }
+
#tutorial {
border-radius: 0;
border-width: 0;
@@ -102,9 +106,19 @@
bottom: 0;
top: 0;
padding: 0;
+ iframe {
+ border-radius: 0;
+ }
}
- #tutorial iframe {
- border-radius: 0;
+
+ main.tutorial {
+ margin-right: 30px;
+ }
+
+/*
+
+ #sidebar {
+ padding-top: 22px;
}
#sidebar:not(.active) > i.fa-bars {
font-size: 12pt;
diff --git a/kalamar.conf b/kalamar.conf
index 2351717..b062064 100644
--- a/kalamar.conf
+++ b/kalamar.conf
@@ -25,11 +25,6 @@
cache_size => '12m'
}
},
-# Oro => {
-# users => {
-# file => app->home .'/db/users.sqlite'
-# },
-# },
hypnotoad => {
listen => ['http://*:6666', 'http://*:5555'],
workers => 5,
@@ -44,6 +39,41 @@
current => '<span>{current}</span>',
page => '<span>{page}</span>'
},
+ Localize => {
+ dict => {
+ _ => sub { $_->locale },
+ de => {
+ about => 'Über KorAP',
+ login => 'Anmelden',
+ searchplaceholder => 'Finde ...',
+ go => 'Suche!',
+ in => 'in',
+ with => 'mit',
+ glimpse => 'Stichproben',
+ faq => 'Häufige Fragen',
+ tutorial => 'Einführung',
+ korap => {
+ -short => 'KorAP',
+ long => 'KorAP - Korpusanalyseplattform der nächsten Generation'
+ }
+ },
+ en => {
+ about => 'About KorAP',
+ login => 'Login',
+ go => 'Go!',
+ searchplaceholder => 'Find ...',
+ in => 'in',
+ with => 'with',
+ glimpse => 'Glimpse',
+ faq => 'F.A.Q.',
+ tutorial => 'Tutorial',
+ korap => {
+ -short => 'KorAP',
+ long => 'KorAP - Corpus Analysis Platform'
+ }
+ }
+ }
+ },
MailException => {
from => 'korap@ids-mannheim.de',
to => 'diewald@ids-mannheim.de',
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 1f55517..abd4d5e 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -13,11 +13,55 @@
my $pkg = b($self->home . '/package.json')->slurp;
$Kalamar::VERSION = decode_json($pkg)->{version};
+ # Load documentation navigation
+ my $navi = b($self->home . '/templates/doc/_nav.json')->slurp;
+
+ # Add additional plugin path
+ push(@{$self->plugins->namespaces}, __PACKAGE__ . '::Plugin');
+
+ # Load plugins
+ foreach (qw/Config
+ Localize
+ Notifications
+ DocNavi
+ KalamarTagHelpers/) {
+ $self->plugin($_);
+ };
+
+ $self->config(navi => decode_json($navi));
+
+ $self->plugin('MailException' => $self->config('MailException'));
+
+ # Establish routes
+ my $r = $self->routes;
+
+ $r->get('/')->to(
+ cb => sub {
+ return shift->render(template => 'intro');
+ });
+
+ # Base query page
+ $r->get('/')->to('search#query')->name('index');
+
+
+ # Documentation
+ $r->get('/doc')->to('documentation#page', page => 'korap');
+ $r->get('/doc/:page')->to('documentation#page', scope => undef);
+ $r->get('/doc/*scope/:page')->to('documentation#page')->name('doc');
+};
+
+
+1;
+
+
+__END__
+
+
# Set default totle
- $self->defaults(
- layout => 'main',
- title => 'KorAP - Corpus Analysis Platform'
- );
+# $self->defaults(
+# layout => 'main',
+# title => 'KorAP - Corpus Analysis Platform'
+# );
# Set secret for signed cookies
$self->secrets([
@@ -42,25 +86,20 @@
$h->header( 'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With' );
});
- # Add additional plugin path
- push(@{$self->plugins->namespaces}, __PACKAGE__ . '::Plugin');
# Load plugins
- foreach (qw/Config
- CHI
+ foreach (qw/CHI
TagHelpers::Pagination
- Notifications
Number::Commify
Search
KalamarHelpers
- KalamarTagHelpers/) {
+ /) {
$self->plugin($_);
};
# $self->plugin(AssetPack => { minify => 1 });
$self->plugin('AssetPack');
$self->plugin('AssetPack::LibSass');
- $self->plugin('MailException' => $self->config('MailException'));
# Add assets for AssetPack
$self->asset(
@@ -107,8 +146,6 @@
}
);
- # Routes
- my $r = $self->routes;
# Base search route
$r->get('/')->to('search#query')->name('index');
@@ -124,10 +161,6 @@
$r->get('/tutorial')->to('tutorial#page', tutorial => 'index');
$r->get('/tutorial/(*tutorial)')->to('tutorial#page')->name('tutorial');
- $r->get('/doc/korap')->to(
- cb => sub {
- return shift->render(template => 'doc/korap');
- });
# Todo: The FAQ-Page has a contact form for new questions
};
diff --git a/lib/Kalamar/Controller/Documentation.pm b/lib/Kalamar/Controller/Documentation.pm
new file mode 100644
index 0000000..31d11cf
--- /dev/null
+++ b/lib/Kalamar/Controller/Documentation.pm
@@ -0,0 +1,37 @@
+package Kalamar::Controller::Documentation;
+use Mojo::Base 'Mojolicious::Controller';
+
+# Show documentation page
+sub page {
+ my $c = shift;
+ if ($c->param('embedded')) {
+ $c->stash(embedded => 1);
+ };
+
+ my @path = ('doc');
+
+ # There is a scope defined
+ my $scope = $c->stash('scope');
+ push(@path, $scope) if $scope;
+
+ # Use the defined page
+ my $page = $c->stash('page');
+ push(@path, $page);
+
+ $c->content_for(
+ sidebar => '<nav>' . $c->doc_navi($c->config('navi')) . '</nav>'
+ );
+
+ # Render template
+ return $c->render(
+ sidebar_active => 1,
+ main_class => 'tutorial',
+ template => join('/', @path)
+ );
+};
+
+
+1;
+
+
+__END__
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index f56ca77..25954d4 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -3,13 +3,26 @@
# Add X-Forwarded-For to user agent call everywhere
-
-# Query the KorAP backends and render a template
sub query {
my $c = shift;
my $query = $c->param('q');
+ # No query
+ unless ($query) {
+ return $c->render(template => 'index');
+ }
+};
+
+
+1;
+
+
+__END__
+
+# Query the KorAP backends and render a template
+sub query {
+
# Base parameters for remote access
my %param = (
query_language => scalar $c->param('ql'),
diff --git a/lib/Kalamar/Controller/Tutorial.pm b/lib/Kalamar/Controller/Tutorial.pm
deleted file mode 100644
index db86998..0000000
--- a/lib/Kalamar/Controller/Tutorial.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package Kalamar::Controller::Tutorial;
-use Mojo::Base 'Mojolicious::Controller';
-
-# Todo: Set title as defaults
-
-sub page {
- my $c = shift;
-
- # Show tutorial embedded
- if ($c->param('embedded')) {
- $c->layout('snippet');
- $c->stash(embedded => 1);
- }
-
- # Show tutorial in full screen
- else {
- $c->layout('default');
- };
-
- # Title should be "KorAP"
- $c->title('KorAP');
-
- my $page = $c->stash('tutorial');
- return $c->render(
- template => 'tutorial/' . $page
- );
-};
-
-
-1;
-
-
-__END__
diff --git a/package.json b/package.json
index 38344d9..d3bbea4 100755
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "kalamar",
- "version": "0.14.1",
+ "version": "0.14.2",
"description": "User Frontend for KorAP",
"devDependencies": {
"grunt": "~0.4.5",
diff --git a/templates/partial/header.html.ep b/templates/partial/header.html.ep
index abd03de..02e660e 100644
--- a/templates/partial/header.html.ep
+++ b/templates/partial/header.html.ep
@@ -1,9 +1,38 @@
-<head>
- <title><%= title %></title>
-%= asset 'kalamar.css'
-%= asset 'kalamar.js'
-
- <link rel="shortcut icon" href="/kalamar/favicon.ico" type="image/x-icon" />
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=false, user-scalable=no" />
-</head>
+<header>
+ <a href="/" class="logo" title="<%= title %>"><h1><span><%= title %></span></h1></a>
+ <div class="button top">
+ <a href="#" class="login" title="<%= loc 'login' %>"><span><%= loc 'login' %></span></a>
+ </div>
+ <form autocomplete="off" action="/kalamar">
+ <div id="searchbar">
+ <input placeholder="<%= loc 'searchplaceholder' %>" name="q" id="q-field" autofocus="autofocus" type="search" />
+ <button type="submit" title="<%= loc 'go' %>"><span><%= loc 'go' %></span></button>
+ </div>
+ <div id="vc-view"></div>
+ <%= loc 'in' %> <input type="hidden" id="vc-name" name="vc-name" value="Wikipedia" />
+ <input type="text" name="vc" id="vc" value="corpusID = Wikipedia" />
+ <%= loc 'with' %> <span class="select">
+ <select name="ql" id="ql-field">
+ <option value="poliqarp">Poliqarp</option>
+ <option value="cosmas2">Cosmas II</option>
+ <option value="annis">Annis</option>
+ <option value="cql">CQL v1.2</option>
+ </select>
+ </span>
+ <div class="button right">
+ % param(cutoff => 1) unless param 'q';
+ %= check_box cutoff => 1, id => 'q-cutoff-field', class => 'checkbox'
+ <label for="q-cutoff-field"><span></span><%= loc('glimpse') %></label>
+ % unless (current_route 'tutorial') {
+ <a href="/doc/faq"
+ title="<%= loc 'faq' %>"
+ class="question"
+ id="view-faq"><span><%= loc 'faq' %></span></a>
+ % };
+ <a href="/doc"
+ title="<%= loc 'tutorial' %>"
+ class="tutorial"
+ id="view-tutorial"><span><%= loc 'tutorial' %></span></a>
+ </div>
+ </form>
+</header>