diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8d29a1f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__ +.pyc +.env +Pipfile* +.DS_Store diff --git a/AUTHORS b/AUTHORS index c379eed3..e57f1b6f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,3 +15,4 @@ Other contributors Francis Tyers (@ftyers) Sushain Cherivirala (@sushain97) Kevin Brubeck Unhammer (@unhammer) + Kevin Murphy (@keggsmurph21) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..183b36ba --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +certifi==2018.1.18 +chardet==3.0.4 +click==6.7 +Flask==0.12.2 +GitHub-Flask==3.2.0 +idna==2.6 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +requests==2.18.4 +urllib3==1.22 +Werkzeug==0.14.1 diff --git a/server/env.py b/server/env.py new file mode 100644 index 00000000..a9bb6ffb --- /dev/null +++ b/server/env.py @@ -0,0 +1,20 @@ +import os + +class Env(): + def __init__(self, filepath): + self.variables = {} + self.read(filepath) + + def read(self, filepath): + with open(filepath) as f: + for line in f.readlines(): + key,value = line.split('=') + self.variables[key] = value.strip('\n') + + def get(self, key): + if key in self.variables: + return self.variables[key] + return None + + def __repr__(self): + return str(self.variables) diff --git a/server/server.py b/server/server.py index e84114f4..b0a11423 100644 --- a/server/server.py +++ b/server/server.py @@ -12,9 +12,12 @@ from flask import send_file from flask import send_from_directory from flask import url_for +from flask_github import GitHub + import os import uuid from db import CorpusDB +from env import Env PATH_TO_CORPORA = 'corpora' @@ -25,7 +28,11 @@ ******************************************************************************* ''' +environment = Env('.env') app = Flask(__name__, static_folder='../standalone', static_url_path='/annotatrix') +app.config['GITHUB_CLIENT_ID'] = '2aed75d6a4e13b9dd029' +app.config['GITHUB_CLIENT_SECRET'] = environment.get('GITHUB_CLIENT_SECRET') +github = GitHub(app) if not os.path.exists(PATH_TO_CORPORA): os.mkdir(PATH_TO_CORPORA) @@ -69,7 +76,7 @@ def download_corpus(): if os.path.exists(PATH_TO_CORPORA + '/' + db_path): db = CorpusDB(PATH_TO_CORPORA + '/' + db_path) corpus, corpus_name = db.get_file() - with open(PATH_TO_CORPORA + '/' + treebank_id, 'w') as f: + with open(PATH_TO_CORPORA + '/' + treebank_id, 'w') as f: f.write(corpus) return send_file(PATH_TO_CORPORA + '/' + treebank_id, as_attachment=True, attachment_filename=corpus_name) return jsonify({'corpus': 'something went wrong'}) @@ -103,7 +110,28 @@ def annotatrix(): def index(): return send_from_directory('../standalone', 'welcome_page.html') +@app.route('/login', methods=['GET', 'POST']) +def login(): + return github.authorize() + +@app.route('/github-callback', methods=['GET', 'POST']) +@github.authorized_handler +def authorized(oauth_token): + print('token', oauth_token) + + next_url = request.args.get('next') or url_for('index') + if oauth_token is None: + return redirect(next_url) + + ''' + user = User.query.filter_by(github_access_token=oauth_token).first() + if user is None: + user = User(oauth_token) + db_session.add(user) + user.github_access_token = oauth_token + db_session.commit()''' + return redirect(next_url) # @app.route('/', methods=['GET', 'POST']) # def index_corpus(treebank_id): # return redirect(url_for('corpus_page', treebank_id=treebank_id)) diff --git a/standalone/lib/annotator.js b/standalone/lib/annotator.js index 1b33ded8..9f9f0d53 100644 --- a/standalone/lib/annotator.js +++ b/standalone/lib/annotator.js @@ -87,10 +87,10 @@ function getContents() { // // TODO: implement // } else { var splitted = localStorage.getItem('treebank'); // TODO: implement a more memory-friendly func? - splitted = JSON.parse(splitted); // string to array + splitted = JSON.parse(splitted) || {}; // string to array splitted[CURRENTSENTENCE] = $("#indata").val(); localStorage.setItem('treebank', JSON.stringify(splitted)); // update the treebank - return splitted.join('\n\n'); + // return splitted.join('\n\n'); NOTE: doesn't make sense to call .join() on an Object ... // } } @@ -140,6 +140,7 @@ function bindCyHandlers() { /* Binds event handlers to cy elements. NOTE: If you change the style of a node (e.g. its selector) then you also need to update it here. */ + cy.on('click', 'node, edge', clickInCy); cy.on('click', 'node.wf', drawArcs); cy.on('cxttapend', 'edge.dependency', selectArc); cy.on('click', 'node.pos', changeNode); @@ -149,6 +150,16 @@ function bindCyHandlers() { cy.on('zoom', cy.center); // center the view port when the page zoom is changed } +function clickInCy() { + console.log(this); + + let oldValue = $('#edit').attr( 'value' ); + let newValue = $('#edit').val(); + + if ( ISEDITING && (oldValue!==newValue) ) + writePOS( newValue ); +} + function loadFromUrl(argument) { /* Check if the URL contains arguments. If it does, takes first @@ -290,9 +301,9 @@ function splitIntoSentences(corpus) { // splitting if (format == "plain text") { - var splitted = corpus.match(/[^ ].+?[.!?](?=( |$))/g); + var splitted = corpus.match(/[^ ].+?[.!?](?=( |$))/g) || []; } else { - var splitted = corpus.split("\n\n"); + var splitted = corpus.split("\n\n") || []; } // removing empty lines @@ -316,7 +327,7 @@ function showDataIndiv() { } if(AVAILABLESENTENCES != 0) { document.getElementById('currentsen').value = (CURRENTSENTENCE+1); - } else { + } else { document.getElementById('currentsen').value = 0; } document.getElementById('totalsen').innerHTML = AVAILABLESENTENCES; @@ -432,7 +443,7 @@ function exportCorpora() { if (SERVER_RUNNING) { console.log('exportCorpora'); downloadCorpus(); - } else { + } else { var finalcontent = getContents(); var link = document.createElement('a'); @@ -468,16 +479,16 @@ function drawTree() { 1. removes the previous tree, if there's one 2. takes the data from the textarea 3. */ - + ISEDITING = false; - + // TODO: update the sentence try {cy.destroy()} catch (err) {}; // remove the previous tree, if there is one var content = $("#indata").val(); // TODO: rename var format = detectFormat(content); - // -- to be moved out-- + // -- to be moved out-- // content = content.replace(/ +\n/, '\n'); // remove extra spaces at the end of lines. #89 // $("#indata").val(content); // TODO: what is this line for? @@ -505,13 +516,13 @@ function drawTree() { var newContent = cleanConllu(content); // TODO: move this one inside of this func // If there are >1 CoNLL-U format sentences is in the input, treat them as such - // conlluMultiInput(newContent); // TODO: move this one also inside of this func, and make a separate func for calling them all at the same time + // conlluMultiInput(newContent); // TODO: move this one also inside of this func, and make a separate func for calling them all at the same time if(newContent != content) { content = newContent; $("#indata").val(content); } - // -- to be moved out -- + // -- to be moved out -- conlluDraw(content); showProgress(); @@ -557,7 +568,7 @@ function detectFormat(content) { // console.log('[0] detectFormat() WARNING EMPTY CONTENT'); return "Unknown"; } - + var firstWord = content.replace(/\n/g, " ").split(" ")[0]; //console.log('[0] detectFormat() ' + content.length + " | " + FORMAT); @@ -677,12 +688,12 @@ function getLocalStorageMaxSize(error) { minimalFound = 0, error = error || 25e4; - // fill a string with 1024 symbols / bytes + // fill a string with 1024 symbols / bytes while (i--) string1024 += 1e16; i = max / 1024; - // fill a string with 'max' amount of symbols / bytes + // fill a string with 'max' amount of symbols / bytes while (i--) string += string1024; i = max; diff --git a/standalone/lib/gui.js b/standalone/lib/gui.js index 20b20812..e97a0fbb 100644 --- a/standalone/lib/gui.js +++ b/standalone/lib/gui.js @@ -32,6 +32,15 @@ var POS2RELmappings = { "SCONJ": "mark" } +function getSlicedID(ele, beg, end) { + try { + if (beg===undefined || end===undefined) + return ele.id().slice(2); + return ele.id().slice(beg,end); + } catch (TypeError) { + return ''; + } +} function setUndos(undoManager) { var btnUndo = document.getElementById("btnUndo"); @@ -67,7 +76,7 @@ function drawArcs(evt) { var actNode = cy.$(".activated"); this.addClass("activated"); - + // if there is an activated node already if (actNode.length == 1) { writeArc(actNode, this); @@ -92,7 +101,7 @@ function writeArc(sourceNode, destNode) { var tokens = sent.tokens; // console.log(idx + ' ' + tokens); var thisToken = tokens[idx]; - // console.log('writeArc ' + destIndex + ' ' + thisToken['upostag']); + // console.log('writeArc ' + destIndex + ' ' + thisToken['upostag']); var sentAndPrev = changeConlluAttr(sent, indices, "head", sourceIndex); // If the target POS tag is PUNCT set the deprel to @punct [99%] @@ -138,7 +147,7 @@ function removeArc(destNodes) { // support for multiple arcs $.each(destNodes, function(i, node) { - var destIndex = node.id().slice(2); + var destIndex = getSlicedID(node); var indices = findConlluId(node); var sentAndPrev = changeConlluAttr(sent, indices, "head", undefined); sent = sentAndPrev[0]; @@ -152,7 +161,7 @@ function removeArc(destNodes) { undo: function(){ var sent = buildSent(); $.each(destNodes, function(i, node) { - var destIndex = node.id().slice(2); + var destIndex = getSlicedID(node); var indices = findConlluId(node); var sentAndPrev = changeConlluAttr(sent, indices, "head", prevRelations.head); sent = sentAndPrev[0]; @@ -171,10 +180,10 @@ function removeArc(destNodes) { function selectArc() { - /* + /* Activated when an arc is selected. Adds classes showing what is selected. */ - + if(!ISEDITING) { // if the user clicked an activated node if (this.hasClass("selected")) { @@ -230,19 +239,19 @@ function keyDownClassifier(key) { // drawTree(); // } // }); - + if (key.which == ESC) { key.preventDefault(); drawTree(); }; - + var isEditFocused = $('#edit').is(':focus'); if(isEditFocused) { if (key.which == TAB) { key.preventDefault(); } } - + if (selArcs.length) { if (key.which == DEL_KEY || key.which == BACKSPACE) { removeArc(destNodes); @@ -297,13 +306,13 @@ function keyDownClassifier(key) { if(key.shiftKey) { // zoom in CURRENT_ZOOM += 0.1; } else { // fit to screen - CURRENT_ZOOM = cy.fit(); + CURRENT_ZOOM = cy.fit(); } cy.zoom(CURRENT_ZOOM); cy.center(); } else if((key.which == MINUS || key.which == 173) ) { // zoom out CURRENT_ZOOM = cy.zoom(); - //if(key.shiftKey) { + //if(key.shiftKey) { CURRENT_ZOOM -= 0.1; //} cy.zoom(CURRENT_ZOOM); @@ -325,7 +334,7 @@ function moveArc() { $.each(nodes, function(n, node){ node.removeEventListener("click", drawArcs); node.addEventListener("click", getArc); - }); + }); } @@ -334,7 +343,7 @@ function removeSup(st) { The function takes the cy-element of superoken that was selected, removes it and inserts its former subtokens. */ var sent = buildSent(); - var currentId = +st.id().slice(2); // the id of the supertoken to be removed + var currentId = getSlicedID(st); // the id of the supertoken to be removed var subTokens = sent.tokens[currentId].tokens; // getting its children sent.tokens.splice(currentId, 1); // removing the multiword token $.each(subTokens, function(n, tok) { // inserting the subtokens @@ -346,11 +355,11 @@ function removeSup(st) { function changeNode() { // console.log("changeNode() " + Object.entries(this) + " // " + this.id()); - + ISEDITING = true; this.addClass("input"); - var id = this.id().slice(0, 2); + var id = getSlicedID(this, 0, 2); var param = this.renderedBoundingBox(); param.color = this.style("background-color"); var nodeType; @@ -360,8 +369,8 @@ function changeNode() { } if (id == "np") {nodeType = "UPOS"}; - // for some reason, there are problems with label in deprels without this - if (this.data("label") == undefined) {this.data("label", "")}; + // for some reason, there are problems with label in deprels without this + if (this.data("label") === undefined) {this.data("label", "")}; // to get rid of the magic direction arrows var res = this.data("label").replace(/[⊳⊲]/, ''); @@ -379,18 +388,17 @@ function changeNode() { $(".activated#mute").css("width", $(window).width()-10); } - // TODO: rank the labels + make the style better + // TODO: rank the labels + make the style better var availableLabels = []; if(nodeType == "UPOS") { - availableLabels = U_POS; - } else if(nodeType == "DEPREL") { + availableLabels = U_POS; + } else if(nodeType == "DEPREL") { availableLabels = U_DEPRELS; } // console.log('availableLabels:', availableLabels); - - // autocomplete - $('#edit').selfcomplete({lookup: availableLabels, + // autocomplete + $('#edit').selfcomplete({lookup: availableLabels, tabDisabled: false, autoSelectFirst:true, lookupLimit:5 @@ -431,7 +439,7 @@ function changeEdgeParam(param) { function find2change() { /* Selects a cy element to be changed, returns its index. */ var active = cy.$(".input"); - var Id = active.id().slice(2) - 1; + var Id = getSlicedID(active); // -1; return Id; } @@ -455,15 +463,15 @@ function writeDeprel(deprelInp, indices) { // TODO: DRY /* Writes changes to deprel label. */ // getting indices - if (indices == undefined) { + if (indices === undefined) { var active = cy.$(".input"); - var Id = active.id().slice(2); + var Id = getSlicedID(active); var wfNode = cy.$("#nf" + Id); var indices = findConlluId(wfNode); - } + } var sent = buildSent(); - + var outerIndex = indices[1]; var cur = parseInt(sent.tokens[outerIndex].id); var head = parseInt(sent.tokens[outerIndex].head); @@ -494,9 +502,9 @@ function writePOS(posInp, indices) { /* Writes changes to POS label. */ // getting indices - if (indices == undefined) { + if (indices === undefined) { var active = cy.$(".input"); - var Id = active.id().slice(2); + var Id = getSlicedID(active); var wfNode = cy.$("#nf" + Id); var indices = findConlluId(wfNode); } @@ -527,7 +535,7 @@ function changeConlluAttr(sent, indices, attrName, newVal) { var isSubtoken = indices[0]; var outerIndex = indices[1]; var innerIndex = indices[2]; - + //if(attrName == "deprel") { // newVal = newVal.replace(/[⊲⊳]/g, ''); //} @@ -587,7 +595,7 @@ function findConlluId(wfNode) { // TODO: refactor the arcitecture. } } } else { - var tokNumber = +wfNode.id().slice(2); + var tokNumber = getSlicedID(wfNode); var sent = buildSent(); for (var i = 0; i < sent.tokens.length; ++i) { if (sent.tokens[i].id == tokNumber) { @@ -614,7 +622,7 @@ function thereIsSupertoken(sent) { $.each(sent.tokens, function(n, tok) { if (tok instanceof conllu.MultiwordToken) { supTokFound = true; - } + } }) return supTokFound; } @@ -699,7 +707,7 @@ function mergeNodes(toMerge, side, how) { drawTree(); return; } - + var nodeId = indices[1]; var otherId = (side == "right") ? nodeId + 1 : nodeId - 1; var sent = buildSent(); @@ -734,7 +742,7 @@ function buildSent() { var currentFormat = detectFormat(currentSent); if (currentFormat == "CG3") { currentSent = CG2conllu(currentSent); - if (currentSent == undefined) { + if (currentSent === undefined) { drawTree(); return; } @@ -758,7 +766,7 @@ function redrawTree(sent) { $("#indata").val(changedSent); updateTable(); - drawTree(); + drawTree(); cy.zoom(CURRENT_ZOOM); } @@ -774,7 +782,7 @@ function writeSent(makeChanges) { // redraw tree $("#indata").val(sent.serial); - drawTree(); + drawTree(); } @@ -786,7 +794,7 @@ function viewAsPlain() { // TODO: DRY? text = conllu2plainSent(text); } else if (currentFormat == "CG3") { text = CG2conllu(text); - if (text == undefined) { + if (text === undefined) { cantConvertCG(); // show the error message return; } else { @@ -803,7 +811,7 @@ function viewAsConllu() { if (currentFormat == "CG3") { curSent = CG2conllu(curSent); - if (curSent == undefined) { + if (curSent === undefined) { cantConvertCG(); return; } @@ -945,14 +953,14 @@ $(document).ready(function(){ // $("#treebankSize").text(CONTENTS.length); // TODO: Report the current loaded treebank size to user $(e.target).find('.modal-body').load('help.html'); }); - + $('#exportModal').on('shown.bs.modal', function(e) { // $("#treebankSize").text(CONTENTS.length); // TODO: Report the current loaded treebank size to user $(e.target).find('.modal-body').load('export.html', function() { - exportPNG(); + exportPNG(); }); }); - + $('#exportModal').on('hidden.bs.modal', function (e) { png_exported = false; latex_exported = false;