about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--gn2/utility/tools.py7
-rw-r--r--gn2/wqflask/templates/generif.html101
-rw-r--r--gn2/wqflask/templates/gn_editor.html238
-rw-r--r--gn2/wqflask/templates/gn_editor_commit.html55
-rw-r--r--gn2/wqflask/templates/gn_editor_results_page.html46
-rw-r--r--gn2/wqflask/templates/gn_editor_settings.html195
-rw-r--r--gn2/wqflask/templates/wiki/edit_wiki.html93
-rw-r--r--gn2/wqflask/templates/wiki/genewiki.html31
-rw-r--r--gn2/wqflask/views.py109
9 files changed, 757 insertions, 118 deletions
diff --git a/gn2/utility/tools.py b/gn2/utility/tools.py
index d8e21bcd..26ed3a77 100644
--- a/gn2/utility/tools.py
+++ b/gn2/utility/tools.py
@@ -46,14 +46,11 @@ def get_setting(command_id, guess=None):
     """
     def value(command):
         if command:
-            # sys.stderr.write("Found "+command+"\n")
             app_set(command_id, command)
             return command
-        else:
-            return app.config.get(command_id)
+        return app.config.get(command_id)
 
     # ---- Check whether environment exists
-    # print("Looking for "+command_id+"\n")
     command = value(os.environ.get(command_id))
     if command is None or command == "":
         # ---- Check whether setting exists in app
@@ -61,10 +58,8 @@ def get_setting(command_id, guess=None):
         if command is None:
             command = value(guess)
             if command is None or command == "":
-                # print command
                 raise Exception(
                     command_id + ' setting unknown or faulty (update default_settings.py?).')
-    # print("Set "+command_id+"="+str(command))
     return command
 
 
diff --git a/gn2/wqflask/templates/generif.html b/gn2/wqflask/templates/generif.html
deleted file mode 100644
index ac815b43..00000000
--- a/gn2/wqflask/templates/generif.html
+++ /dev/null
@@ -1,101 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}
-GeneWiki Entry for {{ symbol }}
-{% endblock %}
-
-{% block css %}
-<style>
-
- .badge {
-     vertical-align: top;
-     background-color: #336699;
- }
-
- .list-group {
-     counter-reset: gnentries;
- }
-
- summary::before {
-     counter-increment: gnentries;
-     content: counter(gnentries) "." " ";
- }
-
- summary:hover {
-     cursor: zoom-in;
- }
-</style>
-
-{% endblock %}
-{% block content %}
-
-
-<div class="container">
-    <h1 class="page-header">GeneWiki For {{ symbol }}</h1>
-    <p class="well"><strong>GeneWiki</strong> enables you to enrich the annotation of genes and transcripts.</p>
-
-    <h3>
-	<strong>GeneNetwork</strong>
-	<span class="badge">
-	    {{ entries.gn_entries|length if entries.gn_entries[0] else 0 }}
-	</span>:
-    </h3>
-    {% if entries.gn_entries[0] %}
-    <ul class="list-group">
-	{% for entry in entries.gn_entries %}
-	<li class="list-group-item">
-	    <details>
-		<summary>
-		    {{ entry["entry"]["value"] }}
-		    {% if entry.get("weburl") %}
-		    <sup><small><a href="{{ entry.weburl.value }}" target="_blank"><span class="glyphicon glyphicon-globe" aria-hidden="true"></span> web</a></small></sup>
-		    {% endif %}
-		</summary>
-		<dl class="dl-horizontal">
-		    <dt>Author:</dt>
-		    <dd>{{ entry["author"]["value"] }}</dd>
-
-		    {% if entry.get("geneCategory") %}
-		    <dt>Category:</dt>
-		    <dd>{{ entry["geneCategory"]["value"]}}</dd>
-		    {% endif %}
-
-		    <dt>Add Time:</dt>
-		    <dd>{{ entry["created"]["value"]}}</dd>
-		</dl>
-	    </details>
-	</li>
-	{% endfor %}
-    </ul>
-
-    {% else %}
-
-    <p class="well"><u>There are no GeneNetwork entries for <b>{{ symbol }}.</b></u></p>
-
-    {% endif %}
-
-    <h3>
-	<strong>GeneRIF from NCBI</strong>
-	<span class="badge">
-	    {{ entries.ncbi_entries|length if entries.ncbi_entries[0] else 0 }}
-	</span>:
-    </h3>
-    {% if entries.ncbi_entries[0] %}
-	<ol>
-	{% for entry in entries.ncbi_entries %}
-	<li>
-	    {{ entry.entry.value }}
-	    (<a href="{{ entry['generif']['value'] }}" target="_blank">{{ entry["speciesBinomialName"]["value"] }}</a>)
-            {% if entry.PubMedId.value != "" %}
-	    {% set pmids = entry.PubMedId.value.split(",") %}
-	    (PubMed: {% for id in pmids %} <a href="http://rdf.ncbi.nlm.nih.gov/pubmed/{{ id }}" target="_blank">{{ id }}</a>{% endfor %})
-	    <sup><small><em>{{ entry.createdOn.value }}</em></small></sup>
-	    {% endif %}
-	</li>
-	{% endfor %}
-    </ol>
-    {% else %}
-    <p class="well"><u>There are no NCBI entries for <b>{{ symbol }}.</b></u></p>
-    {% endif %}
-</div>
-{% endblock %}
diff --git a/gn2/wqflask/templates/gn_editor.html b/gn2/wqflask/templates/gn_editor.html
new file mode 100644
index 00000000..e5c649d1
--- /dev/null
+++ b/gn2/wqflask/templates/gn_editor.html
@@ -0,0 +1,238 @@
+{% extends "base.html" %}
+{% block title %}Genenetwork Files Editor{% endblock %}
+{% block css %}
+    <style>
+  #diffBtn {
+      cursor:pointer
+  }
+    </style>
+    <link rel="stylesheet"
+          type="text/css"
+          href="{{ url_for('css', filename='diff2html/diff2html.min.css') }}" />
+    <link rel="stylesheet"
+          type="text/css"
+          href="{{ url_for('css', filename='highlight/default.min.css') }}" />
+{% endblock %}
+{% block search %}{% endblock %}
+{% block content %}
+    <section class="container-fluid">
+        <header class="row">
+            <nav class="navbar" style="background-color:#ccc">
+                <div class="container-fluid">
+                    <div class="navbar-header">
+                        <a class="navbar-brand" href="#"><strong>GN Editor</strong></a>
+                    </div>
+                    <div style="padding-right:3.6rem">
+                        <ul class="nav navbar-nav navbar-right">
+                            <li>
+                                <a href="#"
+                                   hx-get="/editor/commit"
+                                   hx-target="#output"
+                                   hx-trigger="click"
+                                   hx-swap="innerHTML">Commit</a>
+                            </li>
+                            <li>
+                                <a hx-trigger="click" id="diffBtn"  hx-swap="innerHTML">Diff</a>
+                            </li>
+                            <li>
+                                <a href="#"
+                                   hx-get="/editor/settings"
+                                   hx-target="#output"
+                                   hx-trigger="click"
+                                   hx-swap="innerHTML">Settings</a>
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </nav>
+        </header>
+        <div>
+            <div class="row" id="gn-editor">
+                <section class="col-sm-6" id="editor" style="height:100vh">
+                    <textarea name="ckcontent" id="ckcontent" style='display:none'>
+        </textarea>
+                </section>
+                <section class="col-sm-6">
+                    <div class="row">
+                        <section class="col-sm-10 col-sm-offset-1"
+                                 id="output"
+                                 style="height:100vh;
+                                        overflow-y:scroll;
+                                        padding-top:2.5rem"
+                                 id="swap">
+                        </section>
+                    </div>
+                </section>
+            </div>
+        </div>
+    </section>
+{% endblock %}
+{% block js %}
+    <script src="{{ url_for('js', filename='jquery/jquery.min.js') }}"
+            type="text/javascript"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='diff2html/diff2html.min.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='jquery-ui/jquery-ui.min.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='htmx.min.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='jsdiff/diff.min.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='ace/ace.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='marked/marked.min.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='marked/marked-highlight.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='highlight/highlight.min.js') }}"></script>
+    <script language="javascript"
+            type="text/javascript"
+            src="{{ url_for('js', filename='ckeditor/ckeditor.js') }}"></script>
+    <script type="text/javascript">
+  document.addEventListener('DOMContentLoaded', function() {
+      $('footer').hide()
+      var editor_configurations = {
+          selectionStyle: 'line',// "line"|"text"
+          highlightActiveLine: true, // boolean
+          highlightSelectedWord: true, // boolean
+          readOnly: false, // boolean: true if read only
+          cursorStyle: 'ace', // "ace"|"slim"|"smooth"|"wide"
+          mergeUndoDeltas: true, // false|true|"always"
+          behavioursEnabled: true, // boolean: true if enable custom behaviours
+          wrapBehavioursEnabled: true, // boolean
+          autoScrollEditorIntoView: undefined, // boolean: this is needed if editor is inside scrollable
+          keyboardHandler: null,
+
+          // renderer options
+          animatedScroll: false, // boolean: true if scroll should be animated
+          displayIndentGuides: false, // boolean: true if the indent should be shown. See 'showInvisibles'
+          showInvisibles: false, // boolean -> displayIndentGuides: true if show the invisible tabs/spaces in indents
+          showPrintMargin: true, // boolean: true if show the vertical print margin
+          printMarginColumn: 80, // number: number of columns for vertical print margin
+          printMargin: undefined, // boolean | number: showPrintMargin | printMarginColumn
+          showGutter: true, // boolean: true if show line gutter
+          fadeFoldWidgets: false, // boolean: true if the fold lines should be faded
+          showFoldWidgets: true, // boolean: true if the fold lines should be shown ?
+          showLineNumbers: true,
+          highlightGutterLine: false, // boolean: true if the gutter line should be highlighted
+          hScrollBarAlwaysVisible: false, // boolean: true if the horizontal scroll bar should be shown regardless
+          vScrollBarAlwaysVisible: false, // boolean: true if the vertical scroll bar should be shown regardless
+          fontSize: 16, // number | string: set the font size to this many pixels
+          fontFamily: undefined, // string: set the font-family css value
+          maxLines: undefined, // number: set the maximum lines possible. This will make the editor height changes
+          minLines: undefined, // number: set the minimum lines possible. This will make the editor height changes
+          maxPixelHeight: 0, // number -> maxLines: set the maximum height in pixel, when 'maxLines' is defined.
+          scrollPastEnd: 0, // number -> !maxLines: if positive, user can scroll pass the last line and go n * editorHeight more distance
+          fixedWidthGutter: false, // boolean: true if the gutter should be fixed width
+          theme: "ace/theme/textmate", // theme string from ace/theme or custom?
+          scrollSpeed: 2, // number: the scroll speed index
+          dragDelay: 0,
+          dragEnabled: true, // boolean: enable dragging
+          focusTimout: 0, // number: the focus delay before focus starts.
+          tooltipFollowsMouse: true, // boolean: true if the gutter tooltip should follow mouse
+          // session options
+          firstLineNumber: 1, // number: the line number in first line
+          overwrite: false, // boolean
+          newLineMode: 'auto', // "auto" | "unix" | "windows"
+          useWorker: true, // boolean: true if use web worker for loading scripts
+          useSoftTabs: true, // boolean: true if we want to use spaces than tabs
+          tabSize: 4, // number
+          wrap: true, // boolean | string | number: true/'free' means wrap instead of horizontal scroll, false/'off' means horizontal scroll instead of wrap, and number means number of column before wrap. -1 means wrap at print margin
+          indentedSoftWrap: true, // boolean
+          foldStyle: 'markbegin', // enum: 'manual'/'markbegin'/'markbeginend'.
+          mode: 'ace/mode/markdown'
+
+      }
+      let hash = {{ hash|tojson }}
+      let filePath = {{ file_path|tojson }}
+      let fileExt =   (filePath.substring(filePath.lastIndexOf('.')+1, filePath.length) || "md").toLowerCase();
+      let data = {{ content|tojson }}
+
+      localStorage.setItem("gn_editor_sha" ,hash)
+      htmx.on("#output", "commitEvent", function(event){
+          htmx.ajax("POST", "/editor/commit", {target: "#output", swap:"innerHTML",values: {'msg':event.detail.payload, 'content': editor.getValue(), "hash": localStorage.getItem("gn_editor_sha"), "file_path": filePath}})
+      })
+
+      htmx.on("#output", "diffEvent", function(event){
+          var fileName = "{{ file_path }}"
+          var diffContent = Diff.createTwoFilesPatch(fileName,fileName, data, editor.getValue())
+          var diffHtml = Diff2Html.html(diffContent, {
+              drawFileList: true,
+              matching: 'lines',
+              outputFormat: 'line-by-line',
+          });
+          htmx.find("#output").innerHTML = diffHtml
+      })
+
+      htmx.on("#output", "updateEditor", function(event){
+          if (fileExt != 'rtf'){
+              editor.setOptions({
+                  ...editor_configurations,
+                  ...event.detail.payload
+              })
+              editor.renderer.updateFull();
+          }
+
+      })
+
+      htmx.on("#diffBtn","click",function(event){
+          //dispatch your event here
+	  console.log("Clicked diff btn here")
+	  htmx.find("#output").dispatchEvent(
+          new CustomEvent("diffEvent", {
+              bubbles: true,
+              detail: {},
+          }),
+      );
+      })
+      function updatePreview(){
+          const {markedHighlight} = globalThis.markedHighlight;
+
+          let new_marked = new marked.Marked(
+              markedHighlight({
+                  langPrefix: 'hljs language-',
+                  highlight(code, lang) {
+                      const language = hljs.getLanguage(lang) ? lang : 'plaintext';
+                      return hljs.highlight(code, { language }).value;
+                  }}));
+          new_marked.use({
+              pedantic: false,
+              gfm: true,
+          });
+          previewContent = document.querySelector("#output");
+          var markdownContent = editor.getValue();
+          var htmlContent = new_marked.parse(markdownContent)    //work on error handling for invalid markdown
+          previewContent.innerHTML = htmlContent;
+      }
+
+      if (fileExt == "rtf"){
+          var editor = CKEDITOR.replace('ckcontent',    {
+              height: '100vh',
+          });
+          editor.setData(data)
+          editor.getValue = editor.getData
+      }
+      else {
+          var editor = ace.edit("editor");
+          editor.setOptions(editor_configurations);
+          editor.container.style.resize = "horizontal";
+          editor.getSession().on("change", function(e){
+              updatePreview()
+          })
+          editor.setValue(data, -1);
+      }
+
+  });
+
+
+    </script>
+{% endblock %}
diff --git a/gn2/wqflask/templates/gn_editor_commit.html b/gn2/wqflask/templates/gn_editor_commit.html
new file mode 100644
index 00000000..84ebd818
--- /dev/null
+++ b/gn2/wqflask/templates/gn_editor_commit.html
@@ -0,0 +1,55 @@
+<section classs="row commit-content"  style="padding-top:25px">
+    <div class="col-sm-10 col-sm-offset-1">
+        <div>
+            <div>
+                <h2>
+                    <i>Commit your Changes</i>
+                </h2>
+            </div>
+        </div>
+        <br>
+        <div>
+            <div class="row">
+                <label class="col-sm-8" for="message">
+                    <i>*Commit Message*</i>
+                </label>
+            </div>
+            <br>
+            <div class="row">
+                <textarea style="white-space: normal" rows="12" class="col-sm-12" name="message" required id="commit-message">
+        </textarea>
+            </div>
+        </div>
+        <br>
+        <br>
+        <div class="row">
+            <button id="btn-commit" class="btn btn-primary col-lg-3 col-lg-offset-1 col-sm-4 col-sm-offset-1">Commit</button>
+            <button id="btn-diff" class="btn col-lg-3 col-lg-offset-1  col-sm-4 col-sm-offset-1">Show Diff</button>
+        </div>
+        <br>
+        <div id="diff_page"></div>
+    </div>
+</section>
+{% block js %}
+    <script>
+  htmx.on("#btn-commit", "click", function(event){
+      let msg = htmx.find("#commit-message").value.trim()
+      if (msg != ""){
+          htmx.find("#output").dispatchEvent(
+              new CustomEvent("commitEvent", {
+                  bubbles: true,
+                  detail: {action: 'didInitialize', payload: msg},
+              }),
+          );
+      }
+  })
+  htmx.on("#btn-diff", "click", function(event){
+      htmx.find("#output").dispatchEvent(
+          new CustomEvent("diffEvent", {
+              bubbles: true,
+              detail: {},
+          }),
+      );
+  })
+    </script>
+{% endblock %}
diff --git a/gn2/wqflask/templates/gn_editor_results_page.html b/gn2/wqflask/templates/gn_editor_results_page.html
new file mode 100644
index 00000000..666bd432
--- /dev/null
+++ b/gn2/wqflask/templates/gn_editor_results_page.html
@@ -0,0 +1,46 @@
+<section classs="row commit-content"  style="padding-top:25px">
+    <div class="col-sm-10 col-sm-offset-1">
+        <div>
+            <div>
+                <h2>
+                    <i>Results status - {{ status }}</i>
+                </h2>
+            </div>
+        </div>
+        <br>
+        {% if error %}
+            <h1>
+                Error-type: <mark style="font-family: 'Linux Libertine','Georgia','Times','Source Serif Pro',serif"><b><i>{{ error }}</i></b></mark>
+            </h1>
+            <h3>
+                Error-message:
+                <mark style="font-family: 'Linux Libertine','Georgia','Times','Source Serif Pro',serif"><b><i>{{ msg }}</i></b></mark>
+            </h3>
+        {% else %}
+            <div>
+                <h3>
+                    New Commit Sha: <span class="lead">
+                    <mark style="font-family: 'Linux Libertine','Georgia','Times','Source Serif Pro',serif"><b><i>{{ commit_sha }}</i></b></mark>
+                </span>
+            </h3>
+            <br />
+            <div class="lead">
+                <h3>
+                    Commit Message:
+                    <mark style="font-family: 'Linux Libertine','Georgia','Times','Source Serif Pro',serif"><b><i>{{ message }}</i></b></mark>
+                </h3>
+            </div>
+        </div>
+    {% endif %}
+    <br>
+</div>
+</section>
+{% block js %}
+    <script>
+  var commitSha = "{{ commit_sha }}";
+  if (commitSha !="" && commitSha!= undefined){
+      localStorage.setItem("gn_editor_sha", commitSha)
+  }
+
+    </script>
+{% endblock %}
diff --git a/gn2/wqflask/templates/gn_editor_settings.html b/gn2/wqflask/templates/gn_editor_settings.html
new file mode 100644
index 00000000..1e2cae10
--- /dev/null
+++ b/gn2/wqflask/templates/gn_editor_settings.html
@@ -0,0 +1,195 @@
+<section>
+    <h4>
+        <i>Select a theme</i>
+    </h4>
+    <form>
+        <div class="form-check">
+            <input class="form-check-input"
+                   type="radio"
+                   name="theme"
+                   id="twilightTheme"
+                   value="twilight"
+                   checked>
+            <label class="form-check-label" for="twilight">twilight</label>
+        </div>
+        <div class="form-check">
+            <input class="form-check-input"
+                   type="radio"
+                   name="theme"
+                   id="draculaTheme"
+                   value="dracula">
+            <label class="form-check-label" for="draculaTheme">dracula</label>
+        </div>
+        <div class="form-check">
+            <input class="form-check-input"
+                   type="radio"
+                   name="theme"
+                   id="normalTheme"
+                   value="textmate"
+                   checked>
+            <label class="form-check-label" for="normalTheme">Text</label>
+        </div>
+        <div class="form-check">
+            <input class="form-check-input"
+                   type="radio"
+                   name="theme"
+                   id="monokaiTheme"
+                   value="monokai">
+            <label class="form-check-label" for="monokaiTheme">Monokai</label>
+        </div>
+        <div class="form-check">
+            <input class="form-check-input"
+                   type="radio"
+                   name="theme"
+                   id="customTheme"
+                   value="xcode">
+            <label class="form-check-label" for="customTheme">xcode</label>
+        </div>
+        <br>
+        <div class="form-group" style="padding-top:10px">
+            <h4>
+                <i>Select Font Size</i>
+            </h4>
+            <input type="range"
+                   class="form-control-range"
+                   id="fontSizeRange"
+                   name="fontSizeRange"
+                   min="10"
+                   max="36"
+                   step="1"
+                   value="16">
+            <em><span id="fontSizeValue">16</span> px</em>
+        </div>
+        <br>
+        <div>
+            <h4>
+                <i>Select Wrap Option:</i>
+            </h4>
+            <div class="form-check">
+                <input class="form-check-input"
+                       type="radio"
+                       name="wrap"
+                       id="normalTheme"
+                       value="true"
+                       checked>
+                <label class="form-check-label" for="normalTheme">True</label>
+            </div>
+            <div class="form-check">
+                <input class="form-check-input"
+                       type="radio"
+                       name="wrap"
+                       id="monokaiTheme"
+                       value="false">
+                <label class="form-check-label" for="monokaiTheme">False</label>
+            </div>
+        </div>
+        <div>
+            <h4>
+                <i>Cursor Styles:</i>
+            </h4>
+            <div class="form-check">
+                <input class="form-check-input"
+                       type="radio"
+                       name="cursor"
+                       id="aceCursor"
+                       value="ace"
+                       checked>
+                <label class="form-check-label" for="aceCursor">ace</label>
+            </div>
+            <div class="form-check">
+                <input class="form-check-input"
+                       type="radio"
+                       name="cursor"
+                       id="slimCursor"
+                       value="slim">
+                <label class="form-check-label" for="slimCursor">slim</label>
+            </div>
+            <div class="form-check">
+                <input class="form-check-input"
+                       type="radio"
+                       name="cursor"
+                       id="smoothCursor"
+                       value="smooth">
+                <label class="form-check-label" for="smoothCursor">smooth</label>
+            </div>
+            <div class="form-check">
+                <input class="form-check-input"
+                       type="radio"
+                       name="cursor"
+                       id="wideCursor"
+                       value="wide">
+                <label class="form-check-label" for="wideCursor">wide</label>
+            </div>
+        </div>
+    </form>
+    <br>
+    <h4>
+        <i>Choose KeyBoard Binding:</i>
+    </h4>
+    <div class="form-check">
+        <input class="form-check-input"
+               type="radio"
+               name="keyboard"
+               id="default"
+               value="default"
+               checked>
+        <label class="form-check-label" for="default">Default</label>
+    </div>
+    <div class="form-check">
+        <input class="form-check-input"
+               type="radio"
+               name="keyboard"
+               id="vscode"
+               value="vscode">
+        <label class="form-check-label" for="vscode">Vscode</label>
+    </div>
+    <div class="form-check">
+        <input class="form-check-input"
+               type="radio"
+               name="keyboard"
+               id="emacs"
+               value="emacs">
+        <label class="form-check-label" for="emacs">Emacs</label>
+    </div>
+    <div class="form-check">
+        <input class="form-check-input"
+               type="radio"
+               name="keyboard"
+               id="vim"
+               value="vim">
+        <label class="form-check-label" for="vim">Vim</label>
+    </div>
+    <br>
+    <div class="row">
+        <div class="col-sm-offset-1">
+            <button class="btn btn-primary" id="settingBtn">Save Settings</button>
+        </div>
+    </div>
+</section>
+{% block js %}
+    <script>
+  $('#fontSizeRange').on('input', function () {
+      var fontSize = $(this).val();
+      $('#fontSizeValue').text(fontSize);
+  });
+
+  htmx.on("#settingBtn", "click", function () {
+      var formData = {
+          theme: `ace/theme/${$('input[name="theme"]:checked').val()}`,
+          fontSize: parseInt($("#fontSizeRange").val()),
+          wrap:$('input[name="wrap"]:checked').val()== "false" ? false : true,
+          cursorStyle: $('input[name="cursor"]:checked').val(),
+          keyboardHandler:($('input[name="keyboard"]:checked').val()  == "default"?
+                           null: `ace/keyboard/${$('input[name="keyboard"]:checked').val()}`
+                          )
+
+      }
+      htmx.find("#output").dispatchEvent(
+          new CustomEvent("updateEditor", {
+              bubbles: true,
+              detail: {action: 'didInitialize', payload: formData},
+          }),
+      );
+  })
+    </script>
+{% endblock %}
diff --git a/gn2/wqflask/templates/wiki/edit_wiki.html b/gn2/wqflask/templates/wiki/edit_wiki.html
new file mode 100644
index 00000000..242456c4
--- /dev/null
+++ b/gn2/wqflask/templates/wiki/edit_wiki.html
@@ -0,0 +1,93 @@
+{% extends "base.html" %}
+
+{% block css %}
+<style>
+ .panel {
+     width: 90%;
+     margin: 2em;
+ }
+ .container {
+     align-content: center;
+ }
+</style>
+{% endblock %}
+
+{% block content %}
+
+{{ flash_me() }}
+<section class="container center-block">
+    <div class="row">
+	<div class="col-md-3"></div>
+	<div class="col-md-9">
+	    <h2>Edit Wiki</h2>
+	    <br>
+	    <form class="form-horizontal" method="POST">
+		<input type="hidden" name="symbol" value="{{ content["symbol"] }}">
+		<div class="form-group">
+		    <label for="reason" class="col-sm-2">Reason for Modification: </label>
+		    <input type="text" name="reason" size=45 maxlength=100 required>
+		</div>
+		<div class="form-group">
+		    <label for="species" class="col-sm-2">Species: </label>
+		    <select name="species" id="species">
+			{% for name, species_name in species_dict.items() %}
+			    {% if name == content["species"] %}
+			    <option selected="selected" value="{{ name }}">{{ species_name }}</option>
+			    {% else %}
+			    <option value="{{ name }}">{{ species_name }}</option>
+			    {% endif %}
+			{% endfor %}
+		    </select>
+		</div>
+		<div class="form-group">
+		    <label for="pubmed_ids" class="col-sm-2">PubMed IDS: </label>
+		    <input type="text" name="pubmed_ids" size=25 maxlength=25 value="{{ " ".join(content["pubmed_ids"]) }}">
+		    (optional, separate by blank space only)
+		</div>
+		<div class="form-group">
+		    <label for="web_url" class="col-sm-2">Web resource URL: </label>
+		    {% if content["weburl"] %}
+		    <input type="text" name="web_url" value="{{ content["weburl"] }}" size=50 maxlength=255>
+		    {% else %}
+		    <input type="text" name="web_url" value="http://" size=50 maxlength=255>
+		    {% endif %}
+		    (optional)
+		</div>
+		<div class="form-group">
+		    <label for="comment" class="col-sm-2">Text: </label>
+		    <textarea name="comment" rows=5 cols=60 required>{{ content["comment"] }}</textarea>
+		</div>
+		<div class="form-group">
+		    <label for="email" class="col-sm-2">Email: </label>
+		    <input type="text" name="email" value="" required>
+		</div>
+		<div class="form-group">
+		    <label for="usercode" class="col-sm-2">User Code: </label>
+		    <input type="text" name="initial" value="{{ content["initial"] }}"/>
+		    (optional user or project code or your initials)
+		</div>
+		<div class="form-group">
+		    <label class="col-sm-2">Category of Gene<br>(Please select one or <br>many categories): </label>
+		    <div class="col-sm-10">
+		    {% for group in grouped_categories %}
+		    <div class="row">
+		    {% for cat in group %}
+			<label class="checkbox-inline col-sm-3">
+			    {% if cat in content["categories"] %}
+			    <input checked type="checkbox" name="genecategory" value="{{ cat }}"> {{ cat }}
+			    {% else %}
+			    <input type="checkbox" name="genecategory" value="{{ cat }} "> {{ cat }}
+			    {% endif %}
+			</label>
+		    {% endfor %}
+		    </div>
+		    {% endfor %}
+		</div>
+		<div class="form-group">
+		    <button type="submit" name="submit" class="btn btn-primary">Update GeneWiki Entry</button>
+		    <button type="reset" name="rest" class="btn btn-secondary" onClick="window.location.reload();">Reset</button>
+		</div>
+	    </form>
+	</div>
+    </div>
+{% endblock %}
diff --git a/gn2/wqflask/templates/wiki/genewiki.html b/gn2/wqflask/templates/wiki/genewiki.html
new file mode 100644
index 00000000..496f5e28
--- /dev/null
+++ b/gn2/wqflask/templates/wiki/genewiki.html
@@ -0,0 +1,31 @@
+{% extends "base.html" %}
+{% block title %}GeneWiki Entry for {{ symbol }}{% endblock %}
+{% block content %}
+    {{ flash_me() }}
+    <div class = "container">
+        <h1 class = "page-header">GeneWiki Entries</h1>
+        <p>
+            GeneWiki enables you to enrich the annotation of genes and transcripts. Please submit or edit a GeneWiki note (500 characters max) related to a gene, its transcripts, or proteins. When possible include PubMed identifiers or web resource links (URL addresses). Please ensure that the additions will have widespread use. For additional information, check the GeneWiki <a href="https://gn1.genenetwork.org/GeneWikihelp.html" target="_blank">help document</a>.
+        </p>
+        <h3>GeneWiki For {{ symbol }}:</h3>
+        <h5>
+            <strong>GeneNetwork:</strong>
+        </h5>
+        {% if wiki %}
+            <ol>
+                {% for entry in wiki %}
+                    <li>
+                        {{ entry.get("comment") }}
+                        {% if entry.get("web_url") %}
+                            <sup> <small> <a href = "{{ entry.web_url }}" target = "_blank"> <span class = "glyphicon glyphicon-globe" aria-hidden = "true"></span>web</a></small></sup>
+                        {% endif %}
+                    </li>
+                {% endfor %}
+            </ol>
+        {% else %}
+            <p class = "well">
+                <u> There are no GeneNetwork entries for <b> {{ symbol }}.</b></u>
+            </p>
+        {% endif %}
+    </div>
+{% endblock %}
diff --git a/gn2/wqflask/views.py b/gn2/wqflask/views.py
index 993c6f0c..4421011b 100644
--- a/gn2/wqflask/views.py
+++ b/gn2/wqflask/views.py
@@ -1,4 +1,5 @@
 """Main routing table for GN2"""
+
 import array
 import base64
 import csv
@@ -44,6 +45,7 @@ from flask import flash
 
 from gn2.wqflask import search_results
 from gn2.wqflask import server_side
+
 # Used by YAML in marker_regression
 from gn2.base.data_set import create_dataset
 from gn2.base.trait import fetch_symbols
@@ -288,6 +290,38 @@ def gnqna():
     return render_template("gnqa.html")
 
 
+@app.route("/editor/edit", methods=["GET"])
+@require_oauth2
+def edit_gn_doc_file():
+    file_path = request.args.get("file-path", "")
+    response = requests.get(f"http://localhost:8091/edit?file_path={file_path}")
+    response.raise_for_status()
+    return render_template("gn_editor.html", **response.json())
+
+
+@app.route("/editor/settings", methods=["GET"])
+@require_oauth2
+def configure_gn_editor():
+    return render_template("gn_editor_settings.html")
+
+
+@app.route("/editor/commit", methods=["GET", "POST"])
+@require_oauth2
+def commit_gn_doc():
+    # TODO add env variable for gn-guile web server
+    if request.method == "GET":
+        return render_template("gn_editor_commit.html")
+    results = requests.post("http://localhost:8091/commit", json={
+                          "content":  request.form.get("content"),
+                          "filename": request.form.get("file_path"),
+                          "username": session_info()["user"]["name"],
+                          "email": session_info()["user"]["email"],
+                          "commit_message": request.form.get("msg"),
+                          "prev_commit": request.form.get("hash")})
+    data = results.json()
+    data["filename"] = request.form.get("file_path")
+    return render_template("gn_editor_results_page.html", **data)
+
 
 @app.route("/gnqna/hist", methods=["GET", "DELETE"])
 @require_oauth2
@@ -1039,7 +1073,8 @@ def corr_compute_page():
     with Redis.from_url(REDIS_URL, decode_responses=True) as rconn:
         if request.method == "POST":
             request_received = datetime.datetime.utcnow()
-            filename = hmac.hmac_creation(f"request_form_{request_received.isoformat()}")
+            filename = hmac.hmac_creation(
+                f"request_form_{request_received.isoformat()}")
             filepath = f"{TMPDIR}{filename}"
             with open(filepath, "wb") as pfile:
                 pickle.dump(request.form, pfile,
@@ -1201,19 +1236,25 @@ def display_diffs_users():
                            files=files)
 
 
-@app.route("/genewiki/<symbol>")
-def display_generif_page(symbol):
+@app.route("/genewiki/<string:symbol>")
+def display_genewiki_page(symbol: str):
     """Fetch GeneRIF metadata from GN3 and display it"""
-    entries = requests.get(
-        urljoin(
-            GN3_LOCAL_URL,
-            f"/api/metadata/genewiki/{symbol}"
+    wiki = {}
+    try:
+        wiki = requests.get(
+            urljoin(
+                GN3_LOCAL_URL,
+                f"/api/metadata/wiki/{symbol}"
+            )
         )
-    ).json()
+        wiki.raise_for_status()
+        wiki = wiki.json()
+    except requests.RequestException as excp:
+        flash(excp, "alert-warning")
     return render_template(
-        "generif.html",
+        "wiki/genewiki.html",
         symbol=symbol,
-        entries=entries
+        wiki=wiki
     )
 
 
@@ -1346,7 +1387,8 @@ def edit_case_attributes(inbredset_id: int) -> Response:
 
         def flash_success(resp):
             def __succ__(remote_resp):
-                flash(f"Success: {remote_resp.json()['message']}", "alert-success")
+                flash(
+                    f"Success: {remote_resp.json()['message']}", "alert-success")
                 return resp
             return __succ__
         return monad_requests.post(
@@ -1472,3 +1514,48 @@ def approve_reject_diff() -> Response:
         return redirect(url_for("view_diff",
                                 inbredset_id=inbredset_id,
                                 diff_id=form["diff_id"]))
+
+
+@app.route("/metadata/wiki/<int:comment_id>/edit", methods=["GET", "POST"])
+def edit_wiki(comment_id: int):
+    """fetch generif metadata from gn3 and display it"""
+    # FIXME: better error handling
+    if request.method == "GET":
+        last_wiki_resp = requests.get(urljoin(GN3_LOCAL_URL, f"/api/metadata/wiki/{comment_id}"))
+        last_wiki_resp.raise_for_status()
+        last_wiki_content = last_wiki_resp.json()
+
+        species_dict_resp = requests.get(urljoin(GN3_LOCAL_URL, "/api/metadata/wiki/species"))
+        species_dict_resp.raise_for_status()
+        species_dict = species_dict_resp.json()
+
+        categories_resp = requests.get(urljoin(GN3_LOCAL_URL, "/api/metadata/wiki/categories"))
+        categories_resp.raise_for_status()
+        categories = list(categories_resp.json().keys())
+        grouped_categories = [categories[i : i + 3] for i in range(0, len(categories), 3)]
+
+        return render_template(
+            "wiki/edit_wiki.html",
+            content=last_wiki_content,
+            species_dict=species_dict,
+            grouped_categories=grouped_categories,
+        )
+    if request.method == "POST":
+        post_data = request.form
+        payload = {
+            "symbol": post_data["symbol"],
+            "pubmed_ids": [x.strip() for x in post_data["pubmed_ids"].split()],
+            "species": post_data["species"],
+            "comment": post_data["comment"],
+            "email": post_data["email"],
+            "web_url": post_data["web_url"],
+            "initial": post_data["initial"],
+            "categories": post_data.getlist("genecategory"),
+            "reason": post_data["reason"],
+            }
+        post_response = requests.post(urljoin(GN3_LOCAL_URL, f"api/metadata/wiki/{comment_id}/edit"), json=payload)
+        post_response.raise_for_status()
+        post_res = post_response.json()
+
+        flash(f"Success: {post_res}", "alert-success")
+        return redirect(url_for("edit_wiki", comment_id=comment_id))