summaryrefslogtreecommitdiff
path: root/src/www
diff options
context:
space:
mode:
Diffstat (limited to 'src/www')
-rw-r--r--src/www/gfse/Makefile7
-rw-r--r--src/www/gfse/P/1306856253_weather_06.pngbin0 -> 2311 bytes
-rw-r--r--src/www/gfse/P/1307545089_weather_04.pngbin0 -> 2376 bytes
-rw-r--r--src/www/gfse/P/w1s.jpgbin0 -> 26163 bytes
-rw-r--r--src/www/gfse/P/w2s.jpgbin0 -> 25953 bytes
-rw-r--r--src/www/gfse/P/w3s.jpgbin0 -> 29664 bytes
-rw-r--r--src/www/gfse/P/w4s.jpgbin0 -> 22005 bytes
-rw-r--r--src/www/gfse/TODO43
-rw-r--r--src/www/gfse/about.html243
-rw-r--r--src/www/gfse/cloud.js148
-rw-r--r--src/www/gfse/cloud2.js159
-rw-r--r--src/www/gfse/editor.css88
-rw-r--r--src/www/gfse/editor.js1114
-rw-r--r--src/www/gfse/example_based.js158
-rw-r--r--src/www/gfse/gf_abs.js226
-rw-r--r--src/www/gfse/gfse.manifest4
-rw-r--r--src/www/gfse/grammars.cgi19
-rw-r--r--src/www/gfse/index.html48
-rw-r--r--src/www/gfse/localstorage.js21
-rw-r--r--src/www/gfse/molto.css82
-rw-r--r--src/www/gfse/save.hs25
-rw-r--r--src/www/gfse/share.html27
-rw-r--r--src/www/gfse/slideshow.js86
-rw-r--r--src/www/gfse/upload.cgi252
-rw-r--r--src/www/index.html18
-rw-r--r--src/www/minibar/about.html180
-rw-r--r--src/www/minibar/align-btn.pngbin0 -> 138 bytes
-rw-r--r--src/www/minibar/brushed-metal.pngbin0 -> 36233 bytes
-rw-r--r--src/www/minibar/example.html57
-rw-r--r--src/www/minibar/feedback.cgi44
-rw-r--r--src/www/minibar/feedback.html48
-rw-r--r--src/www/minibar/gf-web-api-examples.html151
-rw-r--r--src/www/minibar/minibar-api.html235
-rw-r--r--src/www/minibar/minibar.css53
-rw-r--r--src/www/minibar/minibar.html41
-rw-r--r--src/www/minibar/minibar.js176
-rw-r--r--src/www/minibar/minibar_input.js277
-rw-r--r--src/www/minibar/minibar_online.js25
-rw-r--r--src/www/minibar/minibar_support.js46
-rw-r--r--src/www/minibar/minibar_translations.js162
-rw-r--r--src/www/minibar/pgf_offline.js96
-rw-r--r--src/www/minibar/pgf_online.js52
-rw-r--r--src/www/minibar/phrasebook.html56
-rw-r--r--src/www/minibar/saldotest.html30
-rw-r--r--src/www/minibar/saldotest.js340
-rw-r--r--src/www/minibar/support.js300
-rw-r--r--src/www/minibar/tree-btn.pngbin0 -> 149 bytes
47 files changed, 5137 insertions, 0 deletions
diff --git a/src/www/gfse/Makefile b/src/www/gfse/Makefile
new file mode 100644
index 000000000..00a97b337
--- /dev/null
+++ b/src/www/gfse/Makefile
@@ -0,0 +1,7 @@
+
+save: save.hs
+ ghc --make save.hs
+
+install::
+ @make save
+ rsync -avz --exclude .DS_Store P *.html *.css *.js ../../runtime/javascript/minibar/support.js *.cgi *.manifest save www.grammaticalframework.org:/usr/local/www/GF/demos/gfse
diff --git a/src/www/gfse/P/1306856253_weather_06.png b/src/www/gfse/P/1306856253_weather_06.png
new file mode 100644
index 000000000..3f01afcaa
--- /dev/null
+++ b/src/www/gfse/P/1306856253_weather_06.png
Binary files differ
diff --git a/src/www/gfse/P/1307545089_weather_04.png b/src/www/gfse/P/1307545089_weather_04.png
new file mode 100644
index 000000000..8a7f1e3ae
--- /dev/null
+++ b/src/www/gfse/P/1307545089_weather_04.png
Binary files differ
diff --git a/src/www/gfse/P/w1s.jpg b/src/www/gfse/P/w1s.jpg
new file mode 100644
index 000000000..199fcb7ba
--- /dev/null
+++ b/src/www/gfse/P/w1s.jpg
Binary files differ
diff --git a/src/www/gfse/P/w2s.jpg b/src/www/gfse/P/w2s.jpg
new file mode 100644
index 000000000..9673758c0
--- /dev/null
+++ b/src/www/gfse/P/w2s.jpg
Binary files differ
diff --git a/src/www/gfse/P/w3s.jpg b/src/www/gfse/P/w3s.jpg
new file mode 100644
index 000000000..1a8680ec7
--- /dev/null
+++ b/src/www/gfse/P/w3s.jpg
Binary files differ
diff --git a/src/www/gfse/P/w4s.jpg b/src/www/gfse/P/w4s.jpg
new file mode 100644
index 000000000..5c685c134
--- /dev/null
+++ b/src/www/gfse/P/w4s.jpg
Binary files differ
diff --git a/src/www/gfse/TODO b/src/www/gfse/TODO
new file mode 100644
index 000000000..22974ebe0
--- /dev/null
+++ b/src/www/gfse/TODO
@@ -0,0 +1,43 @@
++ Safety question before deleting a grammar
++ Check identifier syntax
++ Allow lincat for deleted cat to be deleted
++ Allow lin for deleted fun to be deleted
++ Apply category alpha conversion in concrete syntax
++ Remove a concrete syntax
++ Apply function alpha conversion in concrete syntax
++ Change lhs of lins when function type is changed
+
++ Allow languages other than the ones in the given list to be added
++ Export as plain text
++ Allow definitions to be reordered
+
++ 1. possibility to compile the grammar set, returning a URL to a translator app
++ 2a. possibility to import modules - resource libraries
+- 2b. possibility to import modules - user's own auxiliaries
+- 3. possibility to upload own modules
++ 4. access to the created files in an on-line shell (making testing possible)
+- 5. rule-to-rule type checking and guidance (e.g. with library oper
+ suggestions)
++ Try grammars in the Translation Quiz
++ Show lincat and lin before params and opers below
++ Create a new concrete syntax by copying an existing one.
+- Easy access to compute_concrete from the editor
+- Instead of starting with an empty grammar, start a new grammar by copying
+ an example.
++ Cloning grammars
+- Allow grammars to contain a resoure module. Create the resource module by
+ factoring out common parts of the concrete syntaxes.
+- Integrate example-based concrete syntax construction (using Ramona's tool)
+- Open lexicon modules? They are not installed by default!
+ DictEng, MorphalouFre, DictSwe, DictTur, DictBul, DictFun, DictLav
+
++ compile only the uploaded grammar even if other grammars are present
++ 'flags startcat' is needed for grammars with only one category (since the
+ default startcat is S, even if it doesn't exist)
+
+- Bug! After adding a 2nd def of a fun with a different type and then deleting
+ the old fun, the corresponding lin will have the wrong lhs.
+
++ Bug! The startcat menu shows the first category by default, but the startcat
+ flag is actually not set until a selection is made from the menu.
+
diff --git a/src/www/gfse/about.html b/src/www/gfse/about.html
new file mode 100644
index 000000000..8660621a3
--- /dev/null
+++ b/src/www/gfse/about.html
@@ -0,0 +1,243 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>About: GF online editor for simple multilingual grammars</title>
+<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
+<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
+
+<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren">
+
+<meta name = "viewport" content = "width = device-width">
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+<meta charset="UTF-8">
+
+<script type="text/javascript" src="slideshow.js"></script>
+
+</head>
+
+<body>
+<h1>GF online editor for simple multilingual grammars</h1>
+
+<div class=right>
+ <div class=slideshow>
+ <img onload="start_slideshow(this,{delay:5,fade:0.3})" src="P/w1s.jpg" alt="[GF online editor screen shoot]">
+ <img class=hidden src="P/w2s.jpg" alt="[GF online editor screen shoot]">
+ <img class=hidden src="P/w3s.jpg" alt="[GF online editor screen shoot]">
+ <img class=hidden src="P/w4s.jpg" alt="[GF online editor screen shoot]">
+ </div>
+</div>
+<h2>Introduction</h2>
+
+Traditionally, <a href="http://www.grammaticalframework.org/">GF</a>
+grammars are created in a text editor and tested in the
+GF shell. Text editors know very little (if anything) about the syntax of
+GF grammars, and thus provide little guidance for novice GF users. Also, the
+grammar author has to download and install the GF software on his/her own
+computer.
+
+<p>
+In contrast, the
+<a href="."><em>GF online editor for simple multilingual grammars</em></a>
+is available online, making it easier to get started. All that
+is needed is a reasonably modern web browser. Even Android and iOS devices
+can be used.
+<p>
+The editor
+also guides the grammar author by showing a skeleton grammar file and
+hinting how the parts should be filled in. When a new part is added to the
+grammar, it is immediately checked for errors.
+
+<p>
+Editing operations are accessed by clicking on editing symbols embedded
+in the grammar display:
+<span class=more>+</span>=Add an item,
+<span class=delete>×</span>=Delete an item,
+<span class=edit>%</span>=Edit an item.
+These are revealed when hovering over items. On touch devices, hovering is
+in some cases simulated by tapping, but there is also a button at the bottom
+of the display to "Enable editing on touch devices" that reveals all editing
+symbols.
+
+<p>
+In spite of its name, the editor runs entierly in the web
+browser, so once you have opened the web page, you can
+<strong>continue editing</strong> grammars even while you are
+<strong>offline</strong>.
+
+<h3>Current status</h3>
+
+<p>
+At the moment, the editor supports only a small subset of the GF grammar
+notation.
+Proper error checking is done for abstract syntax, but not (yet) for concrete
+syntax.
+
+<p>
+The grammars created with this editor always consists of one file for the
+abstract syntax, and one file for each concrete syntax.
+
+<h4>Abstract syntax</h4>
+
+The supported abstract syntax corresponds to context-free grammars
+(no dependent types). The definition of an abstract syntax is limited to
+<ul>
+ <li>a list of <em>category names</em>,
+ <var>Cat<sub>1</sub></var> ; ... ; <var>Cat<sub>n</sub></var>,
+ <li>a list of <em>functions</em> of the form
+ <var>Fun</var> : <var>Cat<sub>1</sub></var> -> ... ->
+ <var>Cat<sub>n</sub></var>,
+ <li>and a <em>start category</em>.
+</ul>
+
+Available editing operations:
+<ul>
+ <li>Categories can be added, removed and renamed. When renaming a category,
+ occurences of it in function types will be updated accordingly.
+ <li>Functions can be added, removed and edited. Concrete syntaxes are updated
+ to reflect changes.
+ <li>Functions can be reordered using drag-and-drop.
+</ul>
+
+Error checks:
+
+<ul>
+ <li>Syntactically incorrect function definitions are refused.
+ <li>Semantic problem such as duplicated definitions or references to
+ undefined categories, are highlighted.
+</ul>
+
+<h4>Concrete syntax</h4>
+
+At the moment, the concrete syntax for a language <var>L</var> is limited to
+<ul>
+ <li>opening the Resource Grammar Library modules
+ <code>Syntax</code><var>L</var> and <code>Paradigms</code><var>L</var>,
+ <code>Lexicon</code><var>L</var> and <code>Extra</code><var>L</var>,
+ <li><em>linearization types</em> for the categories in the abstract syntax,
+ <li><em>linearizations</em> for the functions in the abstract syntax,
+ <li><em>parameter type definitions</em>,
+ <var>P</var> = <var>C<sub>1</sub></var> | ... |<var>C<sub>n</sub></var>,
+ <li>and <em>operation definitions</em>, <var>op</var> = <var>expr</var>,
+ <var>op</var> : <var>type</var> = <var>expr</var>,
+</ul>
+
+Available editing operations:
+<ul>
+ <li>The LHSs of the linearization types and linearizations are determined by
+ the abstract syntax and do not need to be entered manually.
+ The RHSs can
+ be edited.
+ <li>Parameter types can be added, removed and edited.
+ <li>Operation definitons can be added, removed and edited.
+ <li>Definitions can be reordered (using drag-and-drop)
+</ul>
+Also,
+
+<ul>
+ <li>When a new concrete syntax is added to the grammar, a copy of the
+ currently open concrete syntax is created, since copying and modifying
+ is usually easier than creating something new from scratch.
+ (If the abstract syntax is currently open, the new conrete syntax will
+ start out empty.)
+ <li>When adding a new concrete syntax, you normally pick one of the supported
+ languages from a list. The language code and the file name is determined
+ automatically. But you can also pick <em>Other</em> from the list and change
+ the language code afterwards to add a concrete syntax for a language
+ that is not in the list.
+</ul>
+
+Error checks:
+<ul>
+ <li>The RHSs in the concrete syntax are checked
+ for syntactic correctness by the editor as they are entered.
+ (TODO: the syntax of parameter types is not check at the moment.)
+ <li>Duplicated definitions are highlighted. Checks for other
+ semantic errors are delayed until the grammar is compiled.
+</ul>
+
+<h3>Compiling and testing grammars</h3>
+
+When pressing the <strong>Compile</strong> button, the grammar will be compiled
+with GF, and any errors not detected by the editor will be reported.
+If the grammar is free from errors the user can then
+test the grammar by clicking on links to the online GF shell, the Minibar or
+the Translation Quiz.
+
+<h3><img class=right src="P/1307545089_weather_04.png" alt="">
+<img class=right src="P/1306856253_weather_06.png" alt="">Grammars in the
+cloud</h3>
+
+While the editor normally stores grammars locally in the browser, it is also
+possible to store grammars in the cloud. Grammars can be stored in the cloud
+just for backup, or to make them accessible from multiple devices.
+
+<p>
+There is no automatic synchronization between local grammars and the cloud.
+Instead, the user should press
+<img src="P/1306856253_weather_06.png" alt="[Cloud Upload]">
+to upload the grammars to the cloud, and press
+<img src="P/1307545089_weather_04.png" alt="[Cloud download]">
+to download grammars from the cloud. In both cases, complete grammars
+are copied and older versions at the destination will be overwritten.
+When a grammar is deleted, both the local copy and the copy in the cloud
+is deleted.
+
+<p>
+Each device is initially assigned to its own unique cloud. Each device can thus
+have its own set of grammars that are not available on other devices. It is
+also possible to merge clouds and share a common set of grammars between
+multiple devices: when uploading grammars to the cloud, a link to this grammar
+cloud appears. Accessing this link from another device will cause the clouds of
+the two devices to be merged. After this, grammars uploaded from one of the
+devices can be downloaded on the other devices. Any number devices can join the
+same grammar cloud in this way.
+
+<p>
+<strong>Note</strong> that while it is possible to copy grammars between
+multiple devices, there is no way to merge concurrent edits from multiple
+devices. If the same grammar is uploaded to the
+cloud from multiple devices, the last upload wins. Thus the current
+implementation is suitable for a single user switching between different
+devices, but not recommended for sharing grammars between multiple users.
+
+<p>
+Also <strong>note</strong> that each grammar is assigned a unique identity
+when it is first created. Renaming a grammar does not change its identity.
+This means that name changes are propagated between devices like other changes.
+
+<h3>Example-based grammar writing</h3>
+
+This is work in progress...
+
+<h3>Future work</h3>
+
+This prototype gives an idea of how a web based GF grammar editor could work.
+While this editor is implemented in JavaScript and runs in the web browser,
+we do not expect to create a full implementation of GF that runs in the
+web browser, but let the editor communicate with a server running GF.
+<p>
+By developing a GF server with an appropriate API, it should
+be possible to extend the editor to support a larger fragment of GF,
+to do proper error checking and make more of the existing GF shell functionality
+accessible directly from the editor.
+<p>
+The current grammar cloud service is very primitive. In particular, it is not
+suitable for multiple users developing a grammar in collaboration.
+
+<h3>Related documents</h3>
+<ul>
+ <li><a href="http://www.grammaticalframework.org/~hallgren/Talks/GF/gf-ide.html">GF Grammar Development Tools</a>. Slides from a presentation at the MOLTO meeting in Göteborg, March 2011.
+ <li><a href="http://www.grammaticalframework.org/grammar-ide/">The GF Grammar IDE</a>. MOLTO deliverable.
+ <li><a href="http://www.grammaticalframework.org/compiler-api">The GF Grammar
+ Compiler API</a>. MOLTO deliverable.
+</ul>
+
+<hr>
+<div class=modtime><small>
+<!-- hhmts start --> Last modified: Fri Oct 7 14:06:14 CEST 2011 <!-- hhmts end -->
+ </small></div>
+<address>
+<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
+<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt="">
+</address>
+</body> </html>
diff --git a/src/www/gfse/cloud.js b/src/www/gfse/cloud.js
new file mode 100644
index 000000000..f19b6ee37
--- /dev/null
+++ b/src/www/gfse/cloud.js
@@ -0,0 +1,148 @@
+
+function with_dir(cont) {
+ var dir=local.get("dir","");
+ if(dir) cont(dir);
+ else ajax_http_get("upload.cgi?dir",
+ function(dir) {
+ local.put("dir",dir);
+ cont(dir);
+ });
+}
+
+function remove_cloud_grammar(g) {
+ var dir=local.get("dir")
+ if(dir && g.unique_name) {
+ var path=dir+"/"+g.unique_name+".json"
+ ajax_http_get("upload.cgi?rm="+encodeURIComponent(path),debug);
+ }
+}
+
+// Upload the grammar to the server and check it for errors
+function upload(g) {
+ function upload2(dir) {
+ var form=node("form",{method:"post",action:"upload.cgi"+dir},
+ [hidden(g.basename+".gf",show_abstract(g))])
+ for(var i in g.concretes)
+ form.appendChild(hidden(g.basename+g.concretes[i].langcode+".gf",
+ show_concrete(g.basename)(g.concretes[i])));
+ editor.appendChild(form);
+ form.submit();
+ form.parentNode.removeChild(form);
+ }
+
+ with_dir(upload2);
+}
+
+// Upload the grammar to store it in the cloud
+function upload_json(cont) {
+ function upload3(resptext,status) {
+ local.put("json_uploaded",Date.now());
+ //debug("Upload complete")
+ if(cont) cont();
+ else {
+ var sharing=element("sharing");
+ if(sharing) sharing.innerHTML=resptext;
+ }
+ }
+ function upload2(dir) {
+ var prefix=dir.substr(10)+"-" // skip "/tmp/gfse."
+ //debug("New form data");
+ //var form=new FormData(); // !!! Doesn't work on Android 2.2!
+ var form="",sep="";
+ //debug("Preparing form data");
+ for(var i=0;i<local.count;i++) {
+ var g=local.get(i,null);
+ if(g) {
+ if(!g.unique_name) {
+ g.unique_name=prefix+i;
+ save_grammar(g)
+ }
+ //form.append(g.unique_name+".json",JSON.stringify(g));
+ form+=sep+encodeURIComponent(g.unique_name+".json")+"="+
+ encodeURIComponent(JSON.stringify(g))
+ sep="&"
+ }
+ }
+ //debug("Upload to "+prefix);
+ ajax_http_post("upload.cgi"+dir,form,upload3,cont)
+ }
+
+ with_dir(upload2);
+}
+
+function download_json() {
+ var dir=local.get("dir");
+ var index=grammar_index();
+ var downloading=0;
+
+ function get_list(ok,err) {
+ ajax_http_get("upload.cgi?ls="+dir,ok,err);
+ }
+
+ function get_file(file,ok,err) {
+ downloading++;
+ ajax_http_get("upload.cgi?download="+encodeURIComponent(dir+"/"+file),ok,err);
+ }
+
+ function file_failed(errormsg,status) {
+ debug(errormsg)
+ downloading--;
+ }
+ function file_downloaded(grammar) {
+ downloading--;
+ var newg=JSON.parse(grammar);
+ debug("Downloaded "+newg.unique_name)
+ var i=index[newg.unique_name];
+ if(i!=undefined) merge_grammar(i,newg)
+ else {
+ debug("New")
+ newg.index=null;
+ save_grammar(newg);
+ }
+ if(downloading==0) done()
+ }
+
+ function done() {
+ setTimeout(function(){location.href="."},2000);
+ }
+
+ function download_files(ls) {
+ local.put("current",0);
+ if(ls) {
+ //debug("Downloading "+ls);
+ var files=ls.split(" ");
+ cleanup_deleted(files);
+ for(var i in files) get_file(files[i],file_downloaded,file_failed);
+ }
+ else {
+ debug("No grammars in the cloud")
+ done()
+ }
+ }
+
+ get_list(download_files);
+}
+
+function link_directories(newdir,cont) {
+ with_dir(function(olddir) {
+ ajax_http_get("upload.cgi?rmdir="+olddir+"&newdir="+newdir,cont)
+ })
+}
+
+/* -------------------------------------------------------------------------- */
+
+// Send a command to the GF shell
+function gfshell(cmd,cont) {
+ alert("gfshell(...) not implmemented!!!")
+}
+
+// Check the syntax of an expression
+function check_exp(s,cont) {
+ function check(gf_message) {
+ //debug("cc "+s+" = "+gf_message);
+ cont(/parse error/.test(gf_message) ? "parse error" : null);
+ }
+ if(navigator.onLine)
+ ajax_http_get("upload.cgi?cc="+encodeURIComponent(s),check)
+ else cont(null)
+}
diff --git a/src/www/gfse/cloud2.js b/src/www/gfse/cloud2.js
new file mode 100644
index 000000000..e32749dc1
--- /dev/null
+++ b/src/www/gfse/cloud2.js
@@ -0,0 +1,159 @@
+
+function with_dir(cont) {
+ var dir=local.get("dir","");
+ if(/^\/tmp\//.test(dir)) cont(dir);
+ else ajax_http_get("/new",
+ function(dir) {
+ local.put("dir",dir);
+ cont(dir);
+ });
+}
+
+function remove_cloud_grammar(g) {
+ var dir=local.get("dir")
+ if(dir && g.unique_name) {
+ var path=g.unique_name+".json"
+ gfcloud("rm",{file:path},debug);
+ }
+}
+
+// Upload the grammar to the server and check it for errors
+function upload(g) {
+ function upload2(dir) {
+ var form=node("form",{method:"post",action:"/cloud"},
+ [hidden("dir",dir),hidden("command","make"),
+ hidden(g.basename+".gf",show_abstract(g))])
+ var files = [g.basename+".gf"]
+ for(var i in g.concretes) {
+ var cname=g.basename+g.concretes[i].langcode+".gf";
+ files.push(cname);
+ form.appendChild(hidden(cname,
+ show_concrete(g.basename)(g.concretes[i])));
+ }
+ editor.appendChild(form);
+ form.submit();
+ form.parentNode.removeChild(form);
+ }
+
+ function upload3(message) { if(message) alert(message); }
+
+ with_dir(upload2)
+}
+
+// Upload the grammar to store it in the cloud
+function upload_json(cont) {
+ function upload3(resptext,status) {
+ local.put("json_uploaded",Date.now());
+ //debug("Upload complete")
+ if(cont) cont();
+ else {
+ var sharing=element("sharing");
+ if(sharing) sharing.innerHTML=resptext;
+ }
+ }
+ function upload2(dir) {
+ var prefix=dir.substr(10)+"-" // skip "/tmp/gfse."
+ //debug("New form data");
+ //var form=new FormData(); // !!! Doesn't work on Android 2.2!
+ var form={dir:dir};
+ //debug("Preparing form data");
+ for(var i=0;i<local.count;i++) {
+ var g=local.get(i,null);
+ if(g) {
+ if(!g.unique_name) {
+ g.unique_name=prefix+i;
+ save_grammar(g)
+ }
+ //form.append(g.unique_name+".json",JSON.stringify(g));
+ form[encodeURIComponent(g.unique_name+".json")]=JSON.stringify(g)
+ }
+ }
+ //debug("Upload to "+prefix);
+ ajax_http_post("/cloud","command=upload"+encodeArgs(form),upload3,cont)
+ }
+
+ with_dir(upload2);
+}
+
+function download_json() {
+ var dir=local.get("dir");
+ var index=grammar_index();
+ var downloading=0;
+
+ function get_list(ok,err) { gfcloud("ls",{},ok,err) }
+
+ function get_file(file,ok,err) {
+ downloading++;
+ gfcloud("download",{file:encodeURIComponent(file)},ok,err);
+ }
+
+ function file_failed(errormsg,status) {
+ debug(errormsg)
+ downloading--;
+ }
+ function file_downloaded(grammar) {
+ downloading--;
+ var newg=JSON.parse(grammar);
+ debug("Downloaded "+newg.unique_name)
+ var i=index[newg.unique_name];
+ if(i!=undefined) merge_grammar(i,newg)
+ else {
+ debug("New")
+ newg.index=null;
+ save_grammar(newg);
+ }
+ if(downloading==0) done()
+ }
+
+ function done() {
+ setTimeout(function(){location.href="."},2000);
+ }
+
+ function download_files(ls) {
+ local.put("current",0);
+ if(ls) {
+ //debug("Downloading "+ls);
+ var files=ls.split(" ");
+ cleanup_deleted(files);
+ for(var i in files) get_file(files[i],file_downloaded,file_failed);
+ }
+ else {
+ debug("No grammars in the cloud")
+ done()
+ }
+ }
+
+ get_list(download_files);
+}
+
+function link_directories(newdir,cont) {
+ gfcloud("link_directories",{newdir:newdir},cont)
+}
+
+/* -------------------------------------------------------------------------- */
+
+// Request GF cloud service
+function gfcloud(cmd,args,cont,err) {
+ with_dir(function(dir) {
+ var enc=encodeURIComponent;
+ var url="/cloud?dir="+enc(dir)+"&command="+enc(cmd)+encodeArgs(args)
+ ajax_http_get(url,cont,err)
+ })
+}
+
+// Send a command to the GF shell
+function gfshell(cmd,cont) {
+ with_dir(function(dir) {
+ var enc=encodeURIComponent;
+ ajax_http_get("/gfshell?dir="+enc(dir)+"&command="+enc(cmd),cont)
+ })
+}
+
+// Check the syntax of an expression
+function check_exp(s,cont) {
+ function check(gf_message) {
+ //debug("cc "+s+" = "+gf_message);
+ cont(/parse error/.test(gf_message) ? "parse error" : null);
+ }
+ gfshell("cc "+s,check);
+}
diff --git a/src/www/gfse/editor.css b/src/www/gfse/editor.css
new file mode 100644
index 000000000..ef12fe8f5
--- /dev/null
+++ b/src/www/gfse/editor.css
@@ -0,0 +1,88 @@
+body { background: #eee; }
+h1 { font-size: 175%; }
+h1,h2,h3,h4,small { font-family: sans-serif; }
+h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; }
+
+#editor { max-width: 50em; }
+div.home, div.grammar { border: 1px solid black; background: #9df; }
+div.home { padding: 5px; }
+div.files { margin: 0 8px 8px 8px; }
+
+div#file { border: 2px solid #009; border-top-width: 0; }
+pre.plain { border: 2px solid #009; }
+div#file, pre.plain { background: white; padding: 0.6ex; }
+
+.slideshow .hidden { display: none; }
+
+img.cloud, img.right, div.right, div.modtime { float: right; }
+.modtime { color: #999; white-space: nowrap; }
+
+div.namebar { background: #9df; }
+div.namebar table { width: 100%; }
+.namebar h3, .home h3 { margin: 0; color: #009; }
+
+td.right { text-align: right; }
+
+.kw { font-weight: bold; font-family: sans-serif; color: #009; }
+.sep { font-weight: bold; color: #009; }
+
+div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; }
+
+div.fun:hover, div.param:hover, div.lincat:hover, div.oper:hover, div.lin:hover,
+div.template:hover
+{ background: #def;}
+
+.lin input[type=button],
+.template input[type=button] { float: right; clear: right; margin: 0; }
+.exb_output { background: #dfd; float: right; margin: 0 10px; }
+
+.more, .delete { font-weight: bold; font-family: sans-serif; }
+.more, .delete, .edit { cursor: pointer; }
+
+.hover .more, .hover .delete, .hover .edit { visibility: hidden }
+
+.hover .hidden, .nohover .ifhover { display: none; }
+
+.editable:hover, .deletable:hover { background: #ff9; }
+
+.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete
+ { visibility: visible; }
+
+.more { color: green; }
+.edit { color: orange; }
+.delete { color: red; }
+.error_message,.inError { color: red; }
+.template, .template .sep { color: #999; }
+form { display: inline-block; }
+
+table.tabs {
+ width: 100%;
+ border-width: 0; border-spacing: 0; empty-cells: show;
+}
+
+table.tabs td { text-align: center; border: 2px solid #009; padding: 2px; }
+table.tabs td.active { background: white; border-bottom-width: 0; }
+table.tabs td.inactive {
+ background: #cef;
+ border-top-color: #66c; border-left-color: #66c; border-right-color: #66c;
+}
+
+ table.tabs td.gap
+{ border-top-width: 0; border-left-width: 0; border-right-width: 0; }
+
+table.tabs input[type=button] {
+ border: 0;
+ background: inherit;
+ color: #009;
+ font-size: inherit;
+ font-weight: bold;
+ /*text-decoration: underline;*/
+}
+
+input.string_edit { font-family: inherit; font-size: inherit; }
+
+ul.languages { -moz-column-width: 20em; }
+
+li { margin-top: 0.5ex; margin-bottom: 0.5ex; }
+
+#sharing h1, #sharing .footer { display: none; } \ No newline at end of file
diff --git a/src/www/gfse/editor.js b/src/www/gfse/editor.js
new file mode 100644
index 000000000..859f33a04
--- /dev/null
+++ b/src/www/gfse/editor.js
@@ -0,0 +1,1114 @@
+
+
+var editor=element("editor");
+
+/* -------------------------------------------------------------------------- */
+
+function initial_view() {
+ var current=local.get("current");
+ if(current>0) open_grammar(current-1);
+ else draw_grammar_list();
+ //debug(local.get("dir","no server directory yet"));
+}
+
+function draw_grammar_list() {
+ local.put("current",0);
+ editor.innerHTML="";
+ var uploaded=local.get("dir") && local.get("json_uploaded");
+ var cloud_upload=
+ a(jsurl("upload_json()"),
+ [node("img",{"class":"cloud",
+ src:"P/1306856253_weather_06.png",alt:"[Up Cloud]",
+ title: uploaded
+ ? "Click to upload grammar updates to the cloud"
+ : "Click to store your grammars in the cloud"},
+ [])]);
+ var home=div_class("home",[node("h3",{},
+ [text("Your grammars"),cloud_upload])]);
+ if(uploaded) {
+ var cloud_download=
+ a(jsurl("download_json()"),
+ [node("img",{"class":"cloud",
+ src:"P/1307545089_weather_04.png",alt:"[Down Cloud]",
+ title:"Click to download grammar updates from the cloud"},
+ [])]);
+ insertAfter(cloud_download,cloud_upload);
+ }
+ editor.appendChild(home)
+ var gs=ul([]);
+ function del(i) { return function () { delete_grammar(i); } }
+ function clone(i) { return function (g,b) { clone_grammar(i); } }
+ for(var i=0;i<local.count;i++) {
+ var grammar=local.get(i,null);
+ if(grammar && grammar.basename) {
+ var link=a(jsurl("open_grammar("+i+")"),[text(grammar.basename)]);
+ gs.appendChild(
+ node("li",{"class":"extensible"},
+ [deletable(del(i),link,"Delete this grammar"),
+ more(grammar,clone(i),"Clone this grammar")]))
+ }
+ }
+ if(local.get("count",null)==null)
+ home.appendChild(text("You have not created any grammars yet."));
+ else if(local.count==0)
+ home.appendChild(text("Your grammar list is empty."));
+ home.appendChild(gs);
+
+ home.appendChild(
+ ul([li([a(jsurl("new_grammar()"),[text("New grammar")])])]));
+ //editor.appendChild(text(local.count));
+ home.appendChild(empty_id("div","sharing"));
+}
+
+function new_grammar() {
+ var g={basename:"Unnamed",
+ abstract:{cats:[],funs:[]},
+ concretes:[]}
+ edit_grammar(g);
+}
+
+function remove_local_grammar(i) {
+ local.remove(i);
+ while(local.count>0 && !local.get(local.count-1))
+ local.count--;
+}
+
+function delete_grammar(i) {
+ var g=local.get(i);
+ var ok=confirm("Do you really want to delete the grammar "+g.basename+"?")
+ if(ok) {
+ remove_local_grammar(i)
+ remove_cloud_grammar(g)
+ initial_view();
+ }
+}
+
+function clone_grammar(i) {
+ var old=local.get(i);
+ var g={basename:old.basename,abstract:old.abstract,concretes:old.concretes}
+ save_grammar(g);
+ draw_grammar_list();
+}
+
+function open_grammar(i) {
+ var g=local.get(i);
+ g.index=i;
+ local.put("current",i+1);
+ edit_grammar(g);
+}
+
+function close_grammar(g) { save_grammar(g); draw_grammar_list(); }
+function reload_grammar(g) { save_grammar(g); edit_grammar(g); }
+
+function save_grammar(g) {
+ if(g.index==null) g.index=local.count++;
+ local.put(g.index,g);
+}
+
+function edit_grammar(g) {
+ editor.innerHTML="";
+ editor.appendChild(draw_grammar(g));
+}
+
+
+function draw_grammar(g) {
+ var files=div_class("files",[draw_filebar(g),draw_file(g)]);
+ return div_class("grammar",[draw_namebar(g,files),files])
+
+}
+
+function draw_namebar(g,files) {
+ return div_class("namebar",
+ [table([tr([td(draw_name(g)),
+ td_right([draw_plainbutton(g,files),
+ upload_button(g),
+ draw_closebutton(g)])])])])
+}
+
+function draw_name(g) {
+ return editable("h3",text(g.basename),g,edit_name,"Rename grammar");
+}
+
+function draw_closebutton(g) {
+ var b=button("X",function(){close_grammar(g);});
+ b.title="Save and Close this grammar";
+ return b;
+}
+
+function draw_plainbutton(g,files) {
+ var b2;
+ function show_editor() { edit_grammar(g); }
+ function show_plain() {
+ files.innerHTML="<pre class=plain>"+show_grammar(g)+"</pre>"
+ b.style.display="none";
+ if(b2) b2.style.display="";
+ else {
+ b2=button("Show editor",show_editor);
+ insertAfter(b2,b);
+ }
+ }
+ var b=button("Show plain",show_plain);
+ b.title="Show plain text representaiton of the grammar";
+ return b;
+}
+
+function upload_button(g) {
+ var b=button("Compile",function(){upload(g);});
+ b.title="Upload the grammar to the server to check it in GF and test it in the minibar";
+ return b;
+}
+
+function lang(code,name) { return { code:code, name:name} }
+function lang1(name) {
+ var ws=name.split("/");
+ return ws.length==1 ? lang(name.substr(0,3),name) : lang(ws[0],ws[1]);
+}
+var languages =
+ map(lang1,"Amharic Arabic Bulgarian Catalan Danish Dutch English Finnish French German Hindi Ina/Interlingua Italian Latin Norwegian Polish Ron/Romanian Russian Spanish Swedish Thai Turkish Urdu".split(" "));
+languages.push(lang("Other","Other"));
+
+var langname={};
+for(var i in languages)
+ langname[languages[i].code]=languages[i].name
+
+function concname(code) { return langname[code] || code; }
+
+function add_concrete(g,el) {
+ var file=element("file");
+ file.innerHTML="";
+ var dc={};
+ for(var i in g.concretes)
+ dc[g.concretes[i].langcode]=true;
+ var list=[]
+ for(var i in languages) {
+ var l=languages[i], c=l.code;
+ if(!dc[c])
+ list.push(li([a(jsurl("add_concrete2("+g.index+",'"+c+"')"),
+ [text(l.name)])]));
+ }
+ var from= g.current>0
+ ? "a copy of "+concname(g.concretes[g.current-1].langcode)
+ :"scratch";
+ file.appendChild(p(text("You are about to create a new concrete syntax by starting from "+from+".")));
+ file.appendChild(p(text("Pick a language for the new concrete syntax:")));
+ file.appendChild(node("ul",{"class":"languages"},list));
+}
+
+function new_concrete(code) {
+ return { langcode:code,params:[],lincats:[],opers:[],lins:[] };
+}
+
+function adjust_opens(cnc,oldcode,code) {
+ for(var oi in cnc.opens)
+ for(var li in rgl_modules)
+ if(cnc.opens[oi]==rgl_modules[li]+oldcode)
+ cnc.opens[oi]=rgl_modules[li]+code;
+}
+
+function add_concrete2(ix,code) {
+ var g=local.get(ix);
+ var cs=g.concretes;
+ var ci;
+ for(var ci=0;ci<cs.length;ci++) if(cs[ci].langcode==code) break;
+ if(ci==cs.length) {
+ if(g.current>0) {
+ cs.push(cs[g.current-1]); // old and new are shared at this point
+ save_grammar(g); // serialization loses sharing
+ g=local.get(ix); // old and new are separate now
+ var oldcode=cs[g.current-1].langcode;
+ var cnc=g.concretes[ci];
+ cnc.langcode=code;
+ adjust_opens(cnc,oldcode,code);
+ timestamp(cnc)
+ }
+ else
+ cs.push(new_concrete(code))
+ save_grammar(g);
+ }
+ open_concrete(g,ci);
+}
+
+function open_abstract(g) { g.current=0; reload_grammar(g); }
+function open_concrete(g,i) { g.current=i+1; reload_grammar(g); }
+
+function td_gap(c) {return wrap_class("td","gap",c); }
+function gap() { return td_gap(text(" ")); }
+
+function tab(active,link) {
+ return wrap_class("td",active ? "active" : "inactive",link);
+}
+
+function delete_concrete(g,ci) {
+ var c=g.concretes[ci];
+ var ok=c.params.length==0 && c.lincats.length==0 && c.opers.length==0
+ && c.lins.length==0
+ || confirm("Do you really want to delete the concrete syntax for "+
+ concname(c.langcode)+"?");
+ if(ok) {
+ g.concretes=delete_ix(g.concretes,ci)
+ if(g.current && g.current-1>=ci) g.current--;
+ reload_grammar(g);
+ }
+}
+
+function draw_filebar(g) {
+ var cur=(g.current||0)-1;
+ var filebar = empty_class("tr","extensible")
+ filebar.appendChild(gap());
+ filebar.appendChild(
+ tab(cur== -1,button("Abstract",function(){open_abstract(g);})));
+ var cs=g.concretes;
+ function del(ci) { return function() { delete_concrete(g,ci); }}
+ function open_conc(i) { return function() {open_concrete(g,1*i); }}
+ for(var i in cs) {
+ filebar.appendChild(gap());
+ filebar.appendChild(
+ tab(i==cur,deletable(del(i),button(concname(cs[i].langcode),open_conc(i)),"Delete this concrete syntax")));
+ }
+ filebar.appendChild(td_gap(more(g,add_concrete,"Add a concrete syntax")));
+ return wrap_class("table","tabs",filebar);
+}
+
+function draw_file(g) {
+ return g.current>0 // && g.current<=g.concretes.length
+ ? draw_concrete(g,g.current-1)
+ : draw_abstract(g);
+}
+
+function draw_startcat(g) {
+ var abs=g.abstract;
+ var startcat = abs.startcat || abs.cats[0];
+ function opt(cat) { return option(cat,cat); }
+ var m= node("select",{},map(opt,abs.cats));
+ m.value=startcat;
+ m.onchange=function() {
+ if(m.value!=abs.startcat) {
+ abs.startcat=m.value;
+ timestamp(abs);
+ save_grammar(g);
+ }
+ }
+ return indent([kw("flags startcat"),sep(" = "),m]);
+}
+
+function draw_abstract(g) {
+ var kw_cat = kw("cat");
+ kw_cat.title = "The categories (nonterminals) of the grammar are enumerated here. [C.3.2]";
+ var kw_fun = kw("fun");
+ kw_fun.title = "The functions (productions) of the grammar are enumerated here. [C.3.4]";
+ var flags=g.abstract.startcat || g.abstract.cats.length>1
+ ? draw_startcat(g)
+ : text("");
+ function sort_funs() {
+ g.abstract.funs=sort_list(this,g.abstract.funs,"name");
+ timestamp(g.abstract);
+ save_grammar(g);
+ }
+ return div_id("file",
+ [kw("abstract "),ident(g.basename),sep(" = "),
+ draw_timestamp(g.abstract),
+ flags,
+ indent([extensible([kw_cat,
+ indent(draw_cats(g))]),
+ extensible([kw_fun,
+ indent_sortable(draw_funs(g),sort_funs)])])]);
+}
+
+function add_cat(g,el) {
+ function add(s) {
+ var cats=s.split(/\s*(?:\s|[;])\s*/); // allow separating spaces or ";"
+ if(cats.length>0 && cats[cats.length-1]=="") cats.pop();
+ for(var i in cats) {
+ var err=check_name(cats[i],"Category");
+ if(err) return err;
+ }
+ for(var i in cats) g.abstract.cats.push(cats[i]);
+ timestamp(g.abstract);
+ reload_grammar(g);
+ return null;
+ }
+ string_editor(el,"",add);
+}
+
+function delete_cat(g,ix) {
+ with(g.abstract) cats=delete_ix(cats,ix);
+ timestamp(g.abstract);
+ reload_grammar(g);
+}
+
+function rename_cat(g,el,cat) {
+ function ren(newcat) {
+ if(newcat!="" && newcat!=cat) {
+ var err=check_name(newcat,"Category");
+ if(err) return err;
+ var dc=defined_cats(g);
+ if(dc[newcat]) return newcat+" is already in use";
+ g=rename_category(g,cat,newcat);
+ timestamp(g.abstract);
+ reload_grammar(g);
+ }
+ return null;
+ }
+ string_editor(el,cat,ren);
+}
+
+function draw_cats(g) {
+ var cs=g.abstract.cats;
+ var es=[];
+ var defined={};
+ function eident(cat) {
+ function ren(g,el) { rename_cat(g,el,cat); }
+ return editable("span",ident(cat),g,ren,"Rename category");
+ }
+ function check(cat,el) {
+ return ifError(defined[cat],"Same category named twice",el);
+ }
+ function del(i) { return function() { delete_cat(g,i); }}
+ for(var i in cs) {
+ es.push(deletable(del(i),check(cs[i],eident(cs[i])),"Delete this category"));
+ defined[cs[i]]=true;
+ es.push(sep("; "));
+ }
+ es.push(more(g,add_cat,"Add more categories"));
+ return es;
+}
+
+function add_fun(g,el) {
+ function add(s) {
+ var p=parse_fun(s);
+ if(p.ok) {
+ g.abstract.funs.push(p.ok);
+ timestamp(g.abstract);
+ reload_grammar(g);
+ return null;
+ }
+ else
+ return p.error
+ }
+ string_editor(el,"",add);
+}
+
+function edit_fun(i) {
+ return function (g,el) {
+ function replace(s) {
+ var p=parse_fun(s);
+ if(p.ok) {
+ var old=g.abstract.funs[i];
+ g.abstract.funs[i]=p.ok;
+ if(p.ok.name!=old.name) g=rename_function(g,old.name,p.ok.name);
+ if(show_type(p.ok.type)!=show_type(old.type))
+ g=change_lin_lhs(g,p.ok);
+ timestamp(g.abstract);
+ reload_grammar(g);
+ return null;
+ }
+ else
+ return p.error;
+ }
+ string_editor(el,show_fun(g.abstract.funs[i]),replace);
+ }
+}
+
+function delete_fun(g,ix) {
+ with(g.abstract) funs=delete_ix(funs,ix);
+ timestamp(g.abstract);
+ reload_grammar(g);
+}
+
+function draw_funs(g) {
+ var funs=g.abstract.funs;
+ var es=[];
+ var dc=defined_cats(g);
+ var df={};
+ function del(i) { return function() { delete_fun(g,i); }}
+ function draw_efun(i,df) {
+ return editable("span",draw_fun(funs[i],dc,df),g,edit_fun(i),"Edit this function");
+ }
+ for(var i in funs) {
+ es.push(node_sortable("fun",funs[i].name,[deletable(del(i),draw_efun(i,df),"Delete this function")]));
+ df[funs[i].name]=true;
+ }
+ es.push(more(g,add_fun,"Add a new function"));
+ return es;
+}
+
+function draw_fun(fun,dc,df) {
+ function check(el) {
+ return ifError(dc[fun.name],
+ "Function names must be distinct from category names",
+ ifError(df[fun.name],"Same function defined twice",el));
+ }
+ return node("span",{},
+ [check(ident(fun.name)),sep(" : "),draw_type(fun.type,dc)]);
+}
+
+function draw_type(t,dc) {
+ var el=empty("span");
+ function check(t,el) {
+ return ifError(!dc[t],"Undefined category",el);
+ }
+ for(var i in t) {
+ if(i>0) el.appendChild(sep(" → "));
+ el.appendChild(check(t[i],ident(t[i])));
+ }
+ return el;
+}
+
+function edit_name(g,el) {
+ function change_name(name) {
+ if(name!=g.basename && name!="") {
+ var err=check_name(name,"Grammar");
+ if(err) return err;
+ g.basename=name
+ reload_grammar(g);
+ }
+ return null;
+ }
+ string_editor(el,g.basename,change_name)
+}
+/* -------------------------------------------------------------------------- */
+
+function draw_concrete(g,i) {
+ var conc=g.concretes[i];
+ function edit_langcode(g,el) {
+ function change_langcode(code) {
+ var err=check_name(g.basename+code,"Name of concrete syntax");
+ if(err) return err;
+ adjust_opens(conc,conc.langcode,code);
+ conc.langcode=code;
+ timestamp(conc);
+ reload_grammar(g);
+ }
+ string_editor(el,conc.langcode,change_langcode)
+ }
+ var kw_lincat=kw("lincat")
+ kw_lincat.title="The linearization type for each catagory in the abstract syntax is given here. [C.3.8]"
+ var kw_lin=kw("lin")
+ kw_lin.title="The linearization function for each function in the abstract syntax is given here. [C.3.9]"
+ var kw_param=kw("param")
+ kw_param.title="Parameter type definitions can be added here. [C.3.12]"
+ var kw_oper=kw("oper")
+ kw_oper.title="Operation definitions can be added here. [C.3.14]"
+ return div_id("file",
+ [kw("concrete "),
+ ident(g.basename),
+ editable("span",ident(conc.langcode),g,
+ edit_langcode,"Change language"),
+ kw(" of "),ident(g.basename),sep(" = "),
+ draw_timestamp(conc),
+ indent([extensible([kw("open "),draw_opens(g,i)])]),
+ indent([kw_lincat,draw_lincats(g,i)]),
+ indent([kw_lin,draw_lins(g,i)]),
+ indent([extensible([kw_param,draw_params(g,i)])]),
+ indent([extensible([kw_oper,draw_opers(g,i)])]),
+ exb_extra(g,i)
+ ])
+}
+
+var rgl_modules=["Paradigms","Syntax","Lexicon","Extra"];
+
+function add_open(ci) {
+ return function (g,el) {
+ var conc=g.concretes[ci];
+ var os=conc.opens;
+ var ds={};
+ for(var i in os) ds[os[i]]=true;
+ var list=[]
+ for(var i in rgl_modules) {
+ var b=rgl_modules[i], m=b+conc.langcode;
+ if(!ds[m])
+ list.push(li([a(jsurl("add_open2("+g.index+","+ci+",'"+m+"')"),
+ [text(m)])]));
+ }
+ if(list.length>0) {
+ var file=element("file");
+ file.innerHTML="";
+ file.appendChild(p(text("Pick a resource library module to open:")));
+ file.appendChild(node("ul",{},list));
+ }
+ }
+}
+
+function add_open2(ix,ci,m) {
+ var g=local.get(ix);
+ var conc=g.concretes[ci];
+ conc.opens || (conc.opens=[]);
+ conc.opens.push(m);
+ timestamp(conc);
+ save_grammar(g);
+ open_concrete(g,ci);
+}
+
+function delete_open(g,ci,ix) {
+ with(g.concretes[ci]) opens=delete_ix(opens,ix);
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+}
+
+function draw_opens(g,ci) {
+ var conc=g.concretes[ci];
+ var os=conc.opens || [] ;
+ var es=[];
+ function del(i) { return function() { delete_open(g,ci,i); }}
+ var first=true;
+ for(var i in os) {
+ if(!first) es.push(sep(", "))
+ es.push(deletable(del(i),ident(os[i]),"Don't open this module"));
+ first=false;
+ }
+ es.push(more(g,add_open(ci),"Open more modules"));
+ return indent(es);
+}
+
+function draw_param(p,dp) {
+ function check(el) {
+ return ifError(dp[p.name],"Same parameter type defined twice",el);
+ }
+ return node("span",{},[check(ident(p.name)),sep(" = "),text(p.rhs)]);
+}
+
+function add_param(g,ci,el) {
+ function add(s) {
+ var p=parse_param(s);
+ if(p.ok) {
+ g.concretes[ci].params.push(p.ok);
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+ return null;
+ }
+ else
+ return p.error
+ }
+ string_editor(el,"",add);
+}
+
+function edit_param(ci,i) {
+ return function (g,el) {
+ function replace(s) {
+ var p=parse_param(s);
+ if(p.ok) {
+ g.concretes[ci].params[i]=p.ok;
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+ return null;
+ }
+ else
+ return p.error;
+ }
+ string_editor(el,show_param(g.concretes[ci].params[i]),replace);
+ }
+}
+
+
+function delete_param(g,ci,ix) {
+ with(g.concretes[ci]) params=delete_ix(params,ix);
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+}
+
+function draw_params(g,ci) {
+ var conc=g.concretes[ci];
+ conc.params || (conc.params=[]);
+ var params=conc.params;
+ var es=[];
+ var dp={};
+ function del(i) { return function() { delete_param(g,ci,i); }}
+ function draw_eparam(i,dp) {
+ return editable("span",draw_param(params[i],dp),g,edit_param(ci,i),"Edit this parameter type");
+ }
+ for(var i in params) {
+ es.push(div_class("param",[deletable(del(i),draw_eparam(i,dp),"Delete this parameter type")]));
+ dp[params[i].name]=true;
+ }
+ es.push(more(g,function(g,el) { return add_param(g,ci,el)},
+ "Add a new parameter type"));
+ return indent(es);
+}
+
+function delete_lincat(g,ci,cat) {
+ var i;
+ var c=g.concretes[ci];
+ for(i=0;i<c.lincats.length && c.lincats[i].cat!=cat;i++);
+ if(i<c.lincats.length) c.lincats=delete_ix(c.lincats,i);
+ timestamp(c);
+ reload_grammar(g);
+}
+
+function draw_lincats(g,i) {
+ var conc=g.concretes[i];
+ function edit(c) {
+ return function(g,el) {
+ function check(s,cont) {
+ function check2(msg) {
+ if(!msg) {
+ if(c.template) conc.lincats.push({cat:c.cat,type:s});
+ else c.type=s;
+ reload_grammar(g);
+ }
+ cont(msg);
+ }
+ check_exp(s,check2);
+ }
+ string_editor(el,c.type,check,true)
+ }
+ }
+ function del(c) { return function() { delete_lincat(g,i,c); } }
+ function dlc(c,cls) {
+ var t=editable("span",text_ne(c.type),g,edit(c),"Edit lincat for "+c.cat);
+ return node("span",{"class":cls},
+ [ident(c.cat),sep(" = "),t]);
+ }
+ var dc=defined_cats(g);
+ function draw_lincat(c) {
+ var cat=c.cat;
+ var err=!dc[cat];
+ var l1=dlc(c,"lincat");
+ var l2= deletable(del(cat),l1,"Delete this lincat");
+ var l=ifError(err,"lincat for undefined category",l2);
+ delete dc[cat];
+ return node_sortable("lincat",cat,[l]);
+ }
+ function dtmpl(c) {
+ return wrap("div",dlc({cat:c,type:"",template:true},"template")); }
+ var lcs=map(draw_lincat,conc.lincats);
+ for(var c in dc)
+ lcs.push(dtmpl(c));
+ function sort_lincats() {
+ conc.lincats=sort_list(this,conc.lincats,"cat");
+ timestamp(conc);
+ save_grammar(g);
+ }
+ return indent_sortable(lcs,sort_lincats);
+}
+
+/* -------------------------------------------------------------------------- */
+
+function draw_oper(p,dp) {
+ function check(el) {
+ return ifError(dp[p.name],"Same operator definition defined twice",el);
+ }
+ return node("span",{},[check(ident(p.name)),text(" "),text(p.rhs)]);
+}
+
+function check_oper(s,ok,err) {
+ var p=parse_oper(s);
+ function check2(msg) {
+ if(msg) err(msg);
+ else ok(p.ok)
+ }
+ if(p.ok) {
+ // Checking oper syntax by checking an expression with a local
+ // definition. Some valid opers will be rejected!!
+ var e=p.ok.name+" where { "+p.ok.name+" "+p.ok.rhs+" }";
+ check_exp(e,check2);
+ }
+ else
+ err(p.error);
+}
+
+function add_oper(g,ci,el) {
+ function check(s,cont) {
+ function ok(oper) {
+ g.concretes[ci].opers.push(oper);
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+ cont(null);
+ }
+ check_oper(s,ok,cont)
+ }
+ string_editor(el,"",check,true);
+}
+
+function edit_oper(ci,i) {
+ return function (g,el) {
+ function check(s,cont) {
+ function ok(oper) {
+ g.concretes[ci].opers[i]=oper;
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+ cont(null);
+ }
+ check_oper(s,ok,cont)
+ }
+ string_editor(el,show_oper(g.concretes[ci].opers[i]),check,true);
+ }
+}
+
+function delete_oper(g,ci,ix) {
+ with(g.concretes[ci]) opers=delete_ix(opers,ix);
+ timestamp(g.concretes[ci]);
+ reload_grammar(g);
+}
+
+function draw_opers(g,ci) {
+ var conc=g.concretes[ci];
+ conc.opers || (conc.opers=[]);
+ var opers=conc.opers;
+ var es=[];
+ var dp={};
+ function del(i) { return function() { delete_oper(g,ci,i); }}
+ function draw_eoper(i,dp) {
+ return editable("span",draw_oper(opers[i],dp),g,edit_oper(ci,i),"Edit this operator definition");
+ }
+ for(var i in opers) {
+ es.push(node_sortable("oper",opers[i].name,
+ [deletable(del(i),draw_eoper(i,dp),
+ "Delete this operator definition")]));
+ dp[opers[i].name]=true;
+ }
+ es.push(more(g,function(g,el) { return add_oper(g,ci,el)},
+ "Add a new operator definition"));
+ function sort_opers() {
+ conc.opers=sort_list(this,conc.opers,"name");
+ timestamp(conc);
+ save_grammar(g);
+ }
+ return indent_sortable(es,sort_opers);
+}
+
+function delete_lin(g,ci,fun) {
+ var i;
+ var c=g.concretes[ci];
+ for(i=0;i<c.lins.length && c.lins[i].fun!=fun;i++);
+ if(i<c.lins.length) c.lins=delete_ix(c.lins,i);
+ timestamp(c);
+ reload_grammar(g);
+}
+
+/* -------------------------------------------------------------------------- */
+function arg_names(type) {
+ function lower(s) { return s.toLowerCase(); }
+ var names=map(lower,type);
+ names.pop(); // remove result type
+ var n,count={},use={};
+ for(var i in names) n=names[i],count[n]=0,use[n]=0;
+ for(var i in names) count[names[i]]++;
+ function unique(n) {
+ return count[n]>1 ? n+(++use[n]) : n;
+ }
+ return map(unique,names);
+}
+
+function draw_lins(g,ci) {
+ var conc=g.concretes[ci];
+ function edit(f) {
+ return function(g,el) {
+ function check(s,cont) {
+ function check2(msg) {
+ if(!msg) {
+ if(f.template)
+ conc.lins.push({fun:f.fun,args:f.args,lin:s});
+ else { f.lin=s; f.eb_lin=null; }
+ reload_grammar(g);
+ }
+ cont(msg);
+ }
+ check_exp(s,check2);
+ }
+ string_editor(el,f.lin,check,true)
+ }
+ }
+ function del(fun) { return function () { delete_lin(g,ci,fun); } }
+ function dl(f,cls) {
+ var l=[ident(f.fun)]
+ for(var i in f.args) {
+ l.push(text(" "));
+ l.push(ident(f.args[i]));
+ }
+ l.push(sep(" = "));
+ var t=editable("span",text_ne(f.lin),g,edit(f),"Edit lin for "+f.fun);
+ appendChildren(t,exb_linbuttons(g,ci,f));
+ l.push(t);
+ return node("span",{"class":cls},l);
+ }
+ var df=defined_funs(g);
+ function draw_lin(f) {
+ var fun=f.fun;
+ var err= !df[fun];
+ var l= deletable(del(fun),dl(f,"lin"),"Delete this linearization function")
+ var l=ifError(err,"Function "+fun+" is not part of the abstract syntax",l);
+ delete df[fun];
+ return node_sortable("lin",fun,[l]);
+ }
+ function largs(f) {
+ var funs=g.abstract.funs;
+ for(var i=0;i<funs.length && funs[i].name!=f;i++);
+ return arg_names(funs[i].type);
+ }
+ function dtmpl(f) {
+ return div_class("template",
+ [dl({fun:f,args:largs(f),lin:"",template:true},"template")]);
+ }
+ function sort_lins() {
+ conc.lins=sort_list(this,conc.lins,"fun");
+ timestamp(conc);
+ save_grammar(g);
+ }
+ var ls=map(draw_lin,conc.lins);
+ for(var f in df)
+ ls.push(dtmpl(f));
+ return indent_sortable(ls,sort_lins);
+}
+
+/* -------------------------------------------------------------------------- */
+
+function find_langcode(concs,langcode) {
+ for(var ci in concs)
+ if(concs[ci].langcode==langcode)
+ return concs[ci];
+ return null;
+}
+
+function cleanup_deleted(files) {
+ var keep={}
+ for(var i in files) keep[files[i]]=true;
+ //debug("cleanup_deleted "+JSON.stringify(files))
+ //debug("keep "+JSON.stringify(keep))
+ for(var i=0;i<local.count;i++) {
+ var g=local.get(i,null)
+ if(g && g.unique_name && !keep[g.unique_name+".json"]) {
+ debug("cleanup "+i+" "+g.unique_name);
+ remove_local_grammar(i)
+ }
+ }
+}
+
+function grammar_index() {
+ var index={}
+ var count=local.count
+ for(var i=0;i<count;i++) {
+ var g=local.get(i,null)
+ if(g && g.unique_name) index[g.unique_name]=i
+ }
+ return index
+}
+
+function merge_grammar(i,newg) {
+ var oldg=local.get(i);
+ var keep="";
+ debug("Merging at "+i);
+ if(oldg) {
+ oldg.basename=newg.basename;
+ if(newg.abstract.timestamp<oldg.abstract.timestamp) {
+ newg.abstract=newg.abstract
+ keep+=" "+oldg.basename
+ }
+ for(var ci in newg.concretes) {
+ var conc=newg.concretes[ci];
+ var oldconc=find_langcode(oldg.concretes,conc.langcode);
+ if(oldconc && conc.timestamp<oldconc.timestamp) {
+ newg.concretes[ci]=oldconc;
+ keep+=" "+oldg.basename+conc.langcode;
+ }
+ }
+ }
+ local.put(i,newg)
+ return keep;
+}
+
+function timestamp(obj,prop) {
+ obj[prop || "timestamp"]=Date.now();
+}
+
+function draw_timestamp(obj) {
+ var t=obj.timestamp;
+ return node("small",{"class":"modtime"},
+ [text(t ? " -- "+new Date(t).toLocaleString() : "")]);
+}
+
+/* -------------------------------------------------------------------------- */
+
+function delete_ix(old,ix) {
+ var a=[];
+ for(var i in old) if(i!=ix) a.push(old[i]);
+ return a;
+}
+
+function sort_list(list,olditems,key) {
+ var items=[];
+ function find(fun) {
+ for(var i=0;i<olditems.length;i++)
+ if(olditems[i][key]==fun) return olditems[i];
+ return null;
+ }
+ for(var el=list.firstChild;el;el=el.nextSibling) {
+ var name=el.getAttribute("ident")
+ if(name) {
+ var old=find(name);
+ if(old) items.push(old)
+ else debug("Bug: did not find "+name+" while sorting");
+ }
+ }
+ if(items.length==olditems.length)
+ return items;
+ else {
+ debug("Bug: length changed while sorting")
+ return olditems;
+ }
+}
+
+function string_editor(el,init,ok,async) {
+ var p=el.parentNode;
+ function restore() {
+ e.parentNode.removeChild(e);
+ el.style.display="";
+ }
+ function done() {
+ var edited=e.it.value;
+ restore();
+ function cont(msg) { if(msg) start(msg); }
+ if(async) ok(edited,cont)
+ else cont(ok(edited));
+ return false;
+ }
+ function start(msg) {
+ el.style.display="none";
+ m.innerHTML=msg;
+ insertAfter(e,el);
+ e.it.focus();
+ }
+ var m=empty_class("span","error_message");
+ var i=node("input",{"class":"string_edit",name:"it",value:init},[]);
+ if(init.length>10) i.size=init.length+5;
+// var i=node("textarea",{name:"it",rows:"2",cols:"60"},[text(init)]);
+ var e=node("form",{},
+ [i,
+ node("input",{type:"submit",value:"OK"},[]),
+ button("Cancel",restore),
+ text(" "),
+ m])
+ e.onsubmit=done
+ start("");
+}
+
+function ifError(b,msg,el) { return b ? inError(msg,el) : el; }
+
+function inError(msg,el) {
+ return node("span",{"class":"inError",title:msg},[el]);
+}
+
+function kw(txt) { return wrap_class("span","kw",text(txt)); }
+function sep(txt) { return wrap_class("span","sep",text(txt)); }
+function ident(txt) { return wrap_class("span","ident",text(txt)); }
+function indent(cs) { return div_class("indent",cs); }
+
+function indent_sortable(cs,sort) {
+ var n= indent(cs);
+ n.onsort=sort;
+ return n;
+}
+
+function node_sortable(cls,name,ls) {
+ return node("div",{"class":cls,"ident":name},ls);
+}
+
+function extensible(cs) { return div_class("extensible",cs); }
+
+function more(g,action,hint) {
+ var b=node("span",{"class":"more","title":hint || "Add more"},
+ [text(" + ")]);
+ b.onclick=function() { action(g,b); }
+ return b;
+}
+
+function text_ne(s) { // like text(s), but force it to be non-empty
+ return text(s ? s : "\xa0\xa0\xa0")
+}
+
+function editable(tag,cs,g,f,hint) {
+ var b=edit_button(function(){f(g,e)},hint);
+ var e=node(tag,{"class":"editable"},[cs,b]);
+ //e.onclick=b.onclick;
+ return e;
+}
+
+function edit_button(action,hint) {
+ var b=node("span",{"class":"edit","title":hint || "Edit"},[text("%")]);
+ b.onclick=action;
+ return b;
+}
+
+function deletable(del,el,hint) {
+ var b=node("span",{"class":"delete",title:hint || "Delete"},[text("×")])
+ b.onclick=del;
+ return node("span",{"class":"deletable"},[b,el])
+}
+
+function touch_edit() {
+ var b=node("input",{type:"checkbox"},[]);
+ function touch() {
+ document.body.className=b.checked ? "nohover" : "hover";
+ }
+ b.onchange=touch;
+ insertAfter(b,editor);
+ insertAfter(wrap("small",text("Enable editing on touch devices. ")),b);
+}
+/* --- DOM Support ---------------------------------------------------------- */
+
+function div_id(id,cs) { return node("div",{id:id},cs); }
+function div_class(cls,cs) { return node("div",{"class":cls},cs); }
+function a(url,linked) { return node("a",{href:url},linked); }
+function ul(lis) { return node("ul",{},lis); }
+function li(xs) { return node("li",{},xs); }
+function table(rows) { return node("table",{},rows); }
+function td_right(cs) { return node("td",{"class":"right"},cs); }
+function jsurl(js) { return "javascript:"+js; }
+
+function hidden(name,value) {
+ return node("input",{type:"hidden",name:name,value:value},[])
+}
+
+function insertBefore(el,ref) { ref.parentNode.insertBefore(el,ref); }
+
+function insertAfter(el,ref) {
+ ref.parentNode.insertBefore(el,ref.nextSibling);
+}
+/* -------------------------------------------------------------------------- */
+
+function download_from_cloud() {
+ var newdir="/tmp/"+location.hash.substr(1)
+
+ function download2(olddir) {
+ //debug("Starting grammar sharing in the cloud")
+ if(newdir!=olddir) link_directories(newdir,download3)
+ else download4()
+ }
+ function download3() {
+ //debug("Uploading local grammars to cloud");
+ upload_json(download4)
+ }
+ function download4() {
+ //debug("Downloading grammars from the cloud");
+ download_json()
+ }
+
+ with_dir(download2)
+}
+
+/* --- Initialization ------------------------------------------------------- */
+
+//document.body.appendChild(empty_id("div","debug"));
+
+function dir_bugfix() {
+ // remove trailing newline caused by bug in older version
+ var dir=local.get("dir");
+ if(dir) {
+ var n=dir.length;
+ while(dir[dir.length-1]=="\n" || dir[dir.length-1]=="\r")
+ dir=dir.substr(0,dir.length-1)
+ if(dir.length<n) {
+ debug("removing trailing newline")
+ local.put("dir",dir);
+ }
+ //debug("Server directory: "+JSON.stringify(dir))
+ }
+ else debug("No server directory yet")
+}
+
+if(editor) {
+ initial_view();
+ touch_edit();
+ dir_bugfix();
+}
+
+//console.log("hi")
diff --git a/src/www/gfse/example_based.js b/src/www/gfse/example_based.js
new file mode 100644
index 000000000..5922b60ea
--- /dev/null
+++ b/src/www/gfse/example_based.js
@@ -0,0 +1,158 @@
+
+var example_based=[];
+
+/*
+-- cat lincat fun lin fun cat cat
+environ :: ([(CId, CId)],[(CId, Expr)],[((CId, CId), [CId])]) -> Environ
+*/
+function exb_state(g,ci) {
+ var conc=g.concretes[ci]
+ function show_list(show1,xs) {
+ return "["+map(show1,xs).join(",")+"]";
+ }
+ function show_fun(fun) {
+ var t=fun.type
+ var res=t[t.length-1]
+ var args=t.slice(0,length-1);
+ return "(("+fun.name+","+res+"),["+args.join(",")+"])"
+ }
+ function show_lincat(lincat) {
+ return "("+lincat.cat+","+lincat.type+")"
+ }
+ function show_lin(lin) {
+ return "("+lin.fun+","+(lin.eb_lin||"?")+")"
+ }
+ function show_funs(funs) { return show_list(show_fun,funs) }
+ function show_lincats(lincats) { return show_list(show_lincat,lincats); }
+ function show_lins(lins) { return show_list(show_lin,lins) }
+ return "("+show_lincats(conc.lincats)
+ +","+show_lins(conc.lins)
+ +","+show_funs(g.abstract.funs)+")"
+}
+
+function exb_call(g,ci,command,args,cont) {
+ var url=window.exb_url || "exb/exb.fcgi";
+ var q=encodeArgs(args);
+ var cmd="?command="+command+"&state="+encodeURIComponent(exb_state(g,ci))+q;
+ http_get_json(url+cmd,cont)
+}
+
+function ask_possibilities(g,ci) {
+ var conc=g.concretes[ci];
+
+ function show_poss(poss) {
+ //debug("possibilities: "+JSON.stringify(poss))
+ var exready={}
+ for(var i in poss[0]) exready[poss[0][i]]=true;
+ var testable={}
+ for(var i in poss[1]) testable[poss[1][i]]=true;
+ example_based[ci]={exready:exready,testable:testable}
+ conc.example_based=true;
+ conc.example_lang=g.concretes[0].langcode;
+ reload_grammar(g);
+ }
+
+ exb_call(g,ci,"possibilities",{},show_poss)
+}
+
+function exb_extra(g,ci) {
+ var conc=g.concretes[ci];
+ function stop_exb() {
+ conc.example_based=false;
+ reload_grammar(g);
+ }
+
+ function exblangmenu() {
+ function opt(conc) { return option(conc.langcode,conc.langcode); }
+ // skip target language
+ var m =node("select",{},map(opt,g.concretes));
+ m.onchange=function() { conc.example_lang=m.value }
+ return m
+ }
+
+ function ask_poss() { ask_possibilities(g,ci) }
+
+ if(navigator.onLine && conc.example_based && !example_based[ci]) ask_poss();
+ return conc.langcode=="Eng"
+ ? indent([text("Example based editing: "),
+ conc.example_based
+ ? node("span",{},[button("Stop",stop_exb),
+ text(" Example language: "),
+ exblangmenu()
+ ])
+ : button("Start",ask_poss)])
+ : text("")
+}
+
+function exb_linbuttons(g,ci,f) {
+ var conc=g.concretes[ci];
+ var fun=f.fun;
+ var eb=example_based[ci];
+ var exb_output;
+ function fill_example(maybetree) {
+ var tree=maybetree.Just
+ if(tree) {
+ if(f.template)
+ conc.lins.push({fun:f.fun,args:f.args,
+ lin:tree[0],eb_lin:tree[1]});
+ else {
+ f.lin=tree[0];
+ f.eb_lin=tree[1];
+ }
+ ask_possibilities(g,ci)
+ }
+ else exb_output.innerHTML="Bug: no tree found"
+ }
+ function show_example(example){
+ exb_output.innerHTML="";
+ var s=prompt(example[1]);
+ if(s) {
+ var t=function_type(g,fun);
+ var abscat=t[t.length-1]
+ var cat=cat_lincat(conc,abscat)
+ exb_output.innerHTML="...";
+ //server.parse({from:"ParseEng",cat:cat,input:s},fill_example)
+ exb_call(g,ci,"abstract_example",
+ {cat:cat,input:s,
+ params:"["+f.args.join(",")+"]",
+ abstract:example[0]},
+ fill_example)
+ }
+ }
+ function by_example() {
+ var dir=local.get("dir")
+ if(dir) {
+ if(exb_output) {
+ exb_output.innerHTML="...";
+ exb_call(g,ci,"provide_example",
+ {lang:g.basename+conc.example_lang,
+ fun:fun,
+ grammar:dir+"/"+g.basename+".pgf"},
+ show_example)
+ }
+ }
+ else exb_output.innerHTML="Compile the grammar first!"
+ }
+ function show_test(txt) {
+ exb_output.innerHTML="";
+ exb_output.appendChild(text(txt))
+ }
+ function test_it(b) {
+ if(exb_output) {
+ exb_output.innerHTML="...";
+ exb_call(g,ci,"test_function",{fun:fun},show_test)
+ }
+ }
+ var buttons=[];
+ if(conc.example_based && eb) {
+ if(eb.exready[fun])
+ buttons.push(button("By example",by_example))
+ if(eb.testable[fun] && f.eb_lin) {
+ var b=button("Test it",test_it);
+ buttons.push(b)
+ }
+ var exb_output=node("span",{"class":"exb_output"},[]);
+ buttons.push(exb_output)
+ }
+ return buttons
+}
diff --git a/src/www/gfse/gf_abs.js b/src/www/gfse/gf_abs.js
new file mode 100644
index 000000000..4efaab89a
--- /dev/null
+++ b/src/www/gfse/gf_abs.js
@@ -0,0 +1,226 @@
+/* Abstract syntax for a small subset of GF grammars in JavaScript */
+
+function defined_cats(g) {
+ var dc={};
+ with(g.abstract)
+ for(var i in cats) dc[cats[i]]=true;
+ return dc;
+}
+
+function defined_funs(g) {
+ var df={};
+ with(g.abstract)
+ for(var i in funs) df[funs[i].name]=true;
+ return df;
+}
+
+function function_type(g,fun) {
+ with(g.abstract)
+ for(var i in funs) if(funs[i].name==fun) return funs[i].type
+ return null;
+}
+
+function cat_lincat(conc,cat) {
+ with(conc)
+ for(var i in lincats) if(lincats[i].cat==cat) return lincats[i].type
+ return null;
+}
+
+function rename_category(g,oldcat,newcat) {
+ function rename_cats(cats) {
+ for(var i in cats) if(cats[i]==oldcat) cats[i]=newcat;
+ }
+ function rename_type(t) {
+ for(var i in t) if(t[i]==oldcat) t[i]=newcat;
+ }
+ function rename_funs(funs) {
+ for(var i in funs) rename_type(funs[i].type)
+ }
+ function rename_abstract(a) {
+ rename_cats(a.cats);
+ rename_funs(a.funs);
+ }
+ function rename_lincat(lc) {
+ if(lc.cat==oldcat) lc.cat=newcat;
+ }
+ function rename_concrete(c) {
+ for(var i in c.lincats) rename_lincat(c.lincats[i]);
+ }
+ function rename_concretes(cs) {
+ for(var i in cs) rename_concrete(cs[i]);
+ }
+ rename_abstract(g.abstract)
+ rename_concretes(g.concretes);
+ return g;
+}
+
+function rename_function(g,oldfun,newfun) {
+ function rename_lin(lin) {
+ if(lin.fun==oldfun) lin.fun=newfun;
+ }
+ function rename_concrete(c) {
+ for(var i in c.lins) rename_lin(c.lins[i]);
+ }
+ for(var i in g.concretes) rename_concrete(g.concretes[i]);
+ return g;
+}
+
+function change_lin_lhs(g,fun) {
+ function change_lin(lin) {
+ if(lin.fun==fun.name) lin.args=arg_names(fun.type);
+ }
+ function change_concrete(c) {
+ for(var i in c.lins) change_lin(c.lins[i]);
+ }
+ for(var i in g.concretes) change_concrete(g.concretes[i]);
+ return g;
+}
+
+/* --- Parsing -------------------------------------------------------------- */
+
+// GF idenfifier syntax:
+var lex_id=/^[A-Za-z][A-Za-z0-9_']*$/
+// See https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions
+
+function check_id(s) { return lex_id.test(s); }
+
+function check_name(s,kind) {
+ return check_id(s)
+ ? null
+ : s+"? "+kind+" names must start with a letter and can contain letters, digits, _ and '"
+}
+
+
+function parse_fun(s) {
+ var ws=s.split(/\s+/);
+ var fun={name:"",type:[]};
+ /* Use a state machine to parse function definitions */
+ /* f : T1 -> ... -> Tn */
+ var state="name";
+ var ok=true;
+ for(var i=0;ok && i<ws.length;i++) {
+ if(ws[i]!="") {
+ switch(state) {
+ case "name": fun.name=ws[i]; state=":"; break;
+ case ":": ok=ws[i]==":"; state="type"; break;
+ case "type": fun.type.push(ws[i]); state="->"; break;
+ case "->": ok=ws[i]=="->"; state="type"; break;
+ }
+ }
+ }
+ var err=check_name(fun.name,"Function");
+ if(err) return {error: err};
+ return ok && state=="->"
+ ? {ok:fun}
+ : { error : "Fun : Cat<sub>1</sub> -> ... -> Cat<sub>n</sub>" }
+}
+
+
+function parse_param(s) {
+ var ws=s.split("=");
+ if(ws.length==2) {
+ var name=ws[0].trim();
+ var err=check_name(name,"Parameter type");
+ return err ? { error:err } : { ok: { name:name,rhs:ws[1].trim() } }
+ }
+ else
+ return { error: "P = C1 | ... | Cn" }
+}
+
+function parse_oper(s) {
+ var i=s.indexOf(" ");
+ var operr = { error: "op = expr" }
+ if(i>0 && i<s.length-1) {
+ var name=s.substr(0,i).trim();
+ var rhs=s.substr(i).trim();
+ var err=check_name(name,"Operator");
+ return err
+ ? {error:err}
+ : rhs!="" ? {ok: {name:name, rhs:rhs}}
+ : operr
+ }
+ else return operr
+
+}
+
+/* --- Print as plain text (normal GF concrete syntax) ---------------------- */
+
+function show_type(t) {
+ var s="";
+ for(var i in t) {
+ if(i>0) s+=" -> ";
+ s+=t[i];
+ }
+ return s;
+}
+
+function show_fun(fun) {
+ return fun.name+" : "+show_type(fun.type);
+}
+
+function show_grammar(g) {
+ return show_abstract(g)+"\n"+show_concretes(g)
+}
+
+function show_abstract(g) {
+// var startcat= g.abstract.cats.length==1 ? g.abstract.cats[0] : g.abstract.startcat;
+ var startcat= g.abstract.startcat || g.abstract.cats[0];
+ return "abstract "+g.basename+" = {\n\n"
+ +"flags coding = utf8 ;\n\n"
+ +show_startcat(startcat)
+ +show_cats(g.abstract.cats)
+ +show_funs(g.abstract.funs)
+ +"}\n";
+}
+
+function show_startcat(startcat) {
+ return startcat ? "flags startcat = "+startcat+";\n\n" : "";
+}
+
+function show_cats(cats) {
+ return cats.length>0 ? "cat\n "+cats.join("; ")+";\n\n" : "";
+}
+
+function show_funs(funs) { return show_list("fun",show_fun,funs); }
+
+function show_concretes(g) {
+ return map(show_concrete(g.basename),g.concretes).join("\n\n");
+}
+
+function show_concrete(basename) {
+ return function(conc) {
+ return "--# -path=.:present\n"
+ + "concrete "+basename+conc.langcode+" of "+basename+" ="
+ +show_opens(conc.opens)
+ +" {\n\nflags coding = utf8 ;\n\n"
+ +show_params(conc.params)
+ +show_lincats(conc.lincats)
+ +show_opers(conc.opers)
+ +show_lins(conc.lins)
+ +"}\n"
+ }
+}
+
+function show_list(kw,show1,list) {
+ return list.length>0
+ ? kw+"\n "+map(show1,list).join(";\n ")+";\n\n"
+ : ""
+}
+
+function show_opens(opens) {
+ return opens && opens.length>0 ? "\n\nopen "+opens.join(", ")+" in" : ""
+}
+
+function show_params(params) { return show_list("param",show_param,params); }
+function show_lincats(lincats) { return show_list("lincat",show_lincat,lincats); }
+function show_opers(opers) { return show_list("oper",show_oper,opers); }
+function show_lins(lins) { return show_list("lin",show_lin,lins); }
+
+
+function show_param(p) { return p.name + " = " + p.rhs; }
+function show_oper(p) { return p.name + " " + p.rhs; }
+function show_lincat(p) { return p.cat + " = " + p.type; }
+
+function show_lin(lin) {
+ return lin.fun + " " + lin.args.join(" ")+ " = " + lin.lin;
+} \ No newline at end of file
diff --git a/src/www/gfse/gfse.manifest b/src/www/gfse/gfse.manifest
new file mode 100644
index 000000000..60c6f3757
--- /dev/null
+++ b/src/www/gfse/gfse.manifest
@@ -0,0 +1,4 @@
+CACHE MANIFEST
+# 5
+NETWORK:
+*
diff --git a/src/www/gfse/grammars.cgi b/src/www/gfse/grammars.cgi
new file mode 100644
index 000000000..9f1aa22a6
--- /dev/null
+++ b/src/www/gfse/grammars.cgi
@@ -0,0 +1,19 @@
+#!/bin/bash
+echo "Content-Type: text/javascript"
+echo ""
+
+case "$QUERY_STRING" in
+ jsonp=*) prefix="${QUERY_STRING#jsonp=}("; suffix=")" ;;
+ *) prefix=""; suffix=""
+esac
+
+echo -n "$prefix"
+sep="["
+for g in *.pgf ; do
+ echo -n "$sep\"$g\""
+ sep=", "
+done
+echo "]$suffix"
+#echo "/*"
+#set
+#echo "*/"
diff --git a/src/www/gfse/index.html b/src/www/gfse/index.html
new file mode 100644
index 000000000..c683a1ebf
--- /dev/null
+++ b/src/www/gfse/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html> <!-- manifest="gfse.manifest" -->
+<head>
+<title>GF online editor for simple multilingual grammars</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
+<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
+
+<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren">
+
+<meta name = "viewport" content = "width = device-width">
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+
+</head>
+
+<body class=hover>
+
+<h2>GF online editor for simple multilingual grammars</h2>
+<div id=editor>
+</div>
+<small class="hidden">
+<span class=more>+</span>=Add an item,
+<span class=delete>×</span>=Delete item,
+<span class=edit>%</span>=Edit item.
+</small>
+<small class="ifhover">Hover over items for hints and editing options.</small>
+
+<noscript>
+This page does not work without JavaScript.
+</noscript>
+
+<hr>
+<div class=modtime><small>
+HTML
+<!-- hhmts start --> Last modified: Mon Oct 10 19:24:05 CEST 2011 <!-- hhmts end -->
+</small></div>
+<a href="about.html">About</a>
+<pre id=debug></pre>
+<script type="text/javascript" src="config.js"></script> <!-- optional -->
+<script type="text/javascript" src="support.js"></script>
+<script type="text/javascript" src="localstorage.js"></script>
+<script type="text/javascript" src="gf_abs.js"></script>
+<script type="text/javascript" src="example_based.js"></script>
+<script type="text/javascript" src="editor.js"></script>
+<script type="text/javascript" src="cloud2.js"></script>
+<script type="text/javascript" src="sort.js"></script>
+</body>
+</html>
diff --git a/src/www/gfse/localstorage.js b/src/www/gfse/localstorage.js
new file mode 100644
index 000000000..31201998c
--- /dev/null
+++ b/src/www/gfse/localstorage.js
@@ -0,0 +1,21 @@
+
+// We use localStorage to store the grammars,
+// see http://diveintohtml5.org/storage.html
+
+var local={
+ prefix:"gf.editor.simple.grammar",
+ get: function (name,def) {
+ var id=this.prefix+name
+ return localStorage[id] ? JSON.parse(localStorage[id]) : def;
+ },
+ put: function (name,value) {
+ var id=this.prefix+name;
+ localStorage[id]=JSON.stringify(value);
+ },
+ remove: function(name) {
+ var id=this.prefix+name;
+ localStorage.removeItem(id);
+ },
+ get count() { return this.get("count",0); },
+ set count(v) { this.put("count",v); }
+}
diff --git a/src/www/gfse/molto.css b/src/www/gfse/molto.css
new file mode 100644
index 000000000..052dda431
--- /dev/null
+++ b/src/www/gfse/molto.css
@@ -0,0 +1,82 @@
+body { color: #413b36;
+ background: #fffcfa;
+ }
+h1 { font-size: 175%; }
+h1,h2,h3,h4,small { font-family: sans-serif; }
+h1,h2,h3,h4,a { color: #5c1a1a; }
+
+h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; }
+
+#editor { max-width: 50em; }
+div.grammar { border: 2px solid #b09779; background: #642121; }
+div.files { margin: 0 8px 8px 8px; }
+
+div#file { border: 2px solid #b0977d; border-top-width: 0; }
+pre.plain { border: 2px solid #b0977d; }
+div#file, pre.plain { background: #fffcfa; padding: 0.6ex; }
+
+.slideshow .hidden { display: none; }
+
+img.right, div.right, div.modtime { float: right; }
+.modtime { color: #999; white-space: nowrap; }
+
+/*div.namebar { background: #642121; }*/
+div.namebar table { width: 100%; }
+.namebar h3 { margin: 0; color: white; }
+
+td.right { text-align: right; }
+
+.kw { font-weight: bold; font-family: sans-serif; color: #642121; }
+.sep { font-weight: bold; color: #642121; }
+
+div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; }
+
+/*
+div.fun, div.param, div.lincat, div.oper, div.lin
+{ padding-left: 2em; text-indent: -2em; }
+*/
+.more, .delete { font-weight: bold; font-family: sans-serif; }
+.more, .delete, .edit { cursor: pointer; }
+
+.hover .more, .hover .delete, .hover .edit { visibility: hidden }
+
+.hover .hidden, .nohover .ifhover { display: none; }
+
+.editable:hover, .deletable:hover { background: #ff9; }
+.namebar .editable:hover { background: #04b; }
+
+.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete
+ { visibility: visible; }
+
+.more { color: green; }
+.edit { color: orange; }
+.delete { color: red; }
+.error_message,.inError { color: red; }
+.template, .template .sep { color: #999; }
+form { display: inline-block; }
+
+table.tabs {
+ width: 100%;
+ border-width: 0; border-spacing: 0; empty-cells: show;
+}
+
+table.tabs td { text-align: center; border: 2px solid #b09779; padding: 2px; }
+table.tabs td.active { background: white; border-bottom-width: 0; }
+table.tabs td.inactive {
+ background: #e1e1e1;
+ border-top-color: #b09779; border-left-color: #b09779; border-right-color: #b09779;
+}
+
+ table.tabs td.gap
+{ border-top-width: 0; border-left-width: 0; border-right-width: 0; }
+
+table.tabs input[type=button] {
+ border: 0;
+ background: inherit;
+ color: #642121;
+ font-size: inherit;
+ font-weight: bold;
+ /*text-decoration: underline;*/
+}
+
+input.string_edit { font-family: inherit; font-size: inherit; } \ No newline at end of file
diff --git a/src/www/gfse/save.hs b/src/www/gfse/save.hs
new file mode 100644
index 000000000..0472ff5e8
--- /dev/null
+++ b/src/www/gfse/save.hs
@@ -0,0 +1,25 @@
+import System(getArgs)
+import CGI(getQuery,string)
+import MUtils(apSnd)
+
+main = save2 =<< getArgs
+
+{-
+save1 [dir] =
+ do fs@[ns,_] <- readIO =<< getContents
+ nes <- save_all fs
+ putStrLn $ unwords nes
+ where
+ save_all [ns,cs] = mapM (write1 dir) (zip ns cs)
+-}
+
+write1 dir (n,c) =
+ do writeFile (dir++"/"++ne) c
+ return ne
+ where
+ ne=if '.' `elem` n then n else n++".gf"
+
+save2 [dir] =
+ do nfs <- getQuery
+ nes <- mapM (write1 dir . apSnd string) nfs
+ putStrLn $ unwords nes
diff --git a/src/www/gfse/share.html b/src/www/gfse/share.html
new file mode 100644
index 000000000..260a7b066
--- /dev/null
+++ b/src/www/gfse/share.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title>Download from Grammar Cloud</title>
+<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud">
+<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO">
+
+</head>
+
+<body>
+<h1><img src="P/1307545048_weather_09.png" alt="">Download from Grammar Cloud</h1>
+
+<pre id=debug></pre>
+
+<hr>
+<address></address>
+<!-- hhmts start --> Last modified: Mon Oct 10 20:29:01 CEST 2011 <!-- hhmts end -->
+<script type="text/javascript" src="support.js"></script>
+<script type="text/javascript" src="localstorage.js"></script>
+<script type="text/javascript" src="gf_abs.js"></script>
+<script type="text/javascript" src="editor.js"></script>
+<script type="text/javascript" src="cloud2.js"></script>
+<script type="text/javascript" src="sort.js"></script>
+<script type="text/javascript">
+download_from_cloud();
+</script>
+</body>
+</html>
diff --git a/src/www/gfse/slideshow.js b/src/www/gfse/slideshow.js
new file mode 100644
index 000000000..307bcd98f
--- /dev/null
+++ b/src/www/gfse/slideshow.js
@@ -0,0 +1,86 @@
+
+var internet_explorer=navigator.appName=="Microsoft Internet Explorer";
+
+/* How to change opacity in IE:
+http://joseph.randomnetworks.com/archives/2006/08/16/css-opacity-in-internet-explorer-ie/
+*/
+
+var set_opacity =
+ internet_explorer
+ ? function(el,o) { el.style.filter="alpha(opacity="+Math.round(o*100)+")";}
+ : function(el,o) { el.style.opacity=o; };
+
+function start_slideshow(img,options) {
+ var p=img.parentNode;
+ if(p.tagName=="A") p=p.parentNode;
+ var is=p.getElementsByTagName("img");
+ if(is.length>1) {
+ var cur=0;
+ var w=img.width;
+ var h=img.height;
+ //p.style.position="relative";
+ p.style.minWidth=w+"px";
+ p.style.minHeight=h+"px";
+ var images=[];
+ for(var i=0;i<is.length;i++) {
+ images[i]=is[i];
+ var c=images[i];
+ if(internet_explorer) c.style.zoom=1;
+ c.style.position="absolute";
+ }
+ var timeout=1000*(options.delay || 5);
+ var ft=options.fade==null ? 1 : options.fade;
+ var tick=function() {
+ var c=images[cur];
+ cur= (cur+1) % images.length;
+ var n=images[cur];
+ set_opacity(n,0);
+ //n.style.position="static";
+ n.style.zIndex=1;
+ n.className="";
+ if(n.width>w) { w=n.width; p.style.minWidth=w+"px"; }
+ if(n.height>h) { h=n.height; p.style.minHeight=h+"px"; }
+ c.style.position="absolute";
+ c.style.zIndex=0;
+ fade(n,0,1,ft,function() {
+ if(c.width>n.width || c.height>n.height) fade(c,1,0,ft,null);
+ else set_opacity(c,0); });
+ //debug.innerHTML=w+"x"+h;
+ //for(var i=0;i<images.length;i++)
+ //debug.appendChild(text(" "+images[i].style.position));
+ }
+ //var debug=document.createElement("div");
+ //p.parentNode.insertBefore(debug,p);
+ //debug.innerHTML=w+"x"+h;
+ setInterval(tick,timeout);
+ }
+ //else alert("No slideshow!");
+}
+
+function fade(el,start,stop,t,after) {
+ // el: which element to fade
+ // start: starting opacity [0..1]
+ // stop: ending opacity [0..1]
+ // t: duration of fade (in seconds), default 1s
+ // after: function to call when done fading, optional
+ var dt=40; // Animation granularity, 1/40ms = 25fps
+ el.step=(stop-start)*dt/(1000*(t==null ? 1 : t));
+ el.stop=stop;
+ //alert("fade "+start+" "+stop+" "+el.step);
+ var done=function() {
+ clearInterval(el.timer);
+ el.timer=null;
+ if(after) after();
+ }
+ var f=function() {
+ var next=el.current+el.step;
+ if(next>=1) { next=1; done(); }
+ if(next<=0) { next=0; done(); }
+ set_opacity(el,next);
+ el.current=next
+ }
+ if(!el.timer) {
+ el.current=start;
+ el.timer=setInterval(f,dt);
+ }
+}
diff --git a/src/www/gfse/upload.cgi b/src/www/gfse/upload.cgi
new file mode 100644
index 000000000..87b868f53
--- /dev/null
+++ b/src/www/gfse/upload.cgi
@@ -0,0 +1,252 @@
+#!/bin/bash
+
+bin=/Users/hallgren/www/bin
+
+charset="UTF-8"
+AUTOHEADER=no
+
+. $bin/cgistart.sh
+export LC_CTYPE="UTF-8"
+style_url="editor.css"
+
+tmp="$documentRoot/tmp"
+
+make_dir() {
+ dir="$(mktemp -d "$tmp/gfse.XXXXXXXXXX")"
+# chmod a+rxw "$dir"
+ chmod a+rx "$dir"
+ cp "grammars.cgi" "$dir"
+}
+
+
+check_grammar() {
+ pagestart "Uploaded"
+# echo "$PATH_INFO"
+ chgrp everyone "$dir"
+ chmod g+ws "$dir"
+ umask 002
+# files=( $(Reg from-url | LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") )
+ files=( $(LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") )
+ gffiles=( )
+ otherfiles=( )
+ for f in ${files[*]} ; do
+ case "$f" in
+ *.gf) gffiles=( ${gffiles[*]} "$f" ) ;;
+ *) otherfiles=( ${otherfiles[*]} "$f" ) ;;
+ esac
+ done
+
+ if [ ${#otherfiles} -gt 0 -a -n "$PATH_INFO" ] ; then
+ echo "Use the following link for shared access to your grammars from multiple devices:"
+ begin ul
+ case "$SERVER_PORT" in
+ 80) port="" ;;
+ *) port=":$SERVER_PORT"
+ esac
+ parent="http://$SERVER_NAME$port${REQUEST_URI%/upload.cgi/tmp/gfse.*}"
+ cloudurl="$parent/share.html#${dir##*/}"
+ li; link "$cloudurl" "$cloudurl"
+ end
+ #begin dl
+ #dt ; echo "◂"; link "javascript:history.back()" "Back to Editor"
+ #end
+ fi
+
+ cd $dir
+ if [ ${#gffiles} -gt 0 ] ; then
+ begin pre
+ echo "gf -s -make ${gffiles[*]}"
+ if gf -s -make ${gffiles[*]} 2>&1 ; then
+ end
+ h3 OK
+ begin dl
+ [ -z "$minibar_url" ] || { dt; echo "▸"; link "$minibar_url?/tmp/${dir##*/}/" "Minibar"; }
+ [ -z "$transquiz_url" ] || { dt; echo "▸"; link "$transquiz_url?/tmp/${dir##*/}/" "Translation Quiz"; }
+ [ -z "$gfshell_url" ] || { dt; echo "▸"; link "$gfshell_url?dir=${dir##*/}" "GF Shell"; }
+ dt ; echo "◂"; link "javascript:history.back()" "Back to Editor"
+
+ end
+ sed=();
+ for pgf in *.pgf ; do
+ sed=("${sed[@]}" -e "s%$pgf%<a href=\"${dir##*/}/$pgf\">$pgf</a>%")
+ done
+ begin pre
+ ls -l *.pgf | sed "${sed[@]}"
+ end
+ else
+ end
+ begin h3 class=error_message; echo Error; end
+ for f in ${gffiles[*]} ; do
+ h4 "$f"
+ begin pre class=plain
+ cat -n "$f"
+ end
+ done
+ fi
+ fi
+ begin div class=footer
+ hr
+ date
+ end
+# begin pre ; env
+ endall
+}
+
+error400() {
+ echo "Status: 400"
+ pagestart "Error"
+ echo "What do you want?"
+ endall
+}
+
+error404() {
+ echo "Status: 404"
+ pagestart "Not found"
+ echo "Not found"
+ endall
+}
+
+if [ -z "$tmp" ] || ! [ -d "$tmp" ] ; then
+ pagestart "Error"
+ begin pre
+ echo "upload.cgi is not properly configured:"
+ if [ -z "$tmp" ] ; then
+ echo "cgiconfig.sh must define tmp"
+ elif [ ! -d "$tmp" ] || [ ! -w "$tmp" ] ; then
+ echo "$tmp must be a writeable directory"
+ fi
+ # cgiconfig.sh should define minibar & gfshell to allow grammars to be tested.
+ endall
+else
+case "$REQUEST_METHOD" in
+ POST)
+ case "$PATH_INFO" in
+ /tmp/gfse.*)
+ style_url="../../$style_url"
+ dir="$tmp/${PATH_INFO##*/}"
+ check_grammar
+ ;;
+ *)
+ make_dir
+ echo >&2 "Using temporary directory $dir"
+ check_grammar
+ rm -rf "$dir"
+ esac
+ ;;
+ GET)
+ case "$QUERY_STRING" in
+ dir) make_dir
+ ContentType="text/plain"
+ cgiheaders
+ echo_n "/tmp/${dir##*/}"
+ ;;
+ ls=*)
+ dir=$(qparse "$QUERY_STRING" ls)
+ case "$dir" in
+ /tmp/gfse.*) # shouldn't allow .. in path !!!
+ path="$documentRoot$dir"
+ if [ -d "$path" ] ; then
+ ContentType="text/plain; charset=$charset"
+ cgiheaders
+ cd "$path"
+ shopt -s nullglob
+ echo_n *-*.json
+ else
+ error404
+ fi
+ ;;
+ *) error400
+ esac
+ ;;
+ rmdir=*)
+ dir=$(qparse "$QUERY_STRING" rmdir)
+ case "$dir" in
+ /tmp/gfse.*) # shouldn't allow .. in path !!!
+ path="$documentRoot$dir"
+ if [ -d "$path" ] ; then
+ ContentType="text/plain; charset=$charset"
+ cgiheaders
+ if [ -h "$path" ] ; then
+ cd "$path"
+ cd ..
+ rm "$path"
+ else
+ cd "$path"
+ shopt -s nullglob
+ rm *.gf *.gfo *-*.json *.pgf grammars.cgi
+ cd ..
+ rmdir "$path"
+ fi
+ newdir=$(qparse "$QUERY_STRING" newdir)
+ case "$newdir" in
+ /tmp/gfse.*) # shouldn't allow .. in path !!!
+ newnode="${newdir##*/}"
+ oldnode="${path##*/}"
+ ln -s "$newnode" "$oldnode"
+ esac
+ else
+ error404
+ fi
+ ;;
+ *) error400
+ esac
+ ;;
+ download=*)
+ file=$(qparse "$QUERY_STRING" download)
+ case "$file" in
+ /tmp/gfse.*/*.json) # shouldn't allow .. in path !!!
+ path="$documentRoot$file"
+ if [ -r "$path" ] ; then
+ ContentType="text/javascript; charset=$charset"
+ cgiheaders
+ cat "$path"
+ else
+ error404
+ fi
+ ;;
+ *) error400
+ esac
+ ;;
+ rm=*)
+ file=$(qparse "$QUERY_STRING" rm)
+ case "$file" in
+ /tmp/gfse.*/*.json) # shouldn't allow .. in path !!!
+ path="$documentRoot$file"
+ if [ -r "$path" ] ; then
+ ContentType="text/javascript; charset=$charset"
+ cgiheaders
+ rm "$path"
+ else
+ error404
+ fi
+ ;;
+ *) error400
+ esac
+ ;;
+ cc=*)
+ # Just to check an expression for syntax errors
+ exp=$(qparse "$QUERY_STRING" cc)
+ ContentType="text/plain; charset=$charset"
+ cgiheaders
+ echo "cc $exp" | GF_RESTRICTED=True gf -run
+ ;;
+ "")
+ case "$PATH_INFO" in
+ /tmp/gfse.*/*.pgf)
+ path="$documentRoot$PATH_INFO"
+ if [ -r $path ] ; then
+ ContentType="application/binary"
+ cgiheaders
+ cat "$path"
+ else
+ error404
+ fi
+ ;;
+ *)
+ error400
+ esac
+ ;;
+ *) error400
+ esac
+esac
+fi
diff --git a/src/www/index.html b/src/www/index.html
new file mode 100644
index 000000000..b1aa24028
--- /dev/null
+++ b/src/www/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+
+<!-- This is the start page served by "gf -server" -->
+
+<title>GF web service</title>
+<h1>GF web service</h1>
+
+<h2>Available web services</h2>
+
+<ul>
+ <li><a href="minibar/minibar.html">Minibar</a>
+ <li><a href="gfse/">GF online editor for simple multilingual grammars</a>
+</ul>
+
+
+<hr>
+
+<a href="http://www.grammaticalframework.org/">Grammatical Framework</a>
diff --git a/src/www/minibar/about.html b/src/www/minibar/about.html
new file mode 100644
index 000000000..69fba5a9d
--- /dev/null
+++ b/src/www/minibar/about.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<html> <head>
+<title>About Minibar</title>
+<link rel=stylesheet type="text/css" href="minibar.css">
+<meta charset="UTF-8">
+</head>
+
+<body>
+<h1>About Minibar</h1>
+
+<a href="minibar.html">Minibar</a> is an alternative implementation of the
+<a href="http://www.grammaticalframework.org/">GF</a> web app
+<a href="http://www.grammaticalframework.org:41296/fridge/">Fridge Poetry</a>.
+It doesn't do everything the original Fridge Poetry does (e.g. drag-and-drop is missing),
+so I refer to it as a minibar rather than a full refrigerator :-)
+
+<p>
+Some implementation details:
+
+<ul class=space>
+ <li>It is implemented directly in JavaScipt. It does not use Google Web Toolkit or any big JavaScript libraries.
+ <li>It has been tested and found to work in the following browsers:
+ <ul>
+ <li>On the Mac: Firefox 3.5 &amp; 3.6, Safari 4.0, Opera 10.10 and
+ Google Chrome 4.0.249.49.
+ <li>On Linux: Firefox 3.0.18 & 3.5, Opera 10.10.
+ <li>On the Android Dev Phone: Android Mobile Safari 3.0.4 & 3.1.2
+ and Android Opera Mini 4.2.
+ </ul>
+ It does not seem work in Internet Explorer 7
+ (there are both styling and scripting issues).
+ There seems to be some rendering bugs in Chrome 5.0.342.9 β.
+ <li>The implementation consist of two JavaScript files:
+ <a href="minibar.js">minibar.js</a> and <a href="support.js">support.js</a>
+ The latter is also used in
+ <a href="http://spraakbanken.gu.se/swe/forskning/saldo/ordspel">a couple of
+ small web apps</a> based on the
+ <a href="http://spraakbanken.gu.se/sal/ws/">SALDO web services</a>.
+ <li>To access the GF web service, it uses the
+ <a href="http://en.wikipedia.org/wiki/JSON#JSONP">JSONP method</a>
+ mentioned in the GF
+ web services paper, which allows the web app to be hosted on a different server
+ from the GF web service. (To demonstrate this, I put the Minibar demo on
+ www.cs.chalmers.se, while the GF server that it calls is on
+ www.grammaticalframework.org.)
+ <li>As an experiment, it does no use the <code>grammars.xml</code> file,
+ but instead calls a little CGI script,
+ <a href="http://www.grammaticalframework.org:41296/grammars/grammars.cgi.txt">grammars.cgi</a>
+ which lists the .pgf files in the directory, in JSONP format.
+ (Note: if you want to install this on your own computer,
+ <ul>
+ <li>if you click on the link,
+ the CGI script will be downloaded as <code>grammars.cgi.txt</code>,
+ but it should be called <code>grammars.cgi</code> and stored on the server
+ in the same directory as the grammar files.
+ <li>for CGI scripts to work with lighttpd, <code>"mod_cgi"</code> needs
+ to be included in the definition of <code>server.modules</code> in the
+ <code>lighttpd.conf</code> file.)
+ </ul>
+ <li>[Added 2010-02-16] There is a button for generating random sentences.
+ <li>[Added 2010-02-23] All translations are shown, not just the first one,
+ if there are multiple parses.
+ <li>[Added 2010-02-25] Next to each translation, there is now a little tree
+ icon that you can click on to see a drawing of an abstract syntax tree or a
+ parse tree. If you click on a drawing it collapses back into a tree icon.
+ <li>[Added 2010-04-09] Preparations to support different ways to access the
+ grammar: currently we access a PGF server via JSONP, but I would also like
+ to support AJAX, and local/downloaded JavaScript grammars.
+ <li>[Added 2010-04-19] A text entry field appears when you click in
+ the sentence area (with a dashed border). This allows you to enter words by
+ typing on the keyboard. As you start typing word magnets that don't match what
+ you are typing are removed. When only one magnet remains, you can press enter
+ to complete the word.
+ <li>[Added 2010-04-19] There is a menu for choosing the output language:
+ you can pick "All" to translate to all available languages, or pick one
+ particular language.
+ <li>[Added 2010-04-19] You can pass options to the function
+ <code>start_minibar</code> to customize the user interface. The default is
+ <code>{show_abstract:true,show_trees:true}</code> to show the abstract syntax
+ of parsed sentences, and to show icons that expand to syntax/parse trees next
+ each translation.
+ These features can be turned off by setting the fields to <code>false</code>.
+ <li>[Added 2010-04-30] The grammar menu is omitted if there is only one
+ grammar in the grammar list.
+ <li>[Added 2010-04-30] Fewer hardwired constants and new
+ <code>start_minibar</code> options (server, grammars_url, grammar_list,
+ show_grouped_translations, delete_button_text) to make
+ <code>minibar.js</code> more resuable.)
+ <li>[Added 2010-05-26] The magnets are now created with
+ <code>&lt;input type=button&gt;</code> tags to make them clickable in more
+ browsers.
+ <li>[Added 2010-05-26] The text entry field is now visible from the start,
+ and it is removed when no more words can be added to the sentence. When you
+ press enter, a word is added if there is only one magnet left,
+ <em>or</em> if what you have entered exactly matches one of the remaining
+ magnet.
+ <li>[Added 2010-05-28] Added a link to make it easy to try the same sentence in
+ <a href="http://translate.google.com">Google Translate</a>.This can be
+ turned off by passing the option <code>{try_google:false}</code> to
+ <code>start_minibar</code>.
+ <li>[Added 2010-06-02] Added support for Help and Feedback buttons, controlled
+ by the options <code>feedback_url</code> and <code>help_url</code> passed to
+ <code>start_minibar</code>.
+ <li>[Added 2010-06-02] New option: <code>default_source_language</code>.
+ <li>[Added 2010-09-10] Minibar now automatically uses
+ <a href="http://en.wikipedia.org/wiki/XMLHttpRequest">XHR</a>
+ instead of JSONP when possible (i.e. when the HTML document and the
+ PGF service are on the same server).
+ <li>[Added 2010-09-10] The default input language is now the user's preferred
+ language, if possible. This is implemented by consulting the
+ <code>userLanguage</code> field in the grammar info output by pgf-server.
+ <li>[Added 2010-10-27] Keyboard input and completion should now work much
+ more smoothly:
+ <ul>
+ <li>When you press space, the current word will be completed (if incomplete)
+ and a new magnet will be created. If there is more than one possible
+ completion, no magnet is created, but the common prefix of the possible
+ completions is added to the text box.
+ <li>Instead of asking the server for possible completions every time a new
+ letter is added to the curent word, minibar only ask for completions for
+ whole words and then filters the list locally when more letters are entered,
+ speeding things up when server responses are slow.
+ </ul>
+ <li>[Added 2010-10-27] Code restructuring:
+ <ul>
+ <li>The PGF server API has been moved to its own file:
+ <a href="pgf_online.js">pgf_online.js</a>. This
+ allows it to be reused in other applicaitons without importing the entire
+ minibar. It also allows minibar to be used with different server
+ interfaces. <a href="minibar.html">minibar.html</a> has been updated to
+ show how you use the new <a href="minibar.js">minibar.js</a> and
+ <a href="pgf_online.js">pgf_online.js</a>.
+ <li>The minibar code has been rewritten to avoid storing state information
+ in the document tree and accessing it by referring to named document
+ elements. The code now also avoids using string literals containing
+ the names of top-level functions to specify event handlers for buttons
+ and menus. (The code is no longer introspective, so &alpha; conversion
+ will not change its meaning.)
+ </ul>
+ <li>[Added 2010-11-09] Some new documentation:
+ <ul>
+ <li><a href="gf-web-api-examples.html">gf-web-api-examples.html</a>:
+ examples illustrating the PGF server API provided by
+ <a href="pgf_online.js">pgf_online.js</a>.
+ <li><a href="example.html">example.html</a>: a minimal example of a web
+ page that uses <a href="pgf_online.js">pgf_online.js</a> to talk to the
+ PGF server.
+ </ul>
+ <li>[Added 2011-03-03] Added a button to display word alignment.
+ <li>[Changed 2011-03-22] Don't force focus to the typed input field
+ after every word. On touch-based devices, the on-screen keyboard kept
+ popping up after every word, which was very annoying if you were
+ entering a sentence by tapping on the magnets.
+ <li>[Changed 2011-08-03] Moved the initialization code in minibar.html to
+ <a href="minibar_online.js">minibar_online.js</a>.
+ <li>[Changed 2011-08-08] For improved modularity and reusability,
+ two smaller objects have been factored out from the Minibar object:
+ Input and Translations. These have been placed in two separate files:
+ <a href="minibar_input.js">minibar_input.js</a> and
+ <a href="minibar_translations.js">minibar_translations.js</a>.
+ Some common auxiliary functions have also been moved to a separate file:
+ <a href="minibar_support.js">minibar_support.js</a>.
+ <li>[Added 2011-08-09] Added some <a href="minibar-api.html">Minibar API</a>
+ documentation.
+ <li>[Changed 2011-08-22] Quick fix to allow literals to be entered:
+ if you press Enter, the current word will be accepted, even if there are no
+ matching completions.
+ (You can now use names of people when constructing sentences in the Letter
+ grammar, for example.)
+</ul>
+
+<hr>
+<small class=modtime>
+<!-- hhmts start --> Last modified: Mon Aug 22 19:31:37 CEST 2011 <!-- hhmts end -->
+</small>
+<address>
+<a href="http://www.cs.chalmers.se/~hallgren/">TH</a>
+<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address>
+</address>
+</body> </html>
diff --git a/src/www/minibar/align-btn.png b/src/www/minibar/align-btn.png
new file mode 100644
index 000000000..ca6a391c1
--- /dev/null
+++ b/src/www/minibar/align-btn.png
Binary files differ
diff --git a/src/www/minibar/brushed-metal.png b/src/www/minibar/brushed-metal.png
new file mode 100644
index 000000000..c2f03fe7d
--- /dev/null
+++ b/src/www/minibar/brushed-metal.png
Binary files differ
diff --git a/src/www/minibar/example.html b/src/www/minibar/example.html
new file mode 100644
index 000000000..7d78a7ef6
--- /dev/null
+++ b/src/www/minibar/example.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html> <head>
+<title>PGF online server example</title>
+<style type="text/css">
+body { background: #ddd; }
+h1, h2, h3, small, th { font-family: sans-serif; }
+div.modtime { float: right; }
+.modtime { color: #666; white-space: nowrap; }
+</style>
+<script type="text/JavaScript" src="support.js"></script>
+<script type="text/JavaScript" src="pgf_online.js"></script>
+<script type="text/JavaScript">
+
+var server_options={
+ grammars_url: "http://www.grammaticalframework.org/grammars/",
+ grammar_list: ["Foods.pgf"]
+}
+var pgf_server = pgf_online(server_options);
+
+function call_server() {
+ pgf_server.parse({from:"FoodsEng",input:document.forms[0].input.value},
+ show_output)
+}
+
+function show_output(parsed) {
+ document.getElementById("output").innerHTML=parsed[0].trees[0]
+}
+
+</script>
+
+</head>
+
+<body>
+<h1>PGF online server example</h1>
+
+<form onsubmit="call_server(); return false">
+Input:
+<input name=input size=50 value="this cheese is expensive">
+<input type=submit value=Parse>
+
+<p>
+Output:
+<span id=output></span>
+</form>
+
+
+<h2>Documentation</h2>
+<ul>
+ <li><a href="gf-web-api-examples.html">GF Web API examples</a>
+</ul>
+
+<hr>
+<div class=modtime><small>
+<!-- hhmts start --> Last modified: Wed Aug 3 16:52:51 CEST 2011 <!-- hhmts end -->
+ </small></div>
+<address><a href="http://www.cse.chalmers.se/~hallgren/">TH</a></address>
+</body> </html>
diff --git a/src/www/minibar/feedback.cgi b/src/www/minibar/feedback.cgi
new file mode 100644
index 000000000..0d382f22d
--- /dev/null
+++ b/src/www/minibar/feedback.cgi
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+bin=bin
+AUTOHEADER=no
+. $bin/cgistart.sh
+
+save_feedback() {
+getquery
+
+if [ -n "$feedback_path" ] &&
+ echo "t=$(date +%F+%T)&ip=$REMOTE_ADDR&$query&accept_language=$HTTP_ACCEPT_LANGUAGE&user_agent=$(echo -n $HTTP_USER_AGENT | plain2url)" >> "$feedback_path"
+then
+
+ pagestart "Thank you!"
+ echo "Your feedback has been saved."
+ begin script type="text/javascript"
+ echo "setTimeout(function(){window.close()},4000);"
+ end
+ pageend
+
+else
+
+ pagestart "Feedback error"
+ echo "Your feedback could not be saved. Sorry."
+ p
+ tag 'input type=button onclick="javascript:history.back()" value="&lt;- Go back"'
+ pageend
+
+fi
+}
+
+view_feedback() {
+ charset="UTF-8"
+ pagestart "Collected Feedback"
+ begin pre class=feedbacklist
+ Reg show reverse drop color_depth,pixel_depth,outer_size,inner_size,available_screen_size from-url <"$PATH_TRANSLATED" | plain2html
+ end
+ pageend
+}
+
+case "$PATH_TRANSLATED" in
+ "") save_feedback ;;
+ *) view_feedback
+esac
diff --git a/src/www/minibar/feedback.html b/src/www/minibar/feedback.html
new file mode 100644
index 000000000..94b15a482
--- /dev/null
+++ b/src/www/minibar/feedback.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<title>Feedback</title>
+<link rel=stylesheet type="text/css" href="minibar.css">
+<script type="text/JavaScript" src="support.js"></script>
+<script type="text/JavaScript" src="minibar.js"></script>
+<meta name = "viewport" content = "width = device-width">
+
+</head>
+
+<body onload="prefill_feedback_form()">
+
+<h2><span id=grammar></span> Feedback</h2>
+
+<form class=feedback name=feedback action="feedback.cgi" method="post">
+<input type=hidden name="grammar">
+
+<p>
+<input type=hidden name="from"> <span class=field id=from>...</span> input:
+<input type=hidden name="input"> <span class=field id=input>...</span>
+
+<div id=translation_box>
+<p><input type=hidden name="to"> <span class=field id="to">...</span> translation:
+<input type=hidden name="translation"> <span class=field id=translation>...</span>
+
+<p><label accesskey="S">Suggest a better translation:
+<textarea rows=3 name="improvement"></textarea></label>
+</div>
+
+<p><label accesskey="C">Comments:
+<br><textarea rows=5 name="comment"></textarea></label>
+
+<p>
+<input type=submit value="Submit Feedback">
+<input type=button value="Cancel" onclick="window.close()">
+
+
+<input type=hidden name="inner_size">
+<input type=hidden name="outer_size">
+<input type=hidden name="screen_size">
+<input type=hidden name="available_screen_size">
+<input type=hidden name="color_depth">
+<input type=hidden name="pixel_depth">
+
+</form>
+</body>
+
+</html>
diff --git a/src/www/minibar/gf-web-api-examples.html b/src/www/minibar/gf-web-api-examples.html
new file mode 100644
index 000000000..194967de0
--- /dev/null
+++ b/src/www/minibar/gf-web-api-examples.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>GF web services API examples</title>
+<meta charset="UTF-8">
+
+<style type="text/css">
+body { background: #ddd; }
+h1, h2, h3, small, th { font-family: sans-serif; }
+
+dt { background: #cef; }
+dt.js { background: white; margin-bottom: 1ex; }
+dt.js em { color: #36f; }
+dd { background: #ffc; margin-top: 1ex; margin-bottom: 1ex; }
+
+dl.apiexamples>dt, dl.apiexamples>dd { font-family: monospace; }
+dl.apiexamples>dd { white-space: pre; }
+
+div.modtime { float: right; }
+.modtime { color: #666; white-space: nowrap; }
+
+@media projection {
+ div.intro { display: none; }
+
+ body {
+ font-size: 150%;
+ }
+
+ h2 { page-break-before: always; }
+
+ dl.apiexamples dd {
+ page-break-after: always;
+ /*border-style: none;*/
+ }
+}
+
+</style>
+
+<body>
+<h1>GF web services API examples</h1>
+
+GF can be used interactively from the GF Shell. Some of the functionality
+availiable in the GF shell is also available via the GF web services API.
+
+<p>
+The
+<a href="http://code.google.com/p/grammatical-framework/wiki/GFWebServiceAPI">GF
+Web Service API page</a> describes the calls supported by the GF web service
+API. Below, we illustrate these calls by examples, and also show
+how to make these calls from JavaScript using the API defined in
+<a href="pgf_online.js"><code>pgf_online.js</code></a>.
+
+<p>
+<strong>Note</strong> that <code>pgf_online.js</code> was initially developed
+with one particular web application in mind (the minibar), so the server API was
+incomplete. It was simplified and generalized in August 2011 to support the
+full API.
+
+<dl>
+ <dt class=js>These boxes show what the calls look like in the JavaScript
+ API defined in <code>pgf_online.js</code>.
+ <dt>These boxes show the corresponding URLs sent to the PGF server.
+ <dd>These boxes show the JSON (JavaScript data structures) returned by the PGF
+ server. This will be passed to the callback function supplied in the
+ call.
+</dl>
+
+<h2>Initialization</h2>
+<dl class=apiexamples>
+ <dt class=js>
+ <em>// Select which server and grammars to use:</em>
+ <br>var server_options = {
+ <br>&nbsp;&nbsp;grammars_url: "http://www.grammaticalframework.org/grammars/",
+ <br>&nbsp;&nbsp;grammar_list: ["Foods.pgf"] <em>// It's ok to skip this</em>
+ <br>}
+ <br>var server = pgf_online(server_options);
+</dl>
+
+<h2>Examples</h2>
+
+<dl class=apiexamples>
+ <dt class=js> <em>// Get the list of available grammars</em>
+ <br>server.get_grammarlist(callback)
+ <dt>http://localhost:41296/grammars/grammars.cgi
+ <dd>["Foods.pgf","Phrasebook.pgf"]
+ <dt class=js> <em>// Select which grammar to use</em>
+ <br>server.switch_grammar("Foods.pgf")
+ <dt class=js><em>// Get list of concrete languages and other grammar info</em>
+ <br>server.grammar_info(callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf
+ <dd>{"name":"Foods",
+ "userLanguage":"FoodsEng",
+ "categories":["Comment","Float","Int","Item","Kind","Quality","String"],
+ "functions":["Boring","Cheese","Delicious","Expensive","Fish","Fresh",
+ "Italian","Mod","Pizza","Pred","That","These","This","Those","Very",
+ "Warm","Wine"],
+ "languages":[{"name":"FoodsBul","languageCode":""},
+ {"name":"FoodsEng","languageCode":"en-US"},
+ {"name":"FoodsFin","languageCode":""},
+ {"name":"FoodsSwe","languageCode":"sv-SE"},
+ ...]
+}
+ <dt class=js><em>// Get a random syntax tree</em>
+ <br>server.get_random({},callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf?command=random
+ <dd>[{"tree":"Pred (That Pizza) (Very Boring)"}]
+ <dt class=js><em>// Linearize a syntax tree</em>
+ <br>server.linearize({tree:"Pred (That Pizza) (Very Boring)",to:"FoodsEng"},callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf?command=linearize&tree=Pred+(That+Pizza)+(Very+Boring)&to=FoodsEng
+ <dd>[{"to":"FoodsEng","text":"that pizza is very boring"}]
+ <dt class=js>server.linearize({tree:"Pred (That Pizza) (Very Boring)"},callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf?command=linearize&tree=Pred+(That+Pizza)+(Very+Boring)
+ <dd>[{"to":"FoodsBul","text":"онази пица е много еднообразна"},
+ {"to":"FoodsEng","text":"that pizza is very boring"},
+ {"to":"FoodsFin","text":"tuo pizza on erittäin tylsä"},
+ {"to":"FoodsSwe","text":"den där pizzan är mycket tråkig"},
+ ...
+]
+ <dt class=js><em>// Parse a string</em>
+ <br>server.parse({from:"FoodsEng",input:"that pizza is very boring"},callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf?command=parse&input=that+pizza+is+very+boring&from=FoodsEng
+ <dd>[{"from":"FoodsEng",
+ "brackets":{"cat":"Comment","fid":10,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"cat":"Quality","fid":9,"index":0,"children":[{"token":"very"},{"cat":"Quality","fid":8,"index":0,"children":[{"token":"boring"}]}]}]},
+ "trees":["Pred (That Pizza) (Very Boring)"]}]
+ <dt class=js><em>// Translate to all available languages</em>
+ <br>server.translate({from:"FoodsEng",input:"that pizza is very boring"},callback)
+ <dd>...
+ <dt class=js><em>// Translate to one language</em>
+ <br>server.translate({input:"that pizza is very boring", from:"FoodsEng", to:"FoodsSwe"}, callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf?command=translate&input=that+pizza+is+very+boring&from=FoodsEng&to=FoodsSwe
+ <dd>[{"from":"FoodsEng",
+ "brackets":{"cat":"Comment","fid":10,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"cat":"Quality","fid":9,"index":0,"children":[{"token":"very"},{"cat":"Quality","fid":8,"index":0,"children":[{"token":"boring"}]}]}]},
+ "translations":
+ [{"tree":"Pred (That Pizza) (Very Boring)",
+ "linearizations":
+ [{"to":"FoodsSwe",
+ "text":"den där pizzan är mycket tråkig"}]}]}]
+ <dt class=js><em>// Get completions (what words could come next)</em>
+ <br>server.complete({from:"FoodsEng",input:"that pizza is very "},callback)
+ <dt>http://localhost:41296/grammars/Foods.pgf?command=complete&input=that+pizza+is+very+&from=FoodsEng
+ <dd>[{"from":"FoodsEng",
+ "brackets":{"cat":"_","fid":0,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"token":"very"}]},
+ "completions":["boring","delicious","expensive","fresh","Italian","very","warm"],
+ "text":""}]
+</dl>
+<hr>
+<div class=modtime><small>
+<!-- hhmts start --> Last modified: Sun Aug 21 10:52:43 CEST 2011 <!-- hhmts end -->
+ </small></div>
+<address><a href="http://www.cse.chalmers.se/~hallgren/">TH</a></address>
+ \ No newline at end of file
diff --git a/src/www/minibar/minibar-api.html b/src/www/minibar/minibar-api.html
new file mode 100644
index 000000000..2c5a8ecc0
--- /dev/null
+++ b/src/www/minibar/minibar-api.html
@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>GF web services API examples</title>
+<meta charset="UTF-8">
+
+<style type="text/css">
+body { background: #eee; }
+h1, h2, h3, small, th { font-family: sans-serif; }
+th { text-align: left; }
+h1,h2 { border-bottom: 2px solid black }
+dt { background: #cef; }
+code { background: #ffc; }
+dt.js { background: white; margin-bottom: 1ex; }
+dt.js em { color: #36f; }
+dd { background: #ffc; margin-top: 1ex; margin-bottom: 1ex; }
+
+dl.apiexamples>dt, dl.apiexamples>dd { font-family: monospace; }
+dl.apiexamples>dd { white-space: pre; }
+
+table.border { border-collapse: collapse; margin-top: 1ex; margin-bottom: 1ex; }
+table.border td, table.border th { border: 1px solid black; background: #fcfcfc; }
+
+div.modtime { float: right; }
+.modtime { color: #666; white-space: nowrap; }
+
+</style>
+
+<body>
+<h1>Minibar API</h1>
+
+The Minibar web app consists of the following objects:
+
+<ul>
+ <li><a href="#Minibar">Minibar</a>
+ <li><a href="#Input">Input</a>
+ <li><a href="#Translations">Translations</a>
+</ul>
+
+They are described below.
+
+<h2 id=Minibar>The Minibar object</h2>
+
+<p>
+This object implements the complete Minibar web app. It is defined in
+<a href="minibar.js">minibar.js</a>. It also uses the <code>Input</code>
+and <code>Translations</code> objects described below, and some auxiliary
+functions defined in <a href="minibar_support.js">minibar_support.js</a>
+and <a href="support.js">support.js</a>, so to use it in an
+HTML file, you would normally include at least the following:
+
+<blockquote><pre>
+&lt;script type="text/JavaScript" src="minibar.js">&lt;/script>
+&lt;script type="text/JavaScript" src="minibar_input.js">&lt;/script>
+&lt;script type="text/JavaScript" src="minibar_translations.js">&lt;/script>
+&lt;script type="text/JavaScript" src="minibar_support.js">&lt;/script>
+&lt;script type="text/JavaScript" src="support.js">&lt;/script>
+</pre></blockquote>
+
+<p>
+For an example, see <a href="minibar.html">minibar.html</a>.
+
+<h3>Constructor</h3>
+
+<code>var minibar=new Minibar(server,options,target)</code>
+
+<ul>
+ <li><code>server</code> is the PGF service object.
+ <li><code>options</code> is an object where the following properties
+ can be set to override various default options:
+ <table class=border>
+ <tr><th>Option<th>Default<th>Description
+ <tr><td>show_abstract<td>false<td rowspan=3>See Translations,
+ not used directly by Minibar
+ <tr><td>show_trees<td>false
+ <tr><td>show_grouped_translations<td>true
+ <tr><td>delete_button_text<td>"⌫"<td rowspan=3>See Input,
+ not used directly by Minibar
+ <tr><td>default_source_language<td>null
+ <tr><td>random_button<td>true
+ <tr><td>try_google<td>true<td>Include a button to try the current
+ sentence in Google Translate
+ <tr><td>feedback_url<td>null<td>Include a button to open a feedback
+ form. The HTTP server must be configured to handle form submissions
+ for this to work.
+ <tr><td>help_url<td>null<td>Include a button to open a help text.
+ </table>
+ <li><code>target</code> is the <code>id</code> of the HTML element inside
+ which the minibar user interface is created. It can be omitted if
+ the <code>id</code> is <code>minibar</code>. The HTML document should
+ contain something like this:
+ <blockquote><code>&lt;div id="minibar">&lt;/div></code></blockquote>
+</ul>
+
+<h3>Methods</h3>
+There are several internal methods, but since this is a self-contained
+web app, there is usually no need to call any methods from outside.
+
+<h2 id=Input>The Input object</h2>
+
+This object handles user input. Text can be entered by typing or by clicking
+on the "refrigerator magnets".
+<p>
+It is defined in
+<a href="minibar_input.js">minibar_input.js</a>.
+It also uses some auxiliary functions defined
+in <a href="minibar_support.js">minibar_support.js</a>
+and <a href="support.js">support.js</a>, so to use it in an
+HTML file, you would normally include at least the following:
+
+<blockquote><pre>
+&lt;script type="text/JavaScript" src="minibar_input.js">&lt;/script>
+&lt;script type="text/JavaScript" src="minibar_support.js">&lt;/script>
+&lt;script type="text/JavaScript" src="support.js">&lt;/script>
+</pre></blockquote>
+
+<h3>Constructor</h3>
+
+<code>var input=new Input(server,translations,options)</code>
+
+<ul>
+ <li><code>server</code> is the PGF service object
+ <li><code>options</code> is an object where the following properties
+ can be set to override various default options:
+ <table class=border>
+ <tr><th>Option<th>Default<th>Description
+ <tr><td>delete_button_text<td>"⌫"<td>the label for the button that deletes the last word
+ <tr><td>default_source_language<td>null<td>the concrete language to
+ use for input in case the user's browers doesn't supply a suitable
+ default. If none is provided the first language in alphabetical
+ order will be used.
+ <tr><td>random_button<td>true<td>include a button to generate a
+ random sentence
+ </table>
+
+ <li><code>translations</code> is the object that is notified when the input
+ has changed. In the minibar, this is the object that display translations, but
+ other apps might of course use the entered text for other purposes.
+ The following methods will be called:
+ <ul>
+ <li><code>translations.clear()</code> is called when there no entered
+ text.
+ <li><code>translations.translateFrom({from:<var>conc</var>,input:<var>string</var>})</code>
+ is called when the user has entered some text. The <code>from</code>
+ property is the name of the concrete syntax and the <code>input</code>
+ property is the entered text.
+ </ul>
+</ul>
+
+<h3>Properties and user interface</h3>
+
+The <code>input</code> object created by the <code>Input</code> constructor
+contains two field that the caller should add to the user interface:
+<ul>
+ <li><code>input.main</code> is the main user interface where the current
+ input and the refrigerator magnets are displayed.
+ <li><code>input.menus</code> contains the menu for selecting input language,
+ and buttons for deleting the last word, clearing the input and generating
+ a random sentence (if enabled in the options)
+</ul>
+
+<h3>Methods</h3>
+
+<ul>
+ <li><code>input.change_grammar(grammar_info)</code> should be called
+ after a different grammar is selected in the <code>server</code> object. It
+ will clear away old input and magnets, and update the input language menu
+ with the languages available in the new grammar.
+
+</ul>
+
+<h2 id=Translations>The Translations object</h2>
+
+This object display translations. It is defined in
+<a href="minibar_translations.js">minibar_translations.js</a>.
+It also uses some auxiliary functions defined
+in <a href="minibar_support.js">minibar_support.js</a>
+and <a href="support.js">support.js</a>, so to use it in an
+HTML file, you would normally include at least the following:
+
+<blockquote><pre>
+&lt;script type="text/JavaScript" src="minibar_input.js">&lt;/script>
+&lt;script type="text/JavaScript" src="minibar_support.js">&lt;/script>
+&lt;script type="text/JavaScript" src="support.js">&lt;/script>
+</pre></blockquote>
+
+<h3>Constructor</h3>
+<code>var translations=new Translations(server,options)</code>
+<ul>
+ <li><code>server</code> is the PGF service object.
+ <li><p><code>options</code> is an object where the following properties
+ can be set to override various default options:
+ <table class=border>
+ <tr><th>Option<th>Default<th>Description
+ <tr><td>show_abstract<td>false<td>show the abstract syntax in addition
+ to the concrete syntax for the translations
+ <tr><td>show_trees<td>false<td>add buttons to display syntax trees
+ next to translations.
+ <tr><td>show_grouped_translations<td>true<td>in case there are
+ multiple translations, group them by concrete language
+ </table>
+
+</ul>
+
+<h3>Properties and user interface</h3>
+
+
+The <code>translations</code> object created by the
+<code>Translations</code> constructor contains two field that the caller
+should add to the user interface:
+<ul>
+ <li><code>input.main</code> is the main user interface where the current
+ translations are displayed.
+ <li><code>input.menus</code> contains the menu for selecting target language.
+</ul>
+
+<h3>Methods</h3>
+<ul>
+ <li><code>translations.change_grammar(grammar_info)</code> should be called
+ after a different grammar is selected in the <code>server</code> object. It
+ will clear away old translations and update the target language menu
+ with the languages available in the new grammar.
+</ul>
+
+
+<hr>
+<div class=modtime>
+<small class=modtime>
+HTML <!-- hhmts start --> Last modified: Sun Aug 21 19:11:35 CEST 2011 <!-- hhmts end -->
+</small>
+</div>
+
+<address>
+<a href="Http://www.cse.chalmers.se/~hallgren/">TH</a>
+</address>
diff --git a/src/www/minibar/minibar.css b/src/www/minibar/minibar.css
new file mode 100644
index 000000000..4d2094066
--- /dev/null
+++ b/src/www/minibar/minibar.css
@@ -0,0 +1,53 @@
+body {
+ background: #ccc url("brushed-metal.png");
+}
+
+h1, h2, h3, small, th { font-family: sans-serif; }
+
+th, td { vertical-align: baseline; text-align: left; }
+
+div#surface {
+ min-height: 3ex;
+ margin: 5px;
+ padding: 5px;
+ border: 3px dashed #e0e0e0;
+}
+
+div#words {
+ min-height: 3ex;
+ margin: 5px;
+ padding: 6px;
+ border: 3px solid #e0e0e0;
+}
+
+div.word, span.word, div#words div, div#words input[type=button] {
+ display: inline-block;
+ font-family: sans-serif;
+ font-size: 100%;
+ background-color: white;
+ border: 1px solid black;
+ padding: 3px;
+ margin: 3px;
+}
+
+.invalid { color: red; }
+
+div.modtime { float: right; }
+.modtime { color: #666; white-space: nowrap; }
+
+ul.space>li { margin-top: 0.75ex; }
+
+div#saldospel input[type=button] { font-size: 100%; }
+
+div#saldospel input.correct { color: green; }
+div#saldospel input.incorrect { color: red; }
+
+#surface input[type=text] { width: 5em; }
+
+.feedback textarea { width: 95%; }
+
+span.field { background-color: #eee; }
+
+pre.feedbacklist { background: white }
+
+img.button { padding: 1px; } \ No newline at end of file
diff --git a/src/www/minibar/minibar.html b/src/www/minibar/minibar.html
new file mode 100644
index 000000000..086b8fad6
--- /dev/null
+++ b/src/www/minibar/minibar.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Minibar</title>
+<link rel=stylesheet type="text/css" href="minibar.css">
+
+<meta name = "viewport" content = "width = device-width">
+<meta charset="UTF-8">
+
+</head>
+
+<body>
+<h2>Minibar online</h2>
+<div id=minibar></div>
+
+<noscript>This page doesn't works unless JavaScript is enabled.</noscript>
+
+<hr>
+
+<small>
+[<a href="about.html">About Minibar</a>
+| <a href="http://www.grammaticalframework.org:41296/fridge/">Original Fridge Poetry</a>
+&amp; <a href="http://www.grammaticalframework.org:41296/translate/">Translator</a>]
+</small>
+<small class=modtime>
+HTML <!-- hhmts start --> Last modified: Mon Aug 8 18:04:22 CEST 2011 <!-- hhmts end -->
+</small>
+<address>
+<a href="http://www.cse.chalmers.se/~hallgren/">TH</a>
+<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address>
+
+<script type="text/JavaScript" src="support.js"></script>
+<script type="text/JavaScript" src="minibar.js"></script>
+<script type="text/JavaScript" src="minibar_input.js"></script>
+<script type="text/JavaScript" src="minibar_translations.js"></script>
+<script type="text/JavaScript" src="minibar_support.js"></script>
+<script type="text/JavaScript" src="pgf_online.js"></script>
+<script type="text/javascript" src="minibar_online.js"></script>
+
+</body>
+</html>
diff --git a/src/www/minibar/minibar.js b/src/www/minibar/minibar.js
new file mode 100644
index 000000000..778a3957b
--- /dev/null
+++ b/src/www/minibar/minibar.js
@@ -0,0 +1,176 @@
+/* minibar.js
+needs: minibar_support.js, minibar_input.js, minibar_translations.js, support.js
+*/
+
+/*
+// This is essentially what happens when you call start_minibar:
+if(server.grammar_list) grammars=server.grammar_list;
+else grammars=server.get_grammarlist();
+show_grammarlist(grammars)
+select_grammar(grammars[0])
+grammar_info=server.get_languages()
+show_languages(grammar_info)
+new_language()
+complete_output=get_completions()
+show_completions(complete_output)
+*/
+
+// For backward compatibility:
+function start_minibar(server,opts,target) {
+ if(target) opts.target=target;
+ return new Minibar(server,opts);
+}
+
+/* --- Main Minibar object -------------------------------------------------- */
+function Minibar(server,opts) {
+ // Contructor, typically called when the HTML document is loaded
+
+ /* --- Configuration ---------------------------------------------------- */
+
+ // default values for options:
+ this.options={
+ target: "minibar",
+ try_google: true,
+ feedback_url: null,
+ help_url: null
+ }
+
+ // Apply supplied options
+ if(opts) for(var o in opts) this.options[o]=opts[o];
+
+ /* --- Creating the components of the minibar --------------------------- */
+ this.translations=new Translations(server,this.options)
+ this.input=new Input(server,this.translations,this.options)
+
+ /* --- Creating user interface elements --------------------------------- */
+
+ this.menubar=empty("div");
+ this.extra=div_id("extra");
+
+ this.minibar=element(this.options.target);
+ this.minibar.innerHTML="";
+ with(this) {
+ appendChildren(menubar,[input.menus,translations.menus,input.buttons])
+ appendChildren(minibar,[menubar,input.main,translations.main,extra]);
+ append_extra_buttons(extra,options);
+ }
+
+ /* --- Minibar client state initialisation ------------------------------ */
+ this.grammar=null;
+
+ this.server=server;
+
+ /* --- Main program, this gets things going ----------------------------- */
+ with(this) {
+ if(server.grammar_list) show_grammarlist(server.grammar_list);
+ else server.get_grammarlist(bind(show_grammarlist,this));
+ }
+}
+
+Minibar.prototype.show_grammarlist=function(grammars) {
+ this.grammar_menu=empty_id("select","grammar_menu");
+ with(this) {
+ if(grammars.length>1) {
+ function opt(g) { return option(g,g); }
+ appendChildren(grammar_menu,map(opt,grammars));
+ grammar_menu.onchange=
+ bind(function() { select_grammar(grammar_menu.value); },this);
+ insertFirst(menubar,grammar_menu);
+ insertFirst(menubar,text("Grammar: "));
+ }
+ if(options.help_url)
+ menubar.appendChild(button("Help",bind(open_help,this)));
+ select_grammar(grammars[0]);
+ }
+}
+
+Minibar.prototype.select_grammar=function(grammar_name) {
+ var t=this;
+ //debug("select_grammar ");
+ function change_grammar() {
+ t.server.grammar_info(bind(t.change_grammar,t));
+ }
+ t.server.switch_grammar(grammar_name,change_grammar);
+}
+
+Minibar.prototype.change_grammar=function(grammar_info) {
+ var t=this;
+ with(t) {
+ //debug("show_languages ");
+ grammar=grammar_info;
+
+ input.change_grammar(grammar)
+ translations.change_grammar(grammar)
+ }
+}
+
+Minibar.prototype.append_extra_buttons=function(extra,options) {
+ with(this) {
+ if(options.try_google)
+ extra.appendChild(button("Try Google Translate",bind(try_google,this)));
+ if(options.feedback_url)
+ appendChildren(extra,[text(" "),button("Feedback",bind(open_feedback,this))]);
+ }
+}
+
+Minibar.prototype.try_google=function() {
+ with(this) {
+ var to=translations.target_lang();
+ var s=input.current.input;
+ if(input.surface.typed) s+=input.surface.typed.value;
+ var url="http://translate.google.com/?sl="
+ +langpart(input.current.from,grammar.name);
+ if(to!="All") url+="&tl="+to;
+ url+="&q="+encodeURIComponent(s);
+ window.open(url);
+ }
+}
+
+Minibar.prototype.open_help=function() {
+ with(this) open_popup(options.help_url,"help");
+}
+
+Minibar.prototype.open_feedback=function() {
+ with(this) {
+ // make the minibar state easily accessible from the feedback page:
+ minibar.state={grammar:grammar,current:input.current,
+ to:translations.to_menu.value,
+ translations:translations.translations};
+ open_popup(options.feedback_url,'feedback');
+ }
+}
+
+// This function is called from feedback.html
+function prefill_feedback_form() {
+ var state=opener_element("minibar").state;
+ var trans=state.translations;
+ var gn=state.grammar.name
+ var to=langpart(state.to,gn);
+
+ var form=document.forms.namedItem("feedback");
+ setField(form,"grammar",gn);
+ setField(form,"from",langpart(state.current.from,gn));
+ setField(form,"input",state.current.input);
+ setField(form,"to",to);
+ if(to=="All") element("translation_box").style.display="none";
+ else setField(form,"translation",trans.single_translation.join(" / "));
+
+ // Browser info:
+ form["inner_size"].value=window.innerWidth+"×"+window.innerHeight;
+ form["outer_size"].value=window.outerWidth+"×"+window.outerHeight;
+ form["screen_size"].value=screen.width+"×"+screen.height;
+ form["available_screen_size"].value=screen.availWidth+"×"+screen.availHeight;
+ form["color_depth"].value=screen.colorDepth;
+ form["pixel_depth"].value=screen.pixelDepth;
+
+ window.focus();
+}
+
+
+/*
+se.chalmers.cs.gf.gwt.TranslateApp/align-btn.png
+
+GET /grammars/Foods.pgf?&command=abstrtree&tree=Pred+(This+Fish)+(Very+Fresh)
+GET /grammars/Foods.pgf?&command=parsetree&tree=Pred+(This+Fish)+Expensive&from=FoodsAfr
+GET /grammars/Foods.pgf?&command=alignment&tree=Pred+(This+Fish)+Expensive
+*/
diff --git a/src/www/minibar/minibar_input.js b/src/www/minibar/minibar_input.js
new file mode 100644
index 000000000..8075eb8ea
--- /dev/null
+++ b/src/www/minibar/minibar_input.js
@@ -0,0 +1,277 @@
+
+/* --- Input object --------------------------------------------------------- */
+
+function Input(server,translations,opts) { // Input object constructor
+ this.server=server;
+ this.translations=translations;
+
+ // Default values for options:
+ this.options={
+ delete_button_text: "⌫",
+ default_source_language: null,
+ random_button: true,
+ }
+
+ // Apply supplied options
+ if(opts) for(var o in opts) this.options[o]=opts[o];
+
+ // User interface elements
+ this.main=empty("div");
+ this.menus=empty("span");
+ this.buttons=empty("span");
+ this.surface=div_id("surface");
+ this.words=div_id("words");
+ this.from_menu=empty("select");
+
+ with(this) {
+ appendChildren(main,[surface,words]);
+ appendChildren(menus,[text(" From: "),from_menu])
+ appendChildren(buttons,
+ [button(options.delete_button_text,bind(delete_last,this),"H"),
+ button("Clear",bind(clear_all,this),"L")]);
+ if(options.random_button)
+ buttons.appendChild(button("Random",bind(generate_random,this),"R"));
+ }
+
+ /* --- Input client state initialization --- */
+ this.current={from: null, input: ""};
+ this.previous=null;
+
+ this.from_menu.onchange=bind(this.change_language,this);
+}
+
+Input.prototype.change_grammar=function (grammar) {
+ update_language_menu(this.from_menu,grammar);
+ set_initial_language(this.options,this.from_menu,grammar);
+ this.change_language();
+}
+
+Input.prototype.change_language=function () {
+ this.current.from=this.from_menu.value;
+ this.clear_all();
+}
+
+
+Input.prototype.clear_all1=function() {
+ with(this) {
+ remove_typed_input();
+ current.input="";
+ previous=null;
+ surface.innerHTML="";
+ translations.clear();
+ }
+}
+
+Input.prototype.clear_all=function() {
+ with(this) {
+ clear_all1();
+ get_completions();
+ }
+}
+
+Input.prototype.get_completions=function() {
+ with(this) {
+ //debug("get_completions ");
+ words.innerHTML="...";
+ server.complete({from:current.from,input:current.input},
+ bind(show_completions,this));
+ }
+}
+
+Input.prototype.show_completions=function(complete_output) {
+ with(this) {
+ //debug("show_completions ");
+ var completions=complete_output[0].completions;
+ var emptycnt=add_completions(completions)
+ if(true/*emptycnt>0*/) translations.translateFrom(current);
+ else translations.clear();
+ if(surface.typed && emptycnt==completions.length) {
+ if(surface.typed.value=="") remove_typed_input();
+ }
+ else add_typed_input();
+ }
+}
+
+Input.prototype.add_completions=function(completions) {
+ with(this) {
+ if(words.timeout) clearTimeout(words.timeout),words.timeout=null;
+ words.innerHTML="";
+ words.completions=completions;
+ words.word=[];
+ var t=surface.typed ? surface.typed.value : "";
+ var emptycnt=0;
+ for(var i=0;i<completions.length;i++) {
+ var s=completions[i];
+ if(s.length>0) {
+ var w=word(s);
+ words.appendChild(w);
+ words.word[i]=w;
+ }
+ else emptycnt++;
+ }
+ filter_completions(t,true);
+ return emptycnt;
+ }
+}
+
+Input.prototype.filter_completions=function(t,dim) {
+ with(this) {
+ if(words.timeout) clearTimeout(words.timeout),words.timeout=null;
+ words.filtered=t;
+ //if(dim) debug('filter "'+t+'"');
+ var w=words.word;
+ words.count=0;
+ var dimmed=0;
+ var prefix=""; // longest common prefix, for completion
+ for(var i=0;i<w.length;i++) {
+ var s=words.completions[i];
+ var keep=hasPrefix(s,t);
+ if(keep) {
+ if(words.count==0) prefix=s;
+ else prefix=(commonPrefix(prefix,s));
+ words.count++;
+ }
+ if(dim) {
+ w[i].style.opacity= keep ? "1" : "0.5";
+ if(keep) w[i].style.display="inline";
+ else dimmed++;
+ }
+ else
+ w[i].style.display=keep ? "inline" : "none";
+ }
+ words.theword=prefix;
+ if(dimmed>0)
+ words.timeout=setTimeout(function(){ filter_completions(t,false)},1000);
+ }
+}
+
+
+Input.prototype.add_typed_input=function() {
+ with(this) {
+ if(!surface.typed) {
+ var inp=empty("input","type","text");
+ inp.value="";
+ inp.setAttribute("accesskey","t");
+ inp.style.width="10em";
+ inp.onkeyup=bind(complete_typed,this);
+ surface.appendChild(inp);
+ surface.typed=inp;
+ inp.focus();
+ }
+ }
+}
+
+Input.prototype.remove_typed_input=function() {
+ with(this) {
+ if(surface.typed) {
+ surface.typed.parentNode.removeChild(surface.typed);
+ surface.typed=null;
+ }
+ }
+}
+
+Input.prototype.complete_typed=function(event) {
+ with(this) {
+ //element("debug").innerHTML=show_props(event,"event");
+ var inp=surface.typed;
+ //debug('"'+inp.value+'"');
+ var s=inp.value;
+ var ws=s.split(" ");
+ if(ws.length>1 || event.keyCode==13) {
+ if(ws[0]!=words.filtered) filter_completions(ws[0],true);
+ if(words.count==1) add_word(words.theword);
+ else if(event.keyCode==13) add_word(ws[0]) // for literals
+ else if(elem(ws[0],words.completions)) add_word(ws[0]);
+ else if(words.theword.length>ws[0].length) inp.value=words.theword;
+ }
+ else if(s!=words.filtered) filter_completions(s,true)
+ }
+}
+
+Input.prototype.generate_random=function() {
+ var t=this;
+ function show_random(random) {
+ t.clear_all1();
+ t.add_words(random[0].text);
+ }
+
+ function lin_random(abs) {
+ t.server.linearize({tree:abs[0].tree,to:t.current.from},show_random);
+ }
+ t.server.get_random({},lin_random);
+}
+
+Input.prototype.add_words=function(s) {
+ with(this) {
+ var ws=s.split(" ");
+ for(var i=0;i<ws.length;i++)
+ add_word1(ws[i]+" ");
+ get_completions();
+ }
+}
+
+Input.prototype.word=function(s) {
+ var t=this;
+ function click_word() {
+ if(t.surface.typed) t.surface.typed.value="";
+ t.add_word(s);
+ }
+ return button(s,click_word);
+}
+
+Input.prototype.add_word=function(s) {
+ with(this) {
+ add_word1(s+" ");
+ if(surface.typed) {
+ var s2;
+ if(hasPrefix(s2=surface.typed.value,s)) {
+ s2=s2.substr(s.length);
+ while(s2.length>0 && s2[0]==" ") s2=s2.substr(1);
+ surface.typed.value=s2;
+ }
+ else surface.typed.value="";
+ }
+ get_completions();
+ }
+}
+
+Input.prototype.add_word1=function(s) {
+ with(this) {
+ previous={ input: current.input, previous: previous };
+ current.input+=s;
+ var w=span_class("word",text(s));
+ if(surface.typed) surface.insertBefore(w,surface.typed);
+ else surface.appendChild(w);
+ }
+}
+
+Input.prototype.delete_last=function() {
+ with(this) {
+ if(surface.typed && surface.typed.value!="")
+ surface.typed.value="";
+ else if(previous) {
+ current.input=previous.input;
+ previous=previous.previous;
+ if(surface.typed) {
+ surface.removeChild(surface.typed.previousSibling);
+ surface.typed.focus();
+ }
+ else surface.removeChild(surface.lastChild);
+ translations.clear();
+ get_completions();
+ }
+ }
+}
+
+/* --- Auxiliary functions -------------------------------------------------- */
+
+function set_initial_language(options,menu,grammar) {
+ if(grammar.userLanguage) menu.value=grammar.userLanguage;
+ else if(options.default_source_language) {
+ for(var i=0;i<menu.options.length;i++) {
+ var o=menu.options[i].value;
+ var l=langpart(o,grammar.name);
+ if(l==options.default_source_language) menu.value=o;
+ }
+ }
+}
diff --git a/src/www/minibar/minibar_online.js b/src/www/minibar/minibar_online.js
new file mode 100644
index 000000000..1c15e87bf
--- /dev/null
+++ b/src/www/minibar/minibar_online.js
@@ -0,0 +1,25 @@
+// minibar_demo.js, assumes that minibar.js and pgf_online.js have been loaded.
+
+var online_options={
+ //grammars_url: "http://www.grammaticalframework.org/grammars/",
+ //grammars_url: "http://tournesol.cs.chalmers.se:41296/grammars/",
+ //grammars_url: "http://localhost:41296/grammars/",
+ //grammar_list: ["Foods.pgf"], // leave undefined to get list from server
+}
+
+
+if(/^\?\/tmp\//.test(location.search)) {
+ online_options.grammars_url=location.search.substr(1);
+}
+
+var server=pgf_online(online_options);
+
+var minibar_options= {
+ show_abstract: true,
+ show_trees: true,
+ show_grouped_translations: false,
+ default_source_language: "Eng",
+//feedback_url: "feedback.html",
+ try_google: true
+}
+var minibar=new Minibar(server,minibar_options);
diff --git a/src/www/minibar/minibar_support.js b/src/www/minibar/minibar_support.js
new file mode 100644
index 000000000..a3fc078f7
--- /dev/null
+++ b/src/www/minibar/minibar_support.js
@@ -0,0 +1,46 @@
+
+/* --- Auxiliary functions -------------------------------------------------- */
+
+function langpart(conc,abs) { // langpart("FoodsEng","Foods") == "Eng"
+ return hasPrefix(conc,abs) ? conc.substr(abs.length) : conc;
+}
+
+function update_language_menu(menu,grammar) {
+ // Replace the options in the menu with the languages in the grammar
+ var lang=grammar.languages;
+ menu.innerHTML="";
+
+ for(var i=0; i<lang.length; i++) {
+ var ln=lang[i].name;
+ if(!hasPrefix(ln,"Disamb")) {
+ var lp=langpart(ln,grammar.name);
+ menu.appendChild(option(lp,ln));
+ }
+ }
+}
+
+function button_img(url,action) {
+ var i=img(url);
+ i.setAttribute("class","button");
+ i.setAttribute("onclick",action);
+ return i;
+}
+
+function toggle_img(i) {
+ var tmp=i.src;
+ i.src=i.other;
+ i.other=tmp;
+}
+
+function setField(form,name,value) {
+ form[name].value=value;
+ var el=element(name);
+ if(el) el.innerHTML=value;
+}
+
+function open_popup(url,target) {
+ var w=window.open(url,target,'toolbar=no,location=no,status=no,menubar=no');
+ w.focus();
+}
+
+function opener_element(id) { with(window.opener) return element(id); }
diff --git a/src/www/minibar/minibar_translations.js b/src/www/minibar/minibar_translations.js
new file mode 100644
index 000000000..ef6fbd701
--- /dev/null
+++ b/src/www/minibar/minibar_translations.js
@@ -0,0 +1,162 @@
+/* --- Translations object -------------------------------------------------- */
+
+var tree_icon="tree-btn.png";
+var alignment_icon="align-btn.png";
+
+function Translations(server,opts) {
+ this.server=server;
+
+ // Default values for options:
+ this.options={
+ show_abstract: false,
+ show_trees: false,
+ show_grouped_translations: true,
+ }
+
+ // Apply supplied options
+ if(opts) for(var o in opts) this.options[o]=opts[o];
+
+ this.main=empty("div");
+ this.menus=empty("span");
+
+ this.to_menu=empty_id("select","to_menu");
+
+ appendChildren(this.menus,[text(" To: "), this.to_menu])
+ this.to_menu.onchange=bind(this.get_translations,this);
+
+}
+
+Translations.prototype.change_grammar=function(grammar) {
+ this.grammar=grammar;
+
+ update_language_menu(this.to_menu,grammar);
+ insertFirst(this.to_menu,option("All","All"));
+ this.to_menu.value="All";
+}
+
+Translations.prototype.clear=function() {
+ this.main.innerHTML="";
+}
+
+Translations.prototype.translateFrom=function(current) {
+ this.current=current;
+ this.get_translations();
+}
+
+Translations.prototype.get_translations=function() {
+ with(this) {
+ var c=current;
+ if(options.show_grouped_translations)
+ server.translategroup({from:c.from,input:c.input},
+ bind(show_groupedtranslations,this));
+ else
+ server.translate({from:c.from,input:c.input},
+ bind(show_translations,this));
+ }
+}
+
+Translations.prototype.tdt=function(tree_btn,txt) {
+ with(this) {
+ return options.show_trees ? tda([tree_btn,txt]) : td(txt);
+ }
+}
+
+Translations.prototype.target_lang=function() {
+ with(this) return langpart(to_menu.value,grammar.name);
+}
+
+Translations.prototype.show_translations=function(translationResults) {
+ with(this) {
+ var trans=main;
+ //var to=target_lang(); // wrong
+ var to=to_menu.value;
+ var cnt=translationResults.length;
+ //trans.translations=translations;
+ trans.single_translation=[];
+ trans.innerHTML="";
+ /*
+ trans.appendChild(wrap("h3",text(cnt<1 ? "No translations?" :
+ cnt>1 ? ""+cnt+" translations:":
+ "One translation:")));
+ */
+ for(p=0;p<cnt;p++) {
+ var tra=translationResults[p];
+ if (tra.translations != null) {
+ for (q = 0; q < tra.translations.length; q++) {
+ var t = tra.translations[q];
+ var lin=t.linearizations;
+ var tbody=empty("tbody");
+ if(options.show_abstract && t.tree)
+ tbody.appendChild(
+ tr([th(text("Abstract: ")),
+ tdt(node("span",{},[abstree_button(t.tree),
+ alignment_button(t.tree)]),
+ text(" "+t.tree))]));
+ for(var i=0;i<lin.length;i++) {
+ if(lin[i].to==to)
+ trans.single_translation.push(lin[i].text);
+ if(to=="All" || lin[i].to==to)
+ tbody.appendChild(tr([th(text(langpart(lin[i].to,grammar.name)+": ")),
+ tdt(parsetree_button(t.tree,lin[i].to),
+ text(lin[i].text))]));
+ }
+ trans.appendChild(wrap("table",tbody));
+ }
+ }
+ else if(tra.typeErrors) {
+ var errs=tra.typeErrors;
+ for(var i=0;i<errs.length;i++)
+ trans.appendChild(wrap("pre",text(errs[i].msg)))
+ }
+ }
+ }
+}
+
+Translations.prototype.show_groupedtranslations=function(translationsResult) {
+ with(this) {
+ var trans=main;
+ var to=target_lang();
+ //var to=to_menu.value // wrong
+ var cnt=translationsResult.length;
+ //trans.translations=translationsResult;
+ trans.single_translation=[];
+ trans.innerHTML="";
+ for(p=0;p<cnt;p++) {
+ var t=translationsResult[p];
+ if(to=="All" || t.to==to) {
+ var lin=t.linearizations;
+ var tbody=empty("tbody");
+ if(to=="All") tbody.appendChild(tr([th(text(t.to+":"))]));
+ for(var i=0;i<lin.length;i++) {
+ if(to!="All") trans.single_translation[i]=lin[i].text;
+ tbody.appendChild(tr([td(text(lin[i].text))]));
+ if (lin.length > 1) tbody.appendChild(tr([td(text(lin[i].tree))]));
+ }
+ trans.appendChild(wrap("table",tbody));
+ }
+ }
+ }
+}
+
+
+function abstree_button(abs) {
+ var i=button_img(tree_icon,"toggle_img(this)");
+ i.title="Click to display abstract syntax tree"
+ i.other=server.current_grammar_url+"?command=abstrtree&tree="+encodeURIComponent(abs);
+ return i;
+}
+
+function alignment_button(abs) {
+ var i=button_img(alignment_icon,"toggle_img(this)");
+ i.title="Click to display word alignment"
+ i.other=server.current_grammar_url+"?command=alignment&tree="+encodeURIComponent(abs);
+ return i;
+}
+
+function parsetree_button(abs,lang) {
+ var i=button_img(tree_icon,"toggle_img(this)");
+ i.title="Click to display parse tree"
+ i.other=server.current_grammar_url
+ +"?command=parsetree&from="+lang+"&tree="+encodeURIComponent(abs);
+ return i;
+}
diff --git a/src/www/minibar/pgf_offline.js b/src/www/minibar/pgf_offline.js
new file mode 100644
index 000000000..cd2d40b1c
--- /dev/null
+++ b/src/www/minibar/pgf_offline.js
@@ -0,0 +1,96 @@
+// Assumes that Services.js has been loaded
+
+function pgf_offline(options) {
+ var server = {
+ // State variables (private):
+ grammars_url: "",
+ grammar_list: ["Foods.pgf"],
+
+ current_grammar_url: null,
+ pgf : null,
+
+ // Methods:
+ switch_grammar: function(grammar_url,cont) {
+ //debug("switch_grammar ");
+ var new_grammar_url=this.grammars_url+grammar_url;
+ var self=this;
+ var update_pgf=function(pgfbinary) {
+ debug("Got "+new_grammar_url+", length="
+ +pgfbinary.length+", parsing... ");
+ self.pgf = {v: Services_decodePGF.v({v:pgfbinary}) }
+ //debug("done")
+ self.current_grammar_url=new_grammar_url;
+ cont();
+ }
+ ajax_http_get_binary(new_grammar_url,update_pgf);
+ },
+ get_grammarlist: function(cont) { cont([this.grammar_list]); },
+
+ get_languages: function(cont) {
+ cont(fromJSValue(Services_grammar.v(this.pgf)))
+ },
+ grammar_info: function(cont) {
+ cont(fromJSValue(Services_grammar.v(this.pgf)))
+ },
+
+ get_random: function(cont) {
+ alert("Random generation not supported yet in the offline version");
+ },
+ linearize: function(args,cont) {
+ cont(fromJSValue(Services_linearize.v(this.pgf)(v(args.tree))(v(args.to))));
+ },
+ complete: function(args,cont) {
+ cont(fromJSValue(Services_complete.v(this.pgf)(v(args.from))(v(args.input))));
+ },
+ parse: function(args,cont) {
+ cont(fromJSValue(Services_parse.v(this.pgf)(v(args.from))(v(args.input))));
+ },
+ translate: function(args,cont) {
+ cont(fromJSValue(Services_translate.v(this.pgf)(v(args.from))(v(args.input))));
+ },
+ translategroup: function(args,cont) {
+ cont(fromJSValue(Services_translategroup.v(this.pgf)(v(args.from))(v(args.input))));
+ }
+ };
+ for(var o in options) server[o]=options[o];
+ return server;
+};
+
+
+
+// See https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest
+function ajax_http_get_binary(url,callback) {
+ var http=GetXmlHttpObject()
+ if (http==null) {
+ alert ("Browser does not support HTTP Request")
+ return
+ }
+ var statechange=function() {
+ if (http.readyState==4 || http.readyState=="complete") {
+ if(http.status==200) {
+ var buffer=http.mozResponseArrayBuffer;
+ if(buffer) callback(bufferToString(buffer)) // Gecko 2 (Firefox 4)
+ else callback(http.responseText); // other browsers
+ }
+ else alert("Request for "+url+" failed: "
+ +http.status+" "+http.statusText);
+ }
+ }
+ http.onreadystatechange=statechange;
+ http.open("GET",url,true);
+ http.overrideMimeType('text/plain; charset=x-user-defined');
+ http.send(null);
+ //dump("http get "+url+"\n")
+ return http;
+}
+
+function bufferToString(buffer) {
+ // This function converts to the current representation of ByteString,
+ // but it would be better to use binary buffers for ByteStrings as well.
+ debug("bufferToString");
+ var u=new Uint8Array(buffer);
+ var a=new Array(u.length);
+ for(var i=0;i<u.length;i++)
+ a[i]=String.fromCharCode(u[i]);
+ return a.join("");
+}
diff --git a/src/www/minibar/pgf_online.js b/src/www/minibar/pgf_online.js
new file mode 100644
index 000000000..ac6620ee5
--- /dev/null
+++ b/src/www/minibar/pgf_online.js
@@ -0,0 +1,52 @@
+
+/* --- Grammar access object ------------------------------------------------ */
+
+function pgf_online(options) {
+ var server = {
+ // State variables (private):
+ grammars_url: "/grammars/",
+ grammar_list: null,
+ current_grammar_url: null,
+
+ // Methods:
+ switch_grammar: function(grammar_url,cont) {
+ this.current_grammar_url=this.grammars_url+grammar_url;
+ if(cont) cont();
+ },
+ get_grammarlist: function(cont) {
+ http_get_json(this.grammars_url+"grammars.cgi",cont);
+ },
+ pgf_call: function(cmd,args,cont) {
+ var url=this.current_grammar_url+"?command="+cmd+encodeArgs(args)
+ http_get_json(url,cont);
+ },
+
+ get_languages: function(cont) { this.pgf_call("grammar",{},cont); },
+ grammar_info: function(cont) { this.pgf_call("grammar",{},cont); },
+
+ get_random: function(args,cont) { // cat, limit
+ args.random=Math.random(); // side effect!!
+ this.pgf_call("random",args,cont);
+ },
+ linearize: function(args,cont) { // tree, to
+ this.pgf_call("linearize",args,cont);
+ },
+ complete: function(args,cont) { // from, input, cat, limit
+ this.pgf_call("complete",args,cont);
+ },
+ parse: function(args,cont) { // from, input, cat
+ this.pgf_call("parse",args,cont);
+ },
+ translate: function(args,cont) { // from, input, cat, to
+ this.pgf_call("translate",args,cont);
+ },
+ translategroup: function(args,cont) { // from, input, cat, to
+ this.pgf_call("translategroup",args,cont);
+ }
+
+ };
+ for(var o in options) server[o]=options[o];
+ if(server.grammar_list && server.grammar_list.length>0)
+ server.switch_grammar(server.grammar_list[0]);
+ return server;
+} \ No newline at end of file
diff --git a/src/www/minibar/phrasebook.html b/src/www/minibar/phrasebook.html
new file mode 100644
index 000000000..78f5c353f
--- /dev/null
+++ b/src/www/minibar/phrasebook.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Phrasebook</title>
+<link rel=stylesheet type="text/css" href="minibar.css">
+<meta charset="UTF-8">
+<meta name = "viewport" content = "width = device-width">
+</head>
+
+<body>
+
+<div id=minibar></div>
+
+<hr>
+
+<small>
+
+Powered by <a href="http://www.grammaticalframework.org/">GF</a>,
+see <a href="http://www.grammaticalframework.org/examples/phrasebook/doc-phrasebook.html">doc</a>.
+
+</small>
+
+<script type="text/JavaScript" src="support.js"></script>
+<script type="text/JavaScript" src="minibar.js"></script>
+<script type="text/JavaScript" src="minibar_input.js"></script>
+<script type="text/JavaScript" src="minibar_translations.js"></script>
+<script type="text/JavaScript" src="minibar_support.js"></script>
+<script type="text/JavaScript" src="pgf_online.js"></script>
+
+<script type="text/JavaScript">
+
+var online_options={
+ // grammars_url: "http://www.grammaticalframework.org/grammars/",
+//grammars_url: "http://tournesol.cs.chalmers.se:41296/grammars",
+//grammars_url: "http://localhost:41296/grammars/",
+ grammar_list: ["Phrasebook.pgf"] // leave undefined to get list from server
+}
+
+var server=pgf_online(online_options);
+
+var phrasebook_options={
+ delete_button_text: "Del",
+ help_url: "http://www.grammaticalframework.org/examples/phrasebook/help-phrasebook.html",
+ feedback_url: "feedback.html",
+ default_source_language: "Eng"
+}
+
+start_minibar(server,phrasebook_options)
+</script>
+
+</body>
+
+</html>
+
+
+
diff --git a/src/www/minibar/saldotest.html b/src/www/minibar/saldotest.html
new file mode 100644
index 000000000..179c42e4d
--- /dev/null
+++ b/src/www/minibar/saldotest.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<title>Saldotest</title>
+<link rel=stylesheet type="text/css" href="minibar.css">
+<script type="text/JavaScript" src="support.js"></script>
+<script type="text/JavaScript" src="saldotest.js"></script>
+<meta name = "viewport" content = "width = device-width">
+</head>
+
+<body onload="start_saldotest();start_saldospel()">
+
+<h2>Vilket ord ska bort?</h2>
+<div id=saldospel>
+</div>
+
+<h2>Hel- och halvspöke</h2>
+<div id=saldotest>
+</div>
+
+<hr>
+<small>
+[Baserad på <a href="http://spraakbanken.gu.se/sal/ws/">SALDOs nättjänster</a>]
+</small>
+<small class=modtime>
+HTML <!-- hhmts start --> Last modified: Thu May 27 14:02:42 CEST 2010 <!-- hhmts end -->
+</small>
+<address>TH <img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address>
+</body>
+
+</html>
diff --git a/src/www/minibar/saldotest.js b/src/www/minibar/saldotest.js
new file mode 100644
index 000000000..47cb95047
--- /dev/null
+++ b/src/www/minibar/saldotest.js
@@ -0,0 +1,340 @@
+
+var Saldo_ws_url = "http://spraakbanken.gu.se/ws/saldo-ws/";
+//var Saldo_ff_url = Saldo_ws_url+"ff/json+remember_completions/";
+var Saldo_lid_url = Saldo_ws_url+"lid/json";
+
+function saldo_ws(fn,fmt,arg,cont_name) {
+ jsonp(Saldo_ws_url+fn+"/"+fmt+(cont_name ? "+"+cont_name : "")+"/"+arg,"");
+}
+
+function saldo_json(fn,arg,cont_name) { saldo_ws(fn,"json",arg,cont_name); }
+function saldo_lid(arg,cont_name) { saldo_json("lid",arg,cont_name); }
+function saldo_lid_rnd(cont_name) { saldo_lid("rnd?"+Math.random(),cont_name); }
+
+var ordlista=[];
+var current="";
+
+function start_saldotest() {
+ appendChildren(element("saldotest"),
+ [button("Slumpa","random_word()"),
+ button("Rensa","clear_all()"),
+ button("⌫","delete_last()"),
+ //button("Ordlista","show_ordlista()"),
+ button("Visa tänkbara drag","show_moves()"),
+ button("Gör ett drag","make_a_move()"),
+ //button("Visa prefix","show_prefixes()"),
+ div_id("surface"),
+ div_id("words"),
+ div_id("translations")])
+ var style0="min-height: 3ex; margin: 5px; padding: 5px;";
+ element("surface").setAttribute("style",style0+"border: 3px dashed #e0e0e0;");
+ element("words").setAttribute("style",style0+"border: 3px solid #e0e0e0;");
+ clear_all();
+}
+
+function random_word() {
+ saldo_lid_rnd("show_random");
+}
+
+function show_random(lid) {
+ var lex=lid.lex;
+ reset_all(lex.substring(0,lex.indexOf('.')));
+}
+
+function clear_all() { reset_all(""); }
+
+function reset_all(s) {
+ current=s;
+ element("surface").innerHTML=s;
+ element("translations").innerHTML="";
+ get_completions();
+}
+
+function delete_last() {
+ var len=current.length;
+ if(len>0) {
+ current=current.substring(0,len-1);
+ var s=element("surface");
+ s.innerHTML=current;
+ element("translations").innerHTML="";
+ get_completions();
+ }
+}
+
+function with_completions(s,cont) {
+ var c=ordlista[s];
+ if(c && c.a) cont(c);
+ else {
+ //if(c) alert("c already has fields"+field_names(c));
+ ordlista[s]={put: function(c) { ordlista[s]=c; cont(c); }};
+ var url=Saldo_ws_url+"ff/json+ordlista[\""+s+"\"].put/"+encodeURIComponent(s);
+ jsonp(url,"");
+ }
+}
+
+function get_completions() {
+ with_completions(current,show_completions);
+}
+
+function word(s) {
+ //var w=span_class("word",text(s));
+ //if(s==" ") w.innerHTML="&nbsp;";
+ //w.setAttribute("onclick",'extend_current("'+s+'")');
+ //return w;
+ return button(s,'extend_current("'+s+'")');
+}
+
+function extend_current(s) {
+ current+=s;
+ element("words").innerHTML="";
+ element("surface").innerHTML=current;
+ get_completions();
+}
+
+function show_completions(saldo_ff) {
+ var box=element("words");
+ box.innerHTML="";
+ //var c=saldo_ff.c.split("");
+ var c=filter(allowed,saldo_ff.c);
+ sort(c);
+ for(var i=0;i<c.length;i++) {
+ var s=c[i];
+ if(s!='-')
+ box.appendChild(word(s));
+ }
+ show_translations(saldo_ff.a);
+}
+
+function allowed(c) {
+ switch(c) {
+ case 'å':
+ case 'ä':
+ case 'ö':
+ case 'é':
+ case 'ü':
+ return true;
+ default:
+ return 'a'<=c && c<='z';
+ }
+}
+
+// ordklasser: mxc sxc (förekommer bara som prefix),
+// *h (förekommer bara som suffix)
+function ignore(msd) {
+ switch(msd) {
+ case "c":
+ case "ci":
+ case "cm":
+ case "seg":
+ case "sms":
+ return true;
+ default:
+ return false;
+ }
+}
+
+function count_wordforms(a) {
+ var cnt=0;
+ for(var i=0;i<a.length;i++)
+ if(!ignore(a[i].msd)) cnt++;
+ return cnt;
+}
+
+function pad(s) {
+ return s.length>0 ? " "+s : "";
+}
+
+function show_translations(a) {
+ var tr=element("translations");
+ tr.innerHTML="";
+ //if(!a) alert("a undefined in show_translations");
+ if(count_wordforms(a)<1) {
+ tr.appendChild(p(text(a.length<1 ? "Detta är inte en giltig ordform"
+ : "Denna form förekommer bara i sammansättningar")));
+ element("surface").setAttribute("class","invalid");
+ }
+ else {
+ element("surface").setAttribute("class","valid");
+ for(var i=0;i<a.length;i++)
+ if(!ignore(a[i].msd))
+ tr.appendChild(p(text(a[i].gf+" ("+a[i].pos+pad(a[i].is)+", "+a[i].msd+")")));
+ }
+}
+
+function show_ordlista() {
+ var trans=element("translations");
+ trans.innerHTML="Följande ord har slagits upp: ";
+ var apnd=function(el) { trans.appendChild(el) };
+ for(var i in ordlista) {
+ apnd(empty("br"));
+ apnd(span_class(ordlista[i].a.length<1 ? "invalid" : "valid",text(" "+i)));
+ apnd(text(": "+(ordlista[i].ok!=null ? ordlista[i].ok.length : "?")
+ +"/"+(ordlista[i].allowed!=null ? ordlista[i].allowed.length : "?")));
+ }
+}
+
+function extend_ordlista(s,cs,cont) {
+ if(cs.length<1) cont();
+ else {
+ var c=cs[0];
+ var cs2=cs.substring(1);
+ with_completions(s+c,function(o){extend_ordlista(s,cs2,cont)});
+ }
+}
+
+function known_possible_moves(s,cont) {
+ var c=implode(sort(filter(allowed,ordlista[s].c)));
+ ordlista[s].allowed=c;
+ extend_ordlista(s,c,function() {
+ var ok="";
+ for(var i=0;i<c.length;i++) {
+ var next=s+c[i];
+ var ff=ordlista[next];
+ //if(!ff.a) alert(show_props(ff,"ff"));
+ if(next.length<2 || count_wordforms(ff.a)<1) ok+=c[i];
+ }
+ ordlista[s].ok=ok;
+ cont(ok);
+ }
+ );
+}
+
+function unknown_possible_moves(s,cont) {
+ with_completions(s,function(c){known_possible_moves(s,cont);});
+}
+
+function currently_possible_moves(cont) {
+ known_possible_moves(current,cont);
+}
+
+function show_moves() {
+ var trans=element("translations");
+ trans.innerHTML="Letar efter möjliga drag";
+ currently_possible_moves(function(ok) {
+ trans.innerHTML="Tänkbara drag: "+ok;
+ winning_moves(trans,ok);
+ });
+}
+
+function winning_moves(trans,ok) {
+ var ws=map(function(c){return current+c;},ok);
+ mapc(unknown_possible_moves,ws,function(oks){
+ var winning="";
+ for(i=0;i<oks.length;i++)
+ if(oks[i].length<1) winning+=ok[i];
+ trans.innerHTML+="<br>Vinnande drag: "+winning;
+ });
+}
+
+function make_a_move() {
+ currently_possible_moves(function(ok) {
+ if(ok.length<1) element("translations").innerHTML="Hittade inga möjliga drag!";
+ else {
+ var i=Math.floor(Math.random()*ok.length);
+ extend_current(ok[i]);
+ }
+ }
+ );
+}
+
+function show_prefixes_of(trans,s) {
+ if(s.length>0) {
+ var p=s.substr(0,s.length-1);
+ with_completions(p,function(c) {
+ if(count_wordforms(c.a)>0) trans.innerHTML+="<br>"+p;
+ show_prefixes_of(trans,p);
+ });
+ }
+}
+
+function show_prefixes() {
+ var trans=element("translations");
+ trans.innerHTML="Prefix av "+current+":";
+ show_prefixes_of(trans,current);
+}
+
+/* -------------------------------------------------------------------------- */
+
+var spel={ antal_ord: 4, // antal närbesläktade ord att visa
+ antal_korrekta_svar: 0,
+ antal_felaktiga_svar: 0
+ };
+
+function start_saldospel() {
+ spel.hylla=div_id("hylla");
+ spel.status=div_id("status");
+ //element("saldospel").innerHTML="<span id=score></span>";
+ appendChildren(element("saldospel"),
+ [spel.hylla,spel.status,
+ p(text("")),
+ button("Nya ord","spel0()"),
+ text(" "),
+ wrap("b",span_id("score"))]);
+ spel.score=element("score");
+ show_score();
+ spel0();
+}
+
+function spel0() { // Välj ord 1
+ saldo_lid_rnd("spel1");
+}
+
+function spel1(lid) { // Slå upp md1 för ord 1
+ spel.lid=lid;
+ saldo_json("md1",lid.lex,"spel2");
+}
+
+function spel2(md1) { // Kontrollera att det finns minst 4 ord i md1 för ord1
+ if(md1.length<spel.antal_ord) spel0();
+ else {
+ spel.md1=md1;
+ spel3();
+ }
+}
+
+function spel3() { // Välj ord 2
+ saldo_lid_rnd("spel4");
+}
+
+function spel4(lid) { // Slå upp md1 för ord 2
+ spel.lid2=lid;
+ saldo_json("md1",lid.lex,"spel5");
+}
+
+function spel5(md1) { // Kontrollera att ord 1 och ord 2 inte har något gemensamt
+ var ordlista1=map(wf,spel.md1);
+ var ord2=wf(spel.lid2.lex);
+ var ordlista2=map(wf,md1).concat(ord2);
+ if(overlaps(ordlista1,ordlista2)) spel3();
+ else spel6(ordlista1,ord2);
+}
+
+function spel6(ordlista1,ord2) {
+ spel.ord2=ord2;
+ var pos=Math.floor(Math.random()*spel.antal_ord);
+ var ordlista=shuffle(shuffle(ordlista1).slice(0,spel.antal_ord).concat(ord2));
+ spel.hylla.innerHTML="";
+ var lista=empty_class("div","space");
+ for(var i=0;i<ordlista.length;i++)
+ lista.appendChild((button(ordlista[i],"spel7(this)")));
+ spel.hylla.appendChild(lista);
+}
+
+function spel7(btn) {
+ btn.disabled=true;
+ var ok=btn.value==spel.ord2;
+ //btn.setAttribute("class",ok ? "correct" : "incorrect");
+ btn.setAttribute("style",ok ? "color: green" : "color: red");
+ if(ok) spel.antal_korrekta_svar++; else spel.antal_felaktiga_svar++;
+ show_score();
+ if(ok) spel0();
+}
+
+function show_score() {
+ spel.score.innerHTML=""+spel.antal_korrekta_svar+" rätt, "
+ +spel.antal_felaktiga_svar+" fel";
+}
+
+function wf(ord) { // word form, wf("band..1") == "band"
+ return ord.split(".",1)[0].split("_").join(" ");
+}
diff --git a/src/www/minibar/support.js b/src/www/minibar/support.js
new file mode 100644
index 000000000..c65c1a389
--- /dev/null
+++ b/src/www/minibar/support.js
@@ -0,0 +1,300 @@
+/* --- Accessing document elements ------------------------------------------ */
+
+function element(id) {
+ return document.getElementById(id);
+}
+
+/* --- JavaScript tricks ---------------------------------------------------- */
+
+// To be able to use object methods that refer to "this" as callbacks
+// See section 3.3 of https://github.com/spencertipping/js-in-ten-minutes/raw/master/js-in-ten-minutes.pdf
+function bind(f, this_value) {
+ return function () {return f.apply (this_value, arguments)};
+};
+
+/* --- JSONP ---------------------------------------------------------------- */
+
+// Inspired by the function jsonp from
+// http://www.west-wind.com/Weblog/posts/107136.aspx
+// See also http://niryariv.wordpress.com/2009/05/05/jsonp-quickly/
+// http://en.wikipedia.org/wiki/JSON#JSONP
+function jsonp(url,callback)
+{
+ if (url.indexOf("?") > -1)
+ url += "&jsonp="
+ else
+ url += "?jsonp="
+ url += callback;
+ //url += "&" + new Date().getTime().toString(); // prevent caching
+
+ var script = empty("script");
+ script.setAttribute("src",url);
+ script.setAttribute("type","text/javascript");
+ document.body.appendChild(script);
+}
+
+var json = {next:0};
+
+// Like jsonp, but instead of passing the name of the callback function, you
+// pass the callback function directly, making it possible to use anonymous
+// functions.
+function jsonpf(url,callback)
+{
+ var name="callback"+(json.next++);
+ json[name]=function(x) { delete json[name]; callback(x); }
+ jsonp(url,"json."+name);
+}
+
+/* --- AJAX ----------------------------------------------------------------- */
+
+function GetXmlHttpObject(handler)
+{
+ var objXMLHttp=null
+ if (window.XMLHttpRequest)
+ {
+ // See http://www.w3.org/TR/XMLHttpRequest/
+ // https://developer.mozilla.org/en/xmlhttprequest
+ objXMLHttp=new XMLHttpRequest()
+ }
+ else if (window.ActiveXObject)
+ {
+ objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP")
+ }
+ return objXMLHttp
+}
+
+function ajax_http(method,url,body,callback,errorcallback) {
+ var http=GetXmlHttpObject()
+ if (!http) {
+ var errortext="Browser does not support HTTP Request";
+ if(errorcallback) errorcallback(errortext,500)
+ else alert(errortext)
+ }
+ else {
+ var statechange=function() {
+ if (http.readyState==4 || http.readyState=="complete") {
+ if(http.status<300) callback(http.responseText,http.status);
+ else if(errorcallback) errorcallback(http.responseText,http.status);
+ else alert("Request for "+url+" failed: "
+ +http.status+" "+http.statusText);
+ }
+ }
+ http.onreadystatechange=statechange;
+ http.open(method,url,true)
+ http.send(body)
+ }
+ return http
+}
+
+function ajax_http_get(url,callback,errorcallback) {
+ ajax_http("GET",url,null,callback,errorcallback)
+}
+
+function ajax_http_post(url,formdata,callback,errorcallback) {
+ ajax_http("POST",url,formdata,callback,errorcallback)
+ // See https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest#Using_FormData_objects
+}
+
+// JSON via AJAX
+function ajax_http_get_json(url,cont) {
+ ajax_http_get(url,function(txt) { cont(eval("("+txt+")")); });
+}
+
+function sameOrigin(url) {
+ var a=empty("a");
+ a.href=url; // converts to an absolute URL
+ return hasPrefix(a.href,location.protocol+"//"+location.host+"/");
+}
+
+// Use AJAX when possible, fallback to JSONP
+function http_get_json(url,cont) {
+ if(sameOrigin(url)) ajax_http_get_json(url,cont);
+ else jsonpf(url,cont);
+}
+
+/* --- URL construction ----------------------------------------------------- */
+
+function encodeArgs(args) {
+ var q=""
+ for(var arg in args)
+ if(args[arg]!=undefined)
+ q+="&"+arg+"="+encodeURIComponent(args[arg]);
+ return q;
+}
+
+/* --- HTML construction ---------------------------------------------------- */
+function text(s) { return document.createTextNode(s); }
+
+function node(tag,as,ds) {
+ var n=document.createElement(tag);
+ for(var a in as) n.setAttribute(a,as[a]);
+ for(var i in ds) n.appendChild(ds[i]);
+ return n;
+}
+
+function empty(tag,name,value) {
+ var el=node(tag,{},[])
+ if(name && value) el.setAttribute(name,value);
+ return el;
+}
+
+function empty_id(tag,id) { return empty(tag,"id",id); }
+function empty_class(tag,cls) { return empty(tag,"class",cls); }
+
+function div_id(id) { return empty_id("div",id); }
+function span_id(id) { return empty_id("span",id); }
+
+function wrap(tag,contents) { return node(tag,{},[contents]); }
+
+function wrap_class(tag,cls,contents) {
+ var el=empty_class(tag,cls);
+ if(contents) el.appendChild(contents);
+ return el;
+}
+
+function span_class(cls,contents) { return wrap_class("span",cls,contents); }
+function div_class(cls,contents) { return wrap_class("div",cls,contents); }
+
+function p(contents) { return wrap("p",contents); }
+function dt(contents) { return wrap("dt",contents); }
+function li(contents) { return wrap("li",contents); }
+
+function th(contents) { return wrap("th",contents); }
+function td(contents) { return wrap("td",contents); }
+
+function tr(cells) { return node("tr",{},cells); }
+
+function button(label,action,key) {
+ var el=node("input",{"type":"button","value":label},[]);
+ if(typeof action=="string") el.setAttribute("onclick",action);
+ else el.onclick=action;
+ if(key) el.setAttribute("accesskey",key);
+ return el;
+}
+
+function option(label,value) {
+ return node("option",{"value":value},[text(label)]);
+}
+
+function appendChildren(el,ds) {
+ for(var i in ds) el.appendChild(ds[i]);
+ return el;
+}
+
+function insertFirst(parent,child) {
+ parent.insertBefore(child,parent.firstChild);
+}
+
+function tda(cs) { return node("td",{},cs); }
+
+function img(src) { return empty("img","src",src); }
+
+/* --- Debug ---------------------------------------------------------------- */
+
+function debug(s) {
+ var d=element("debug");
+ if(d) d.appendChild(text(s+"\n"))
+}
+
+function show_props(obj, objName) {
+ var result = "";
+ for (var i in obj) {
+ result += objName + "." + i + " = " + obj[i] + "<br>";
+ }
+ return result;
+}
+
+function field_names(obj) {
+ var result = "";
+ for (var i in obj) {
+ result += " " + i;
+ }
+ return result;
+}
+
+/* --- Data manipulation ---------------------------------------------------- */
+function swap(a,i,j) { // Note: this doesn't work on strings.
+ var tmp=a[i];
+ a[i]=a[j];
+ a[j]=tmp;
+ return a;
+}
+
+function sort(a) {
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/sort
+ return a.sort();
+ /* // Note: this doesn't work on strings.
+ for(var i=0;i<a.length-1;i++) {
+ var min=i;
+ for(var j=i+1;j<a.length;j++)
+ if(a[j]<a[min]) min=j;
+ if(min!=i) swap(a,i,min);
+ }
+ return a;
+ */
+}
+
+function filter(p,xs) {
+ var ys=[];
+ for(var i=0;i<xs.length;i++)
+ if(p(xs[i])) ys[ys.length]=xs[i];
+ return ys;
+}
+
+function implode(cs) { // array of strings to string
+ /*
+ var s="";
+ for(var i=0;i<cs.length;i++)
+ s+=cs[i];
+ return s;
+ */
+ return cs.join("");
+}
+
+function hasPrefix(s,pre) { return s.substr(0,pre.length)==pre; }
+
+function commonPrefix(s1,s2) {
+ for(var i=0;i<s1.length && i<s2.length && s1[i]==s2[i];i++);
+ return s1.substr(0,i);
+}
+
+/*
+function all(p,xs) {
+ for(var i=0;i<xs.length;i++)
+ if(!p(xs[i])) return false;
+ return true;
+}
+*/
+
+function map(f,xs) {
+ var ys=[];
+ for(var i=0;i<xs.length;i++) ys[i]=f(xs[i]);
+ return ys;
+}
+
+// map in continuation passing style
+function mapc(f,xs,cont) { mapc_from(f,xs,0,[],cont); }
+
+function mapc_from(f,xs,i,ys,cont) {
+ if(i<xs.length)
+ f(xs[i],function(y){ys[i]=y;mapc_from(f,xs,i+1,ys,cont)});
+ else
+ cont(ys);
+}
+
+function overlaps(as,bs) {
+ for(var i=0;i<as.length;i++)
+ if(elem(as[i],bs)) return true;
+ return false;
+}
+
+function elem(a,as) {
+ for(var i=0;i<as.length;i++)
+ if(a==as[i]) return true;
+ return false;
+}
+
+function shuffle(a) {
+ for(i=0;i<a.length;i++) swap(a,i,Math.floor(Math.random()*a.length))
+ return a;
+}
diff --git a/src/www/minibar/tree-btn.png b/src/www/minibar/tree-btn.png
new file mode 100644
index 000000000..ebd243617
--- /dev/null
+++ b/src/www/minibar/tree-btn.png
Binary files differ