Improvements of "gf -server" mode and related setup
"gf -server" mode now contains everything needed to run the minibar and the grammar editor (including example-based grammar writing). The Setup.hs script installs the required files where gf -server can find them. These files have been moved to a new directory: src/www. The separate server program pgf-http is now obsolete.
7
src/www/gfse/Makefile
Normal file
@@ -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
|
||||
BIN
src/www/gfse/P/1306856253_weather_06.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/www/gfse/P/1307545089_weather_04.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/www/gfse/P/w1s.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/www/gfse/P/w2s.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
src/www/gfse/P/w3s.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/www/gfse/P/w4s.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
43
src/www/gfse/TODO
Normal file
@@ -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.
|
||||
|
||||
243
src/www/gfse/about.html
Normal file
@@ -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>
|
||||
148
src/www/gfse/cloud.js
Normal file
@@ -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)
|
||||
}
|
||||
159
src/www/gfse/cloud2.js
Normal file
@@ -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);
|
||||
}
|
||||
88
src/www/gfse/editor.css
Normal file
@@ -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; }
|
||||
1114
src/www/gfse/editor.js
Normal file
158
src/www/gfse/example_based.js
Normal file
@@ -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
|
||||
}
|
||||
226
src/www/gfse/gf_abs.js
Normal file
@@ -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;
|
||||
}
|
||||
4
src/www/gfse/gfse.manifest
Normal file
@@ -0,0 +1,4 @@
|
||||
CACHE MANIFEST
|
||||
# 5
|
||||
NETWORK:
|
||||
*
|
||||
19
src/www/gfse/grammars.cgi
Normal file
@@ -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 "*/"
|
||||
48
src/www/gfse/index.html
Normal file
@@ -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>
|
||||
21
src/www/gfse/localstorage.js
Normal file
@@ -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); }
|
||||
}
|
||||
82
src/www/gfse/molto.css
Normal file
@@ -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; }
|
||||
25
src/www/gfse/save.hs
Normal file
@@ -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
|
||||
27
src/www/gfse/share.html
Normal file
@@ -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>
|
||||
86
src/www/gfse/slideshow.js
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
252
src/www/gfse/upload.cgi
Normal file
@@ -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
|
||||
18
src/www/index.html
Normal file
@@ -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>
|
||||
180
src/www/minibar/about.html
Normal file
@@ -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 & 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><input type=button></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 α 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>
|
||||
BIN
src/www/minibar/align-btn.png
Normal file
|
After Width: | Height: | Size: 138 B |
BIN
src/www/minibar/brushed-metal.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
57
src/www/minibar/example.html
Normal file
@@ -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>
|
||||
44
src/www/minibar/feedback.cgi
Normal file
@@ -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="<- 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
|
||||
48
src/www/minibar/feedback.html
Normal file
@@ -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>
|
||||
151
src/www/minibar/gf-web-api-examples.html
Normal file
@@ -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> grammars_url: "http://www.grammaticalframework.org/grammars/",
|
||||
<br> 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>
|
||||
|
||||
235
src/www/minibar/minibar-api.html
Normal file
@@ -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>
|
||||
<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="support.js"></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><div id="minibar"></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>
|
||||
<script type="text/JavaScript" src="minibar_input.js"></script>
|
||||
<script type="text/JavaScript" src="minibar_support.js"></script>
|
||||
<script type="text/JavaScript" src="support.js"></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>
|
||||
<script type="text/JavaScript" src="minibar_input.js"></script>
|
||||
<script type="text/JavaScript" src="minibar_support.js"></script>
|
||||
<script type="text/JavaScript" src="support.js"></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>
|
||||
53
src/www/minibar/minibar.css
Normal file
@@ -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; }
|
||||
41
src/www/minibar/minibar.html
Normal file
@@ -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>
|
||||
& <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>
|
||||
176
src/www/minibar/minibar.js
Normal file
@@ -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
|
||||
*/
|
||||
277
src/www/minibar/minibar_input.js
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/www/minibar/minibar_online.js
Normal file
@@ -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);
|
||||
46
src/www/minibar/minibar_support.js
Normal file
@@ -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); }
|
||||
162
src/www/minibar/minibar_translations.js
Normal file
@@ -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;
|
||||
}
|
||||
96
src/www/minibar/pgf_offline.js
Normal file
@@ -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("");
|
||||
}
|
||||
52
src/www/minibar/pgf_online.js
Normal file
@@ -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;
|
||||
}
|
||||
56
src/www/minibar/phrasebook.html
Normal file
@@ -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>
|
||||
|
||||
|
||||
|
||||
30
src/www/minibar/saldotest.html
Normal file
@@ -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>
|
||||
340
src/www/minibar/saldotest.js
Normal file
@@ -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=" ";
|
||||
//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(" ");
|
||||
}
|
||||
300
src/www/minibar/support.js
Normal file
@@ -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;
|
||||
}
|
||||
BIN
src/www/minibar/tree-btn.png
Normal file
|
After Width: | Height: | Size: 149 B |