diff --git a/_version.py b/_version.py index 1589024..2833b4c 100755 --- a/_version.py +++ b/_version.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -__version__ = "3.0.3" +__version__ = "4.0.0" __author__ = "Amir Zeldes, Luke Gessler" __copyright__ = "Copyright 2015-2021, Amir Zeldes" __license__ = "MIT License" diff --git a/admin.py b/admin.py index b64f18d..d6d223d 100644 --- a/admin.py +++ b/admin.py @@ -136,6 +136,7 @@ def is_email(email): + @@ -719,6 +720,31 @@ def is_email(email): cpout += '''

''' cpout += '''''' + # spans/multinucs + cpout += '''

Allow secondary edges

+

Allow adding tree-breaking secondary edges using ctrl+drag.

''' + + try: + secedge_state = get_setting("use_secedges") + except IndexError: + secedge_state="False" + + if "switch_secedges" in theform: + if int(get_schema()) < 7: + update_schema() + if theform["switch_secedges"] == "switch_secedges": + if secedge_state == "True": + secedge_state = "False" + else: + secedge_state = "True" + save_setting("use_secedges",secedge_state) + if secedge_state == "True": + opposite_secedge = "Disable" + else: + opposite_secedge = "Enable" + cpout += '''
''' + + cpout += '''

Update schema

Update the schema without losing data between major schema upgrades.

''' diff --git a/css/rst.css b/css/rst.css index fcf1566..f99c39f 100644 --- a/css/rst.css +++ b/css/rst.css @@ -184,19 +184,44 @@ div.shield-of-justice { } .edu--clickable .tok:not(.tok--selected):hover { - background: rgba(255,255,0,0.3); + background-color: rgba(255,255,0,0.6); } .tok--highlighted { - background: rgba(255,255,0,0.3); + background-color: rgba(255,255,0,0.8) !important; +} +.tok--highlighted.signal-color { + background-color: rgba(var(--rgb), 0.8) !important; } .tok--highlighted-by-button { - background: rgba(255,255,0,0.2); -} + --rgb: 255,255,0; + background-color: rgba(var(--rgb),0.2); +} + +.tok--highlighted-by-button.signal-type-1{--rgb: 255,0,0;background-color: rgba(var(--rgb),0.2);} /*red*/ +.tok--highlighted-by-button.signal-type-2{--rgb: 247,231,51;background-color: rgba(var(--rgb),0.2);} /*yellow*/ +.tok--highlighted-by-button.signal-type-3{--rgb: 251,183,52;background-color: rgba(var(--rgb),0.2);} /*orange*/ +.tok--highlighted-by-button.signal-type-4{--rgb: 128,128,0;background-color: rgba(var(--rgb),0.2);} /*ochre*/ +.tok--highlighted-by-button.signal-type-5{--rgb: 255,0,255;background-color: rgba(var(--rgb),0.2);} /*pink*/ +.tok--highlighted-by-button.signal-type-6{--rgb: 0,0,255;background-color: rgba(var(--rgb),0.2);} /*blue*/ +.tok--highlighted-by-button.signal-type-7{--rgb: 4,93,161;background-color: rgba(var(--rgb),0.2);} /*glas*/ +.tok--highlighted-by-button.signal-type-8{--rgb: 0,255,0;background-color: rgba(var(--rgb),0.2);} /*green*/ +.tok--highlighted-by-button.signal-type-9{--rgb: 0,255,255;background-color: rgba(var(--rgb),0.2);} /*cyan*/ +.tok--highlighted-by-button.signal-type-10{--rgb: 105,3,117;background-color: rgba(var(--rgb),0.2);}/*violet*/ +.tok--highlighted-by-button.signal-type-11{--rgb: 15,8,75;background-color: rgba(var(--rgb),0.2);} /* gray*/ +.tok--highlighted-by-button.signal-type-12{--rgb: 199,214,109;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-13{--rgb: 131,10,72;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-14{--rgb: 174,132,126;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-15{--rgb: 0,255,0;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-16{--rgb: 255,255,0;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-17{--rgb: 0,255,255;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-18{--rgb: 255,0,255;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-19{--rgb: 255,0,255;background-color: rgba(var(--rgb),0.2);} +.tok--highlighted-by-button.signal-type-20{--rgb: 255,0,255;background-color: rgba(var(--rgb),0.2);} .edu--clickable .tok--selected { - background: rgba(255,255,0,0.7); + background-color: rgba(255,255,0,0.7); } @@ -341,7 +366,7 @@ select.rst_rel { .seg_end:before{content:"\a0\a0"} .seg_end:hover:before{content:"x"} -.tab{position: absolute;left: 0px; top:86px; background-color:white;height: 700px; width: 500px;padding-left:5px} +.tab{position: absolute;left: 0px; top:86px; background-color:white;height: 800px; width: 500px;padding-left:5px} .tab_btn{width:82px;top:85px;position:absolute;z-index:2;border-width:1px;border-color: black black black black; border-style: solid; text-align:center; border-top-left-radius: 6px; border-top-right-radius: 6px; cursor: pointer; background-color: buttonface; border-bottom-color: black} .tab_btn:after{content: "\a0"; width:8px; border-bottom-style: solid; border-bottom-width:1px;border-bottom-color: black; left: 82px;position:absolute;top:0px} button:hover:enabled, .nav_button:hover:enabled{ color: red; background: #f5f5f5; } @@ -542,6 +567,33 @@ input:checked + .slider:before { height: 80%; } -.ui-dialog { z-index: 1000 !important ;} +.ui-dialog { z-index: 10000 !important ;} + +#quick_container{padding-top:5px} + +.custom-menu { + display: none; + z-index: 10000; + position: absolute; + overflow: hidden; + border: 1px solid #CCC; + white-space: nowrap; + font-family: sans-serif; + background: #FFF; + color: #333; + border-radius: 5px; + padding: 0; +} + +/* Each of the items in the list */ +.custom-menu li { + padding: 8px 12px; + cursor: pointer; + list-style-type: none; + transition: all .3s ease; + user-select: none; +} -#quick_container{padding-top:5px} \ No newline at end of file +.custom-menu li:hover { + background-color: #DEF; +} \ No newline at end of file diff --git a/get_structure.py b/get_structure.py index 576e5d0..b1a67d6 100644 --- a/get_structure.py +++ b/get_structure.py @@ -23,7 +23,8 @@ def get_structure_main(**kwargs): current_project = "quick" rels = {} - nodes, signals = read_rst(rs3,rels,as_string=True) + nodes, signals, signal_type_dict = read_rst(rs3,rels,as_string=True) + secedges = [] for nid in nodes: node = nodes[nid] node.relkind = "rst" @@ -31,8 +32,12 @@ def get_structure_main(**kwargs): node.relkind = "multinuc" elif node.relname == "span": node.relkind = "span" + if node.secedges != "": + secedges += node.secedges.split(";") + secedge_data = ";".join(secedges) rels = sorted([(k,v) for k,v in iteritems(rels)]) - output = build_canvas(current_doc, current_project, rels, nodes, signal_data=signals, show_signals=False, validations="validate_empty;validate_flat;validate_mononuc") + output = build_canvas(current_doc, current_project, rels, nodes, secedge_data=secedge_data, signal_data=signals, + validations="validate_empty;validate_flat;validate_mononuc", signal_type_dict=signal_type_dict) return output def get_structure_main_server(): diff --git a/modules/formats/db2rst.py b/modules/formats/db2rst.py index f5dd142..9242b66 100644 --- a/modules/formats/db2rst.py +++ b/modules/formats/db2rst.py @@ -1,7 +1,54 @@ -import re +import re, sys -def db2rst(rels, nodes, signals): +def db2rst(rels, nodes, secedges, signals, signal_types, doc, project, user): + def sequential_ids(rst_xml): + # Ensure no gaps in node IDs and corresponding adjustments to signals and secedges. + # Assume input xml IDs are already sorted, but with possible gaps + output = [] + temp = [] + current_id = 1 + id_map = {} + for line in rst_xml.split("\n"): + if ' id="' in line: + xml_id = re.search(r' id="([^"]+)"', line).group(1) + if ('", "\t
", @@ -13,6 +60,15 @@ def db2rst(rels, nodes, signals): rst_out.append('\t\t\t') rst_out.append("\t\t") + + if len(signal_types) > 0: + rst_out.append("\t\t") + for major_type in signal_types: + minor_types = signal_types[major_type] + minor_types = ";".join(sorted(minor_types)) + rst_out.append('\t\t\t') + rst_out.append("\t\t") + rst_out.append("\t
") rst_out.append("\t") @@ -53,6 +109,18 @@ def db2rst(rels, nodes, signals): rst_out.append('\t\t') + if len(secedges) > 0: + rst_out.append("\t\t") + for s in secedges.split(";"): + if ":" not in s: + continue + src_trg, relname = s.split(":",1) + src, trg = src_trg.split("-") + if relname.endswith("_r") or relname.endswith("_m"): + relname = relname[:-2] + rst_out.append('\t\t\t') + rst_out.append("\t\t") + if len(signals) > 0: rst_out.append("\t\t") for signal in signals: @@ -61,4 +129,7 @@ def db2rst(rels, nodes, signals): rst_out.append("\t\t") rst_out.append("\t") rst_out.append("") - return '\n'.join(rst_out) + '\n' + rst_out = '\n'.join(rst_out) + '\n' + rst_out = sequential_ids(rst_out) + + return rst_out diff --git a/modules/logintools/login.py b/modules/logintools/login.py index d0b97a8..662a0d4 100644 --- a/modules/logintools/login.py +++ b/modules/logintools/login.py @@ -97,7 +97,7 @@ # login is the function most frequently imported # and called directly by external scripts -def login(theform, userdir, thisscript=None, action=None): +def login(theform, userdir, thisscript=None, action=None, print_cookie=True): """From the form decide which function to call.""" # FIXME: this function got a bit more complicated than intended # it also handles checking logins when editaccount or admin @@ -185,8 +185,9 @@ def login(theform, userdir, thisscript=None, action=None): else: displaylogin(userdir, thisscript, action) # we haven't understood - just display the login screen XXXX error message instead ? - - print(newcookie) # XXXX ought to be a way of returning the cookie object instead... + + if print_cookie: + print(newcookie) # XXXX ought to be a way of returning the cookie object instead... if action == 'EMPTY_VAL_MJF': # the programmer ought never to 'see' this value, although it might get passed to his script by the login code a few times.... action = None userconfig['lastused'] = str(time()) diff --git a/modules/rstweb_classes.py b/modules/rstweb_classes.py index 1a51589..584e893 100644 --- a/modules/rstweb_classes.py +++ b/modules/rstweb_classes.py @@ -7,7 +7,7 @@ """ class NODE: - def __init__(self, id, left, right, parent, depth, kind, text, relname, relkind): + def __init__(self, id, left, right, parent, depth, kind, text, relname, relkind, secedges): """Basic class to hold all nodes (EDU, span and multinuc) in structure.py and while importing""" @@ -21,6 +21,7 @@ def __init__(self, id, left, right, parent, depth, kind, text, relname, relkind) self.relname = relname self.relkind = relkind #rst (a.k.a. satellite), multinuc or span relation self.sortdepth = depth + self.secedges = secedges class SEGMENT: diff --git a/modules/rstweb_reader.py b/modules/rstweb_reader.py index 013bc7e..bf64db9 100644 --- a/modules/rstweb_reader.py +++ b/modules/rstweb_reader.py @@ -23,11 +23,13 @@ def read_rst(filename, rel_hash, do_tokenize=False, as_string=False): else: f = codecs.open(filename, "r", "utf-8") in_rs3 = f.read() + # Remove processing instruction + in_rs3 = re.sub(r'<\?xml[^<>]*?\?>','',in_rs3) try: xmldoc = minidom.parseString(codecs.encode(in_rs3, "utf-8")) except ExpatError: message = "Invalid .rs3 file" - return message + return message, None nodes = [] ordered_id = {} @@ -184,10 +186,19 @@ def read_rst(filename, rel_hash, do_tokenize=False, as_string=False): signals = [] for signal in item_list: source = signal.attributes["source"].value - # This will crash if signal source refers to a non-existing node: - source = ordered_id[source] - if source not in all_node_ids: - raise IOError("Invalid source node ID for signal: " + str(source) + " (from XML file source="+signal.attributes["source"].value+"(\n") + if "-" in source: # Secedge signal + src, trg = source.split("-") + # We assume .rs4 format files are properly ordered, so directly look up IDs from secedge + if int(src) not in all_node_ids or int(trg) not in all_node_ids: + raise IOError("Invalid secedge ID for signal: " + str(source) + " (from XML file source="+signal.attributes["source"].value+")\n") + src = str(ordered_id[src]) + trg = str(ordered_id[trg]) + source = src + "-" + trg + else: + # This will crash if signal source refers to a non-existing node: + source = ordered_id[source] + if source not in all_node_ids: + raise IOError("Invalid source node ID for signal: " + str(source) + " (from XML file source="+signal.attributes["source"].value+")\n") type = signal.attributes["type"].value subtype = signal.attributes["subtype"].value tokens = signal.attributes["tokens"].value @@ -199,15 +210,42 @@ def read_rst(filename, rel_hash, do_tokenize=False, as_string=False): raise IOError("Signal refers to non-existent token: " + str(max_tok)) signals.append([str(source),type,subtype,tokens]) + # Collect signal type inventory declaration if available + item_list = xmldoc.getElementsByTagName("sig") + signal_type_dict = {} + for sig in item_list: + sigtype = sig.attributes["type"].value + subtypes = sig.attributes["subtypes"].value.split(";") + signal_type_dict[sigtype] = subtypes + if len(signal_type_dict) == 0: + signal_type_dict = None + + # Collect secondary edges if any are available + item_list = xmldoc.getElementsByTagName("secedge") + secedge_dict = collections.defaultdict(set) + for secedge in item_list: + source = secedge.attributes["source"].value + target = secedge.attributes["target"].value + relname = secedge.attributes["relname"].value + # This will crash if signal source or target refers to a non-existing node: + source = ordered_id[source] + target = ordered_id[target] + if source not in all_node_ids: + raise IOError("Invalid source node ID for secedge: " + str(source) + " (from XML file source="+secedge.attributes["source"].value+"(\n") + if target not in all_node_ids: + raise IOError("Invalid target node ID for secedge: " + str(target) + " (from XML file source="+secedge.attributes["target"].value+"(\n") + secedge_dict[str(source)].add(str(source) + "-" + str(target) + ":" + relname + "_r") + elements = {} for row in nodes: - elements[row[0]] = NODE(row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],"") + secedges = ";".join(secedge_dict[row[0]]) if row[0] in secedge_dict else "" + elements[row[0]] = NODE(row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],"",secedges) for element in elements: if elements[element].kind == "edu": get_left_right(element, elements,0,0,rel_hash) - return elements, signals + return elements, signals, signal_type_dict def read_text(filename,rel_hash,do_tokenize=False): @@ -238,7 +276,7 @@ def read_text(filename,rel_hash,do_tokenize=False): contents = tokenize(contents) contents = " ".join(contents) - nodes[str(id_counter)] = NODE(str(id_counter),id_counter,id_counter,"0",0,"edu",contents,list(rels.keys())[0],list(rels.values())[0]) + nodes[str(id_counter)] = NODE(str(id_counter),id_counter,id_counter,"0",0,"edu",contents,list(rels.keys())[0],list(rels.values())[0],"") return nodes diff --git a/modules/rstweb_sql.py b/modules/rstweb_sql.py index 965ff2a..80c5b71 100644 --- a/modules/rstweb_sql.py +++ b/modules/rstweb_sql.py @@ -14,6 +14,7 @@ from modules.formats import db2rst, rst2dis, rst2dep from modules.formats.dep2rst import rsd2rs3 from modules.rstweb_reader import * +from collections import defaultdict try: basestring @@ -45,7 +46,7 @@ def setup_db(): # Create tables cur.execute('''CREATE TABLE IF NOT EXISTS rst_nodes - (id text, left real, right real, parent text, depth real, kind text, contents text, relname text, doc text, project text, user text, UNIQUE (id, doc, project, user) ON CONFLICT REPLACE)''') + (id text, left real, right real, parent text, depth real, kind text, contents text, relname text, doc text, project text, user text, secedges text, UNIQUE (id, doc, project, user) ON CONFLICT REPLACE)''') cur.execute('''CREATE TABLE IF NOT EXISTS rst_signals (source text, type text, subtype text, tokens text, doc text, project text, user text, UNIQUE (source, type, subtype, tokens, doc, project, user) ON CONFLICT REPLACE)''') cur.execute('''CREATE TABLE IF NOT EXISTS rst_signal_types @@ -76,7 +77,7 @@ def update_schema(): # Create tables cur.execute('''CREATE TABLE IF NOT EXISTS rst_nodes - (id text, left real, right real, parent text, depth real, kind text, contents text, relname text, doc text, project text, user text, UNIQUE (id, doc, project, user) ON CONFLICT REPLACE)''') + (id text, left real, right real, parent text, depth real, kind text, contents text, relname text, doc text, project text, user text, secedges text, UNIQUE (id, doc, project, user) ON CONFLICT REPLACE)''') cur.execute('''CREATE TABLE IF NOT EXISTS rst_relations (relname text, reltype text, doc text, project text, UNIQUE (relname, reltype, doc, project) ON CONFLICT REPLACE)''') cur.execute('''CREATE TABLE IF NOT EXISTS rst_signals @@ -107,11 +108,13 @@ def update_schema(): cur.execute('ALTER TABLE projects ADD COLUMN validations text') if schema < 6: initialize_signal_types_on_existing_docs(cur) + if schema < 7: + if not generic_query("SELECT * from pragma_table_info('rst_nodes') where name='secedges';", ()): + cur.execute('ALTER TABLE rst_nodes ADD COLUMN secedges text') conn.commit() conn.close() - initialize_settings() @@ -148,7 +151,7 @@ def initialize_settings(): save_setting("signals_file", "default.json") save_setting("use_span_buttons", "True") save_setting("use_multinuc_buttons", "True") - set_schema('6') + set_schema('7') def check_refresh(user, timestamp): @@ -213,6 +216,7 @@ def read_signals_file(): except IOError: return {} + def import_document(filename, project, user, do_tokenize=False): dbpath = os.path.dirname(os.path.realpath(__file__)) + os.sep +".."+os.sep+"rstweb.db" conn = sqlite3.connect(dbpath) @@ -228,7 +232,7 @@ def import_document(filename, project, user, do_tokenize=False): if schema < 6: # Schemas below 6 do not support importing signals update_schema() - rst_nodes, rst_signals = read_rst(filename, rel_hash, do_tokenize=do_tokenize) + rst_nodes, rst_signals, signal_types = read_rst(filename, rel_hash, do_tokenize=do_tokenize) if isinstance(rst_nodes,basestring): return rst_nodes @@ -237,14 +241,15 @@ def import_document(filename, project, user, do_tokenize=False): for key in rst_nodes: node = rst_nodes[key] - cur.execute("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,user)) #user's instance - cur.execute("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,"_orig")) #backup instance + cur.execute("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,user,node.secedges)) #user's instance + cur.execute("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,"_orig",node.secedges)) #backup instance for signal in rst_signals: cur.execute("INSERT INTO rst_signals VALUES(?,?,?,?,?,?,?)", (signal[0],signal[1],signal[2],signal[3],doc,project,user)) #user's instance cur.execute("INSERT INTO rst_signals VALUES(?,?,?,?,?,?,?)", (signal[0],signal[1],signal[2],signal[3],doc,project,"_orig")) #backup instance - signal_types = read_signals_file() + if signal_types is None: + signal_types = read_signals_file() for majtype, subtypes in signal_types.items(): for subtype in subtypes: cur.execute("INSERT INTO rst_signal_types VALUES(?,?,?,?)", @@ -275,8 +280,8 @@ def import_plaintext(filename, project, user, rel_hash, do_tokenize=False): for key in rst_nodes: node = rst_nodes[key] - generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,user)) #user's instance - generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,"_orig")) #backup instance + generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,user,'')) #user's instance + generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (node.id,node.left,node.right,node.parent,node.depth,node.kind,node.text,node.relname,doc,project,"_orig",'')) #backup instance for key in rel_hash: rel_name = key @@ -301,7 +306,7 @@ def get_rst_doc(doc,project,user): with conn: cur = conn.cursor() - cur.execute("SELECT id, left, right, parent, depth, kind, contents, relname, doc, project, user FROM rst_nodes WHERE doc=? and project=? and user=? ORDER BY CAST(id AS int)", (doc,project,user)) + cur.execute("SELECT id, left, right, parent, depth, kind, contents, relname, secedges, doc, project, user FROM rst_nodes WHERE doc=? and project=? and user=? ORDER BY CAST(id AS int)", (doc,project,user)) rows = cur.fetchall() return rows @@ -339,7 +344,7 @@ def get_all_docs_by_project(): def add_node(node_id,left,right,parent,rel_name,text,node_kind,doc,project,user): - generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)", (node_id,left,right,parent,0,node_kind,text,rel_name,doc,project,user)) + generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (node_id,left,right,parent,0,node_kind,text,rel_name,doc,project,user,"")) def get_all_projects(): @@ -351,7 +356,10 @@ def create_project(project_name): def update_parent(node_id,new_parent_id,doc,project,user): - prev_parent = get_parent(node_id,doc,project,user) + try: + prev_parent = get_parent(node_id,doc,project,user) + except: + return generic_query("UPDATE rst_nodes SET parent=? WHERE id=? and doc=? and project=? and user=?",(new_parent_id,node_id,doc,project,user)) if new_parent_id == "0": update_rel(node_id,get_def_rel("rst",doc,project),doc,project,user) @@ -475,6 +483,19 @@ def delete_node(node_id,doc,project,user): update_parent(child[0],"0",doc,project,user) generic_query("DELETE FROM rst_nodes WHERE id=? and doc=? and project=? and user=?",(node_id,doc,project,user)) generic_query("DELETE FROM rst_signals WHERE source=? and doc=? and project=? and user=?",(node_id,doc,project,user)) + generic_query("DELETE from rst_signals WHERE source like ? and doc=? and project=? and user=?",("%-" + node_id, doc, project, user)) + generic_query("DELETE from rst_signals WHERE source like ? and doc=? and project=? and user=?",(node_id + "-%", doc, project, user)) + target_secedges = generic_query("SELECT secedges from rst_nodes WHERE secedges like ? and doc=? and project=? and user=?",("%-"+node_id+":", doc, project, user)) + for t in target_secedges: + old_secedges = t[0] + secedges = old_secedges.split(";") + new_secedges = [] + for s in secedges: + if "-" + node_id + ":" in s: + continue + new_secedges.append(s) + new_secedges = ";".join(new_secedges) + generic_query("UPDATE rst_nodes set secedges=? WHERE secedges=? and doc=? and project=? and user=?",(new_secedges,old_secedges,doc,project,user)) if not parent=="0": if not count_children(parent,doc,project,user)>0: delete_node(parent,doc,project,user) @@ -496,8 +517,8 @@ def insert_parent(node_id,new_rel,node_kind,doc,project,user): def reset_rst_doc(doc,project,user): generic_query("DELETE FROM rst_nodes WHERE doc=? and project=? and user=?",(doc,project,user)) - generic_query("""INSERT INTO rst_nodes (id, left, right, parent, depth, kind, contents, relname, doc, project, user) - SELECT id, left, right, parent, depth, kind, contents, relname, doc, project, '""" + user + "' FROM rst_nodes WHERE doc=? and project=? and user='_orig'""",(doc,project)) + generic_query("""INSERT INTO rst_nodes (id, left, right, parent, depth, kind, contents, relname, doc, project, user, secedges) + SELECT id, left, right, parent, depth, kind, contents, relname, doc, project, '""" + user + "', secedges FROM rst_nodes WHERE doc=? and project=? and user='_orig'""",(doc,project)) generic_query("DELETE FROM rst_signals WHERE doc=? and project=? and user=?",(doc,project,user)) generic_query("""INSERT INTO rst_signals (source, type, subtype, tokens, doc, project, user) SELECT source, type, subtype, tokens, doc, project, '""" + user + "' FROM rst_signals WHERE doc=? and project=? and user='_orig'""",(doc,project)) @@ -516,7 +537,7 @@ def get_max_right(doc,project,user): def get_users(doc,project): - return generic_query("SELECT user from rst_nodes WHERE doc=? and project=? and not user='_orig'",(doc,project)) + return generic_query("SELECT DISTINCT user from rst_nodes WHERE doc=? and project=? and not user='_orig'",(doc,project)) def generic_query(sql,params): @@ -574,14 +595,20 @@ def export_document(doc, project, exportdir, output_format='rs3'): def get_export_string(doc, project, user): rels = get_rst_rels(doc, project) nodes = get_rst_doc(doc, project, user) + secedges = get_secedges(doc, project, user) signals = get_signals(doc, project, user) - return db2rst(rels, nodes, signals) + signal_rows = get_signal_types(doc, project) + signal_types = defaultdict(list) + for row in signal_rows: + signal_types[row[0]].append(row[1]) + return db2rst(rels, nodes, secedges, signals, signal_types, doc, project, user) def delete_document(doc,project): generic_query("DELETE FROM rst_nodes WHERE doc=? and project=?",(doc,project)) generic_query("DELETE FROM rst_relations WHERE doc=? and project=?",(doc,project)) generic_query("DELETE FROM rst_signals WHERE doc=? and project=?",(doc,project)) + generic_query("DELETE FROM rst_signal_types WHERE doc=? and project=?",(doc,project)) generic_query("DELETE FROM docs WHERE doc=? and project=?",(doc,project)) @@ -589,6 +616,7 @@ def delete_project(project): generic_query("DELETE FROM rst_nodes WHERE project=?",(project,)) generic_query("DELETE FROM rst_relations WHERE project=?",(project,)) generic_query("DELETE FROM rst_signals WHERE project=?",(project,)) + generic_query("DELETE FROM rst_signal_types WHERE project=?",(project,)) generic_query("DELETE FROM docs WHERE project=?",(project,)) generic_query("DELETE FROM projects WHERE project=?",(project,)) @@ -601,6 +629,7 @@ def insert_seg(token_num, doc, project, user): update_seg_contents(seg_to_split,parts[0].strip(),doc,project,user) add_seg(str(int(seg_to_split)+1),parts[1].strip(),doc,project,user) + def get_tok_map(doc,project,user): rows = generic_query("SELECT id, contents FROM rst_nodes WHERE kind='edu' and doc=? and project=? and user=? ORDER BY CAST(id AS int)",(doc,project,user)) all_tokens = {} @@ -616,26 +645,86 @@ def get_tok_map(doc,project,user): def push_up(push_above_this_seg,doc,project,user): - ids_above_push = generic_query("SELECT id from rst_nodes WHERE CAST(id as int) > ? and doc=? and project=? and user=? ORDER BY CAST(id as int) DESC",(push_above_this_seg,doc,project,user)) - for row in ids_above_push: #Do this row-wise to avoid sqlite unique constraint behavior - id_to_increment = row[0] - generic_query("UPDATE rst_nodes set id = CAST((CAST(id as int) + 1) as text) WHERE id=? and doc=? and project=? and user=?",(id_to_increment,doc,project,user)) - generic_query("UPDATE rst_signals set source = CAST((CAST(source as int) + 1) as text) WHERE source=? and doc=? and project=? and user=?",(id_to_increment,doc,project,user)) + all_rows = generic_query("SELECT id, secedges from rst_nodes WHERE doc=? and project=? and user=? ORDER BY CAST(id as int) DESC",(doc,project,user)) + for row in all_rows: # Do this row-wise to avoid sqlite unique constraint behavior + row_id = row[0] + secedges = row[1] + if secedges is not None: # Update secedges with source or target above push + if len(secedges) > 0: + updated_edges = [] + secedges = secedges.split(";") + for secedge in secedges: + src_trg, relname = secedge.split(":") + src, trg = src_trg.split("-") + if int(src) > push_above_this_seg: + src = str((int(src) + 1)) + if int(trg) > push_above_this_seg: + trg = str((int(trg) + 1)) + edge = src + "-" + trg + ":" + relname + updated_edges.append(edge) + updated_edges = ";".join(updated_edges) + if updated_edges != row[1]: + generic_query("UPDATE rst_nodes set secedges = ? WHERE id=? and doc=? and project=? and user=?", (updated_edges, row_id, doc, project, user)) + if int(row_id) > push_above_this_seg: + generic_query("UPDATE rst_nodes set id = CAST((CAST(id as int) + 1) as text) WHERE id=? and doc=? and project=? and user=?", (row_id, doc, project, user)) + generic_query("UPDATE rst_signals set source = CAST((CAST(source as int) + 1) as text) WHERE source=? and doc=? and project=? and user=?",(row_id, doc, project, user)) generic_query("UPDATE rst_nodes set parent = CAST((CAST(parent as int) + 1) as text) WHERE CAST(parent as int)>? and doc=? and project=? and user=?",(push_above_this_seg,doc,project,user)) generic_query("UPDATE rst_nodes set left = left + 1 WHERE left>? and doc=? and project=? and user=?",(push_above_this_seg,doc,project,user)) generic_query("UPDATE rst_nodes set right = right + 1 WHERE right>? and doc=? and project=? and user=?",(push_above_this_seg,doc,project,user)) + # Update secedge signals + secedge_signals = generic_query("SELECT DISTINCT source from rst_signals WHERE source like '%-%' and doc=? and project=? and user=?",(doc,project,user)) + for row in secedge_signals: + src, trg = row[0].split("-") + if int(src) > push_above_this_seg: + src = str((int(src) + 1)) + if int(trg) > push_above_this_seg: + trg = str((int(trg) + 1)) + src_trg = src + "-" + trg + if src_trg != row[0]: + generic_query("UPDATE rst_signals set source = ? WHERE source=? and doc=? and project=? and user=?",(src_trg, row[0], doc, project, user)) + def push_down(push_above_this_seg,doc,project,user): - ids_above_push = generic_query("SELECT id from rst_nodes WHERE CAST(id as int) > ? and doc=? and project=? and user=? ORDER BY CAST(id as int)",(push_above_this_seg,doc,project,user)) - for row in ids_above_push: #Do this row-wise to avoid sqlite unique constraint behavior - id_to_decrement = row[0] - generic_query("UPDATE rst_nodes set id = CAST((CAST(id as int) - 1) as text) WHERE id=? and doc=? and project=? and user=?",(id_to_decrement,doc,project,user)) - generic_query("UPDATE rst_signals set source = CAST((CAST(source as int) - 1) as text) WHERE source=? and doc=? and project=? and user=?",(id_to_decrement,doc,project,user)) + all_rows = generic_query("SELECT id, secedges from rst_nodes WHERE doc=? and project=? and user=? ORDER BY CAST(id as int)",(doc,project,user)) + for row in all_rows: # Do this row-wise to avoid sqlite unique constraint behavior + row_id = row[0] + secedges = row[1] + if secedges is not None: # Update secedges with source or target above push + if len(secedges) > 0: + updated_edges = [] + secedges = secedges.split(";") + for secedge in secedges: + src_trg, relname = secedge.split(":") + src, trg = src_trg.split("-") + if int(src) > push_above_this_seg: + src = str((int(src) - 1)) + if int(trg) > push_above_this_seg: + trg = str((int(trg) - 1)) + edge = src + "-" + trg + ":" + relname + updated_edges.append(edge) + updated_edges = ";".join(updated_edges) + if updated_edges != row[1]: + generic_query("UPDATE rst_nodes set secedges = ? WHERE id=? and doc=? and project=? and user=?", (updated_edges, row_id, doc, project, user)) + if int(row_id) > push_above_this_seg: + generic_query("UPDATE rst_nodes set id = CAST((CAST(id as int) - 1) as text) WHERE id=? and doc=? and project=? and user=?", (row_id, doc, project, user)) + generic_query("UPDATE rst_signals set source = CAST((CAST(source as int) - 1) as text) WHERE source=? and doc=? and project=? and user=?",(row_id, doc, project, user)) generic_query("UPDATE rst_nodes set parent = CAST((CAST(parent as int) - 1) as text) WHERE CAST(parent as int)>? and doc=? and project=? and user=?",(push_above_this_seg,doc,project,user)) generic_query("UPDATE rst_nodes set left = left - 1 WHERE left>? and doc=? and project=? and user=?",(push_above_this_seg,doc,project,user)) generic_query("UPDATE rst_nodes set right = right - 1 WHERE right>? and doc=? and project=? and user=?",(push_above_this_seg,doc,project,user)) + # Update secedge signals + secedge_signals = generic_query("SELECT DISTINCT source from rst_signals WHERE source like '%-%' and doc=? and project=? and user=?",(doc,project,user)) + for row in secedge_signals: + src, trg = row[0].split("-") + if int(src) > push_above_this_seg: + src = str((int(src) - 1)) + if int(trg) > push_above_this_seg: + trg = str((int(trg) - 1)) + src_trg = src + "-" + trg + if src_trg != row[0]: + generic_query("UPDATE rst_signals set source = ? WHERE source=? and doc=? and project=? and user=?",(src_trg, row[0], doc, project, user)) + def get_split_text(tok_num,doc,project,user): rows = generic_query("SELECT id, contents FROM rst_nodes WHERE kind='edu' and doc=? and project=? and user=? ORDER BY CAST(id AS int)",(doc,project,user)) @@ -676,7 +765,7 @@ def get_seg_contents(id,doc,project,user): def add_seg(id,contents,doc,project,user): - generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)", (id,id,id,"0","0","edu",contents,get_def_rel("rst",doc,project),doc,project,user)) + generic_query("INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (id,id,id,"0","0","edu",contents,get_def_rel("rst",doc,project),doc,project,user,"")) def merge_seg_forward(last_tok_num,doc,project,user): @@ -707,12 +796,13 @@ def copy_doc_to_user(doc, project, user): copy = [] for row in doc_to_copy: row += (user,) + row += ("",) # secedges field copy += (row,) dbpath = os.path.dirname(os.path.realpath(__file__)) + os.sep +".."+os.sep+"rstweb.db" conn = sqlite3.connect(dbpath) cur = conn.cursor() - cur.executemany('INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?)', copy) + cur.executemany('INSERT INTO rst_nodes VALUES(?,?,?,?,?,?,?,?,?,?,?,?)', copy) cur.execute("INSERT INTO docs VALUES (?,?,?)", (doc,project,user)) conn.commit() @@ -847,3 +937,43 @@ def get_signal_types_dict(doc, project): d[majtype].append(subtype) return d + + +def get_secedges(doc, project, user): + # Return all secedges as a ; separated string in the format: 1-2:elaboration_r;6-3:concession_r + schema = get_schema() + if schema < 7: + update_schema() + output = "" + secedges = generic_query("SELECT DISTINCT secedges FROM rst_nodes WHERE doc=? and project=? and user=?", (doc,project,user)) + if secedges is not None: + if len(secedges)>0: + secedges = [str(s[0]) for s in secedges] + output = ";".join(secedges).replace(";;",";") + if output.startswith(";"): + output = output[1:] + if output.endswith(";"): + output = output[:-1] + return output + + +def update_secedge(node_id,parent_id,new_rel,doc,project,user): + params = (node_id,doc,project,user) + old_secedges = generic_query("SELECT secedges FROM rst_nodes WHERE id=? and doc=? and project=? and user=?", params) + if len(old_secedges) == 0 or old_secedges[0][0] is None: + secedges_string = "" + else: + secedges_string = old_secedges[0][0] + secedges = secedges_string.split(";") + secedges = [s for s in secedges if not s.startswith(node_id + "-" + parent_id + ":") and ":" in s] + if new_rel != "-ERASE-": + secedges.append(node_id + "-" + parent_id + ":" + new_rel) + else: + generic_query("DELETE from rst_signals WHERE source like ? and doc=? and project=? and user=?", (node_id + "-" + parent_id, doc, project, user)) + secedges_string = ";".join(secedges) + params = (secedges_string,node_id,doc,project,user) + generic_query("UPDATE rst_nodes set secedges = ? WHERE id=? and doc=? and project=? and user=?", params) + +if __name__ == "__main__": + #get_rst_doc("GUM_academic_huh.rs4", "A nice demo for the workshop", "amir") + get_export_string("GUM_whow_basil.rs4", "rst++", "sp1184") \ No newline at end of file diff --git a/quick_export.py b/quick_export.py index 3b74bb3..27eeafc 100755 --- a/quick_export.py +++ b/quick_export.py @@ -12,7 +12,7 @@ import codecs import sys import cgi -import os +import os, re from modules.configobj import ConfigObj from modules.logintools import login from modules.rstweb_sql import get_export_string @@ -64,7 +64,7 @@ def quickexp_main_server(): theform = cgi.FieldStorage() scriptpath = os.path.dirname(os.path.realpath(__file__)) + os.sep userdir = scriptpath + "users" + os.sep - action, userconfig = login(theform, userdir, thisscript, action) + action, userconfig = login(theform, userdir, thisscript, action, print_cookie=False) user = userconfig["username"] admin = userconfig["admin"] kwargs={} @@ -72,6 +72,8 @@ def quickexp_main_server(): kwargs[key] = theform[key].value output = quickexp_main(user, admin, 'server', **kwargs) + output = re.sub(r'Set-Cookie[^\n]+','',output) # Remove any cookies + sys.stdout.buffer.flush() if sys.version_info[0] < 3: print(output) else: diff --git a/rstWeb_user_guide.pdf b/rstWeb_user_guide.pdf index b883dcc..d3e1226 100644 Binary files a/rstWeb_user_guide.pdf and b/rstWeb_user_guide.pdf differ diff --git a/rstweb.db b/rstweb.db index 9c44232..b8ecfba 100644 Binary files a/rstweb.db and b/rstweb.db differ diff --git a/script/admin.js b/script/admin.js index 24e9863..42657e1 100644 --- a/script/admin.js +++ b/script/admin.js @@ -254,6 +254,10 @@ function admin(action){ document.getElementById("switch_multinuc_buttons").value = "switch_multinuc_buttons"; document.getElementById("sel_tab").value = "database"; break; + case "switch_secedges": + document.getElementById("switch_secedges").value = "switch_secedges"; + document.getElementById("sel_tab").value = "database"; + break; case "update_schema": document.getElementById("update_schema").value = "update_schema"; document.getElementById("sel_tab").value = "database"; diff --git a/script/images/ui-bg_diagonals-thick_20_666666_40x40.png b/script/images/ui-bg_diagonals-thick_20_666666_40x40.png new file mode 100644 index 0000000..0f1e374 Binary files /dev/null and b/script/images/ui-bg_diagonals-thick_20_666666_40x40.png differ diff --git a/script/images/ui-bg_glass_100_f6f6f6_1x400.png b/script/images/ui-bg_glass_100_f6f6f6_1x400.png new file mode 100644 index 0000000..06e02a1 Binary files /dev/null and b/script/images/ui-bg_glass_100_f6f6f6_1x400.png differ diff --git a/script/images/ui-bg_glass_100_fdf5ce_1x400.png b/script/images/ui-bg_glass_100_fdf5ce_1x400.png new file mode 100644 index 0000000..4e26832 Binary files /dev/null and b/script/images/ui-bg_glass_100_fdf5ce_1x400.png differ diff --git a/script/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/script/images/ui-bg_gloss-wave_35_f6a828_500x100.png new file mode 100644 index 0000000..cc8146e Binary files /dev/null and b/script/images/ui-bg_gloss-wave_35_f6a828_500x100.png differ diff --git a/script/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/script/images/ui-bg_highlight-soft_100_eeeeee_1x100.png new file mode 100644 index 0000000..8afbdca Binary files /dev/null and b/script/images/ui-bg_highlight-soft_100_eeeeee_1x100.png differ diff --git a/script/images/ui-icons_222222_256x240.png b/script/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..c1cb117 Binary files /dev/null and b/script/images/ui-icons_222222_256x240.png differ diff --git a/script/images/ui-icons_ef8c08_256x240.png b/script/images/ui-icons_ef8c08_256x240.png new file mode 100644 index 0000000..036ee07 Binary files /dev/null and b/script/images/ui-icons_ef8c08_256x240.png differ diff --git a/script/quick.js b/script/quick.js index a74318b..bc97aae 100644 --- a/script/quick.js +++ b/script/quick.js @@ -42,23 +42,72 @@ function get_rs3(){ groups.push('\t\t'); } } - for (rel of reltypes.sort()){ - if (rel=="span" || rel=="none"){continue;} - relname_string = rel.replace(/_[rm]/,''); - if (rel.endsWith("_m")){ - reltype = "multinuc"; - } - else{ - reltype = "rst"; - } - rels.push('\t\t\t'); - } + for (rel of reltypes.sort()){ + if (rel=="span" || rel=="none"){continue;} + relname_string = rel.replace(/_[rm]/,''); + if (rel.endsWith("_m")){ + reltype = "multinuc"; + } + else{ + reltype = "rst"; + } + rels.push('\t\t\t'); + } rst_out = '\n\t
\n\t\t\n'; rst_out += rels.join("\n") + "\n"; - rst_out += "\t\t\n\t
\n\t\n"; + rst_out += "\t\t\n"; + + signal_types = window.rstWebSignalTypes; + if (Object.keys(signal_types).length > 0){ + rst_out += "\t\t\n"; + for (major_type in signal_types){ + minor_types = signal_types[major_type]; + minor_types = minor_types.join(";"); + rst_out += '\t\t\t\n'; + } + rst_out += "\t\t\n"; + } + + rst_out += "\t\n\t\n"; rst_out += edus.join("\n") + "\n"; rst_out += groups.join("\n") + "\n"; + + secedges = document.getElementById("secedges").value; + if (secedges.length > 0){ + rst_out += "\t\t\n" + for (s of secedges.split(";")){ + if (!s.includes(":")){continue;} + parts = s.split(":"); + src_trg = parts[0]; + relname = parts[1]; + parts = src_trg.split("-"); + src = parts[0]; + trg = parts[1]; + if (relname.endsWith("_r") || relname.endsWith("_m")){ + relname = relname.slice(0,-2); + } + rst_out += '\t\t\t\n'; + } + rst_out += "\t\t\n"; + } + + signals = window.rstWebSignals; + + if (Object.keys(signals).length > 0){ + rst_out += "\t\t\n"; + for (source in signals){ + for (signal of signals[source]){ + signal_type = signal["type"]; + signal_subtype = signal["subtype"]; + tokens = signal["tokens"]; + tokens = tokens.join(","); + rst_out += '\t\t\t\n'; + } + } + rst_out += "\t\t\n"; + } + rst_out += "\t\n
\n"; return rst_out; } @@ -91,6 +140,7 @@ $(document).ready(function() { data: rs3}, function(newData){ $("#quick_structure").html(newData); + $("#show-all-signals").click(); } ); } diff --git a/script/structure.js b/script/structure.js index ecc88a8..055d4e8 100644 --- a/script/structure.js +++ b/script/structure.js @@ -11,6 +11,14 @@ //TODO: get_max_node_id(nodes) can be implemented without reference to nodes, by parsing hidden data model +var sigtype_num = 1; +var signal_highlight_colors = { + //"dm" : "signal-red", + //"orphan": "signal-blue" +}; +window.ctrlPressed = {}; +ctrlPressed.value = false; + function act(action){ disable_buttons(); @@ -31,6 +39,7 @@ function act(action){ var action_params = action.split(":")[1]; var params = action_params && action_params.split(","); nodes = parse_data(); + secedges = parse_secedges(); document.getElementById("logging").value += action; if (action_type == 'mn' || action_type == 'sp'){ @@ -99,10 +108,23 @@ function act(action){ document.getElementById("g"+params[0]).style.display = "none"; recalculate_depth(parse_data()); } - else if (action_type=="sg") { - // do nothing--all client-side UI changes handled elsewhere - // TODO: support undo - } + else if (action_type=="sg") { + // do nothing--all client-side UI changes handled elsewhere + // TODO: support undo for signals + } + else if (action_type=="sc"){ // secedge + append_undo("xs:" + params[0].split("-")[0] + "," + params[0].split("-")[1]); + add_secedge("n"+params[0].split("-")[0],"n"+params[0].split("-")[1],params[1],nodes); + enable_buttons(); + } + else if (action_type=="xs"){ // remove secedge + src_trg = params[0].split("-"); + src_trg = "n"+src_trg[0] + "-" + "n" + src_trg[1]; + old_rel = secedges[src_trg]; + append_undo("sc:" + params[0] + "," + old_rel); + delete_secedge(params[0]); + enable_buttons(); + } // anim_catch replaces jquery promise to monitor animation queue // enable buttons once this final animation is complete @@ -115,7 +137,13 @@ function act(action){ } function crel(node_id,sel) { - act("rl:" + node_id.toString() + "," + sel); + let nid_string = node_id.toString(); + if (nid_string.includes("-")){ + act("sc:" + nid_string + "," + sel); + } else{ + act("rl:" + nid_string + "," + sel); + } + } function rst_node(id, parent, kind, left, relname, reltype){ @@ -691,9 +719,28 @@ function recalculate_depth(nodes){ jsPlumb.animate(element_id, {top: expected_top, left: expected_left}); } } + render_all_secedges(nodes); show_warnings(nodes); } +function render_all_secedges(nodes){ + secedges = parse_secedges(); + sec_by_src = secedges_by_source(secedges); + for (node_id in nodes){ + let nid = node_id.replace(/n/,''); + if (nid in sec_by_src){ + for (trg in sec_by_src[nid]){ + let eid = nid + "-" + trg; + if (!($("#unlink_sc" + eid).length)){ // edge has not been rendered, add it unlink_sc3-2 + let relname = secedges[eid]; + render_secedge(nid, trg, relname, nodes); + } + } + } + } + +} + function get_left_right(node_id, nodes, min_left, max_right){ if (nodes[node_id].parent != "n0" && node_id != "n0" && typeof nodes[nodes[node_id].parent] != 'undefined'){ parent = nodes[nodes[node_id].parent]; @@ -812,6 +859,18 @@ function detach_target(element_id){ jsPlumb.repaint(element_id); } +function detach_source_target(src,trg){ + source_elem_id = "g" + src; + if (nodes["n"+src].kind=="edu"){ + source_elem_id = "edu" + src; + } + target_elem_id = "g" + trg; + if (nodes["n"+trg].kind=="edu"){ + target_elem_id = "edu" + trg; + } + jsPlumb.select({source:source_elem_id, target: target_elem_id, scope: "secedge"}).detach(); +} + function insert_parent(node_id,new_rel,node_kind){ nodes = parse_data(); old_parent = nodes[node_id].parent; @@ -997,7 +1056,6 @@ function is_ancestor(new_parent_id,node_id){ // signals handling var open_signal_drawer; -$(document).ready(function(){ function raise_shield_of_justice () { var div = document.createElement("div"); @@ -1162,9 +1220,40 @@ $(document).ready(function(){ .removeClass("tok--selected"); } + function bind_tok_contextmenu(){ + $(".tok").unbind("contextmenu"); + $(".tok").contextmenu(function(event) { + if ($(".custom-menu").length > 0){ // context menu will only exist if signals are on + event.preventDefault(); + // Show contextmenu + $(".custom-menu").finish().show(100).css({ + top: event.pageY -90 + "px", + left: event.pageX + "px" + }); + let tokid = $(event.target).attr("id"); + $(".custom-menu").attr("data-action", tokid); + } + }); + $(document).bind("mousedown", function (e) { + + // If the clicked element is not the menu + if (!$(e.target).parents(".custom-menu").length > 0 && $($(e.target).parents(".custom-menu").context).attr("id") != $(".custom-menu").attr("data-action")) { + // Hide it + $(".custom-menu").hide(100); + } + }); + + $(".custom-menu li").unbind("click"); + $(".custom-menu li").click(function(){ + switch($(this).attr("data-action")) { + case "quick_delete_signals": quick_delete_signals($(".custom-menu").attr("data-action")); break; + } + }); + } + function update_signal_button(selDiv) { var id = selDiv.attr('id').slice(6); - var btn = selDiv.find("button.minibtn"); + var btn = selDiv.find("button.minibtn:not(.closer)"); var numSigs = window.rstWebSignals[id] && window.rstWebSignals[id].length; if (numSigs > 0) { @@ -1210,30 +1299,13 @@ $(document).ready(function(){ } } - function make_signal_action(signals) { - var s = "sg:"; - - Object.keys(signals).forEach(function(id) { - signals[id].forEach(function(signal) { - s += id + ","; - s += signal.type + ","; - s += signal.subtype + ","; - s += signal.tokens.join("-") + ":"; - }); - }); - - if (s.endsWith(":")) { - s = s.substring(0, s.length - 1); - } - return s; - } - function close_signal_drawer(should_save) { var selDiv = $(".seldiv--active"); lower_shield_of_justice(); enable_buttons(); remove_classes(); deselect_and_unbind_toks(); + bind_tok_contextmenu(); if (should_save) { if (JSON.stringify(window.rstWebSignals) !== signalsWhenOpened) { @@ -1297,7 +1369,7 @@ $(document).ready(function(){ // for when a " + select_my_rel("both",rel) + ""; + return $(s); +} + +$(document).keyup(function(evt) { + ctrlPressed.value = false; +}); + +function make_signal_action(signals) { + var s = "sg:"; + + Object.keys(signals).forEach(function(id) { + signals[id].forEach(function(signal) { + s += id + ","; + s += signal.type + ","; + s += signal.subtype + ","; + s += signal.tokens.join("-") + ":"; + }); + }); + + if (s.endsWith(":")) { + s = s.substring(0, s.length - 1); + } + return s; +} + + +function quick_delete_signals(tokid){ + tokid = parseInt(tokid.replace('tok','')); + sources_to_remove = []; + selDivs_to_update = []; + for (source_id in window.rstWebSignals){ + signals_to_remove = []; + for (signal_idx in window.rstWebSignals[source_id]){ + signal = window.rstWebSignals[source_id][signal_idx]; + if (signal.tokens.includes(tokid)){ + signals_to_remove.push(signal_idx); + selDivs_to_update.push(source_id); + } + } + for (signal_idx of signals_to_remove.slice().reverse()){ + window.rstWebSignals[source_id].splice(signal_idx,1); + } + if (window.rstWebSignals[source_id].length == 0){ + sources_to_remove.push(source_id); + } + } + for (source_id of sources_to_remove){ + delete window.rstWebSignals[source_id]; + } + for (source_id of selDivs_to_update){ + update_signal_button($("#seldiv"+source_id)); + } + act(make_signal_action(window.rstWebSignals)); + unhighlight_all_tokens(); + // reset token highlighting + attempt_to_bind_token_reveal_until_success(); + if (all_tokens_are_highlighted()) { + highlight_all_tokens(); + } + $(".custom-menu").hide(100); +} \ No newline at end of file diff --git a/signals/default.json b/signals/default.json index a51dcd8..35aaa70 100644 --- a/signals/default.json +++ b/signals/default.json @@ -1,52 +1,38 @@ -{"DM": ["DM"], - "Reference": ["Personal reference", - "Demonstrative reference", - "Comparative reference", - "Propositional reference"], - "Lexical": ["Indicative word", - "Alternate expression"], - "Semantic": ["Synonymy", - "Antonymy", - "Meronymy", - "Repetition", - "Indicative word pair", - "Lexical chain", - "General word"], - "Morphological": ["Tense"], - "Syntactic": ["Relative clause", - "Infinitival clause", - "Present participial clause", - "Past participial clause", - "Imperative clause", - "Interrupted matrix clause", - "Parallel syntactic construction", - "Reported speech", - "Subject auxiliary inversion", - "Nominal modifier", - "Adjectival modifier"], - "Graphical": ["Colon", - "Semicolon", - "Dash", - "Parentheses", - "Items in sequence"], - "Genre": ["Inverted pyramid scheme", - "Newspaper layout", - "Newspaper style attribution", - "Newspaper style definition"], - "Numerical": ["Same count"], - "Reference + syntactic": ["Personal reference + subject NP", - "Demonstrative reference + subject NP", - "Comparative reference + subject NP", - "Propositional reference + subject NP"], - "Semantic + syntactic": ["Repetition + subject NP", - "Lexical chain + subject NP", - "Synonymy + subject NP", - "Meronymy + subject NP", - "General word + subject NP"], - "Lexical + syntactic": ["Indicative word + present participial clause"], - "Syntactic + positional": ["Past participial clause + beginning", - "Present participial clause + beginning"], - "Syntactic + semantic": ["parallel syntactic construction + lexical chain"], - "Graphical + syntactic": ["Comma + present participial clause", - "Comma + past participial clause"], - "Unsure": ["Unsure"]} +{"dm": ["dm"], + "reference": ["personal_reference", + "demonstrative_reference", + "comparative_reference", + "propositional_reference"], + "lexical": ["indicative_word", + "indicative_phrase", + "alternate_expression"], + "semantic": ["attribution_source", + "synonymy", + "antonymy", + "meronymy", + "repetition", + "lexical_chain", + "negation"], + "morphological": ["mood", + "tense"], + "syntactic": ["relative_clause", + "infinitival_clause", + "present_participial_clause", + "past_participial_clause", + "modified_head", + "interrupted_matrix_clause", + "parallel_syntactic_construction", + "reported_speech", + "subject_auxiliary_inversion", + "nominal_modifier"], + "graphical": ["colon", + "semicolon", + "dash", + "layout", + "parentheses", + "question_mark", + "quotation_marks", + "items_in_sequence"], + "numerical": ["same_count"], + "unsure": ["unsure"], + "orphan": ["orphan"]} diff --git a/structure.py b/structure.py index 88b0221..7a065e3 100755 --- a/structure.py +++ b/structure.py @@ -24,7 +24,7 @@ from modules.logintools import login -def build_canvas(current_doc, current_project, rels, nodes, def_rstrel="", def_multirel="", signal_data=None, user=None, show_signals=True, validations=None): +def build_canvas(current_doc, current_project, rels, nodes, secedge_data="", def_rstrel="", def_multirel="", signal_data=None, user=None, show_signals=True, validations=None, signal_type_dict=None): ###GRAPHICAL PARAMETERS### top_spacing = 20 layer_spacing = 60 @@ -40,6 +40,9 @@ def build_canvas(current_doc, current_project, rels, nodes, def_rstrel="", def_m cpout += ''' \n''' + cpout += '''
    +
  • Delete signals
  • +
\n''' # Signal context menu on right click token cpout += '''
@@ -76,11 +79,13 @@ def build_canvas(current_doc, current_project, rels, nodes, def_rstrel="", def_m if def_rstrel == "": def_rstrel = sorted([rel[0] for rel in rels if rel[1] != "multinuc"])[0] # Take alphabetically first rel if def_rstrel == "": - def_rstrel = "elaboration_r" + def_rstrel = "elaboration-additional_r" if def_multirel == "": - def_multirel = sorted([rel[0] for rel in rels if rel[1] == "multinuc"])[0] # Take alphabetically first rel + multirels = sorted([rel[0] for rel in rels if rel[1] == "multinuc"]) + if len(multirels) > 0: + def_multirel = multirels[0] # Take alphabetically first rel if def_multirel == "": - def_multirel = "joint_m" + def_multirel = "joint-other_m" for rel in rels: rel_list.append(rel[0]) if rel[1] == "multinuc": @@ -113,9 +118,13 @@ def build_canvas(current_doc, current_project, rels, nodes, def_rstrel="", def_m signals[s_id].append({'type': s_type, 'subtype': subtype, 'tokens': tokens}) + + if signal_type_dict is None: + signal_type_dict = get_signal_types_dict(current_doc, current_project) + cpout += ' @@ -296,8 +317,15 @@ def build_canvas(current_doc, current_project, rels, nodes, def_rstrel="", def_m cpout += 'function select_my_rel(options,my_rel){' cpout += 'var multi_options = `' + multi_options + '`;\n' cpout += 'var rst_options = `' + rst_options + '`;\n' - cpout += 'if (options =="multi"){options = multi_options;} else {options=rst_options;}' - cpout += ' return options.replace("