Browse Source

Cleaning up refactor:

- Removed deprecated JS files for the rule app
- Added <title> change for v3 Rules
- README update
Nathan Schneider 2 years ago
parent
commit
28bcf2dbfb
4 changed files with 7 additions and 776 deletions
  1. 3 1
      README.md
  2. 0 613
      _includes/rule-scripts.html
  3. 0 161
      _layouts/rule.html
  4. 4 1
      assets/js/vue.rules.js

+ 3 - 1
README.md

@@ -8,6 +8,8 @@ A governance toolkit for great communities, located at [communityrule.info](http
 
 
 This project welcomes contributors. All contributions are assumed to accept the project's GPL and Creative Commons licenses.
 This project welcomes contributors. All contributions are assumed to accept the project's GPL and Creative Commons licenses.
 
 
-To contribute governance templates, copy an existing one at _template/[template_name].md and fill out the YAML metadata. This will automatically add a new template into the system. Propose edits to existing governance templates at _template/[template_name].md.
+To contribute governance templates, copy an existing one at `_template/[template_name].md` and fill out the YAML metadata. This will automatically add a new template into the system. Propose edits to existing governance templates at `_template/[template_name].md`.
+
+The app currently depends on Vue.js. Major scripts are located at `assets/js/`.
 
 
 To get involved, [contact MEDLab](mailto:medlab@colorado.edu).
 To get involved, [contact MEDLab](mailto:medlab@colorado.edu).

+ 0 - 613
_includes/rule-scripts.html

@@ -1,613 +0,0 @@
-<!-- Enables dragging on mobile 
-https://github.com/Bernardo-Castilho/dragdroptouch -->
-<script src="/assets/DragDropTouch.js"></script>
-
-<script>  
-  // Enter JavaScript-land!
-  // First, some functions, followed by initialization commands
-
-  // Begin BUILDER functions
-  // source: https://www.codecanal.com/html5-drag-and-copy/
-  function allowDrop(ev) {
-      ev.preventDefault();
-  }
-  function drag(ev) {
-      ev.dataTransfer.setData("text", ev.target.id);
-      document.getElementById("module-input").classList.add('drag-target');
-  }
-  function dragEnd(ev) {
-      document.getElementById("module-input").classList.remove('drag-target');
-  }
-  function drop(ev) {
-      ev.preventDefault();
-      var target = ev.target;
-      // First, confirm target location is valid
-      function targetCheck () {
-          if (!editMode) {
-              return false;
-          } else if (target.id == "module-input") { 
-              return true;
-          } else if (!document.getElementById("module-input").contains(target)) {
-              // Ignore destinations not in the correct area
-              return false;
-          } else if (target.id == "drag-directions") {
-              // Prevents dropping into dummy text field
-              target = target.parentElement;
-              return true;
-          } else if (target.classList[0] == "module") {
-              return true;
-          } else {
-              // be sure we're adding to module, not its children              
-              target = target.parentElement;
-              return targetCheck();
-          }
-      }
-      if (!targetCheck()) { return; } 
-      // Set up transfer
-      var data = ev.dataTransfer.getData("text");
-      // Iff module is from the menu clone it
-      var module = document.getElementById(data);
-      if ((module.parentElement.id == "module-menu") ||
-          (module.parentElement.parentElement.id == "module-menu")) {
-          // ^ because a subgroup might be parent
-          module = module.cloneNode(true);
-          var name = null;
-          if (module.id == "module-custom") {
-              // For custom modules: replace the <input> with text
-              name = module.getElementsByTagName("input")[0].value;
-              module.getElementsByTagName("input")[0].remove();
-              var customText = document.createElement("span");
-              customText.id = "module-name";
-              customText.append(name);
-              module.prepend(customText);
-          }
-          // append id with unique timestamp
-          var nowModule = new Date();
-          module.id += "-" + nowModule.getTime();
-      }
-      // hide the info button
-    //   module.children[1].style.display = "none";
-      // display the deletion button
-    //   module.children[2].style.display = "inline";
-      // pop it in!
-      target.appendChild(module);
-      // add module-field button (using HTML so it saves to Library)
-      module.outerHTML = module.outerHTML.replace("id=\"module-name\"",
-                               "id=\"module-name\" onclick=\"moduleEditField(this.parentNode.id)\"");      
-      // create the module-field
-      moduleEditField(module.id);
-      // be sure the dummy text is gone
-      if (document.contains(document.getElementById("drag-directions"))) {
-          document.getElementById("drag-directions").remove();
-      }
-  }
-
-  // Edits the title field of a given module based on #custom-field
-  function moduleTitleEdit(moduleID) {
-      var module = document.getElementById(moduleID).children[0];
-      var content =
-          stripHTML(document.getElementById("custom-field").innerHTML);
-      module.title = content;
-  }
-  
-  // Sets up a field for displaying and editing module details
-  function moduleEditField(moduleID) {
-      var module = document.getElementById(moduleID);
-      var moduleName = module.children[0].innerHTML;
-      var moduleTitle = module.children[0].title;
-      if (editMode) {
-          var query = "How does the <span class=\"module\">" + moduleName
-              + "</span> module work?";
-          var destination = document.getElementById("builder-field");
-          if (moduleName == null) { moduleName = ""; }     
-          var output = '\n<div id="custom-field-container">';
-          output += '<span class="prompt">' + query + '</span>';
-          output += '<div class="field-controls"><a onclick="this.parentNode.parentNode.remove()"><img src="{% link assets/tabler_icons/x.svg %}" class="delete-module" /></a></div>';
-          output += '<p contenteditable="true" class="editable" id="custom-field" oninput="moduleTitleEdit(\'' + moduleID + '\')">' + moduleTitle + '</p>';
-          output += '</div>\n';
-          destination.innerHTML = output;
-      } else {
-          var output = '\n<div id="custom-field-container">';
-          output += '<div class="field-controls"><a onclick="this.parentNode.parentNode.remove()"><img src="{% link assets/tabler_icons/x.svg %}" class="delete-module" /></a></div>';
-          output += '<p class="editable" id="custom-field">'
-              + moduleTitle + '</p>';
-      }
-  }
-
-  // Tests if the RuleBuilder is empty
-  function builderEmpty() {
-      var builder = document.getElementById("module-input");
-      var childs = builder.children;
-      if (builder.getElementsByClassName("module").length > 0) {
-          return false;
-      } else {
-          return true;
-      }
-  }
-
-  // Turns RuleBuilder contents into an output-ready nested array
-  // Returns empty array if no modules
-  function builderArray() {
-      var modules = document.getElementById("module-input").children;
-      // takes an array of children
-      // returns an array with all modules in the array, recursively nested
-      function iterateArray (childs) {
-          var moduleArray = [];
-          if (childs.length > 0) {
-              for (var i = 0; i < childs.length; i++) {
-                  module = childs[i];
-                  if (module.classList[0] == "module") {
-                      var moduleName = module.children.item("module-name");
-                      var moduleData = moduleName.title;
-                      var moduleChilds = module.children;
-                      moduleArray.push(
-                          [stripHTML(moduleName.innerHTML),
-                           stripHTML(moduleData),
-                           iterateArray(moduleChilds)]);
-                  }
-              }
-          }
-          return moduleArray;
-      } // end function
-      return iterateArray(modules);
-  }
-
-  // returns HTML version of Builder content
-  function displayBuilderHTML() {
-      var output = "";
-      var mainArray = builderArray();
-      function arrayHTML(thisArray) {
-          var thisOutput = "";
-          if (thisArray.length > 0) {
-              thisOutput += '<ul>\n';
-              for (var i = 0; i < thisArray.length; i++) {
-                  var item = thisArray[i];
-                  thisOutput += '<li><strong>' + item[0] + '</strong> ';
-                  thisOutput += item[1] + '</li>\n';
-                  if (item[2].length > 0) {
-                      thisOutput += arrayHTML(item[2]);
-                  }
-              }
-              thisOutput += '</ul>\n';
-          }
-          return thisOutput
-      }
-      if (mainArray.length > 0) {
-          output += '<div class="builder-list">\n';
-          output += arrayHTML(mainArray) + '\n</div>\n';
-      }
-      return output;
-  }
-
-  // returns Markdown version of Builder content
-  function displayBuilderMD() {
-      var mainArray = builderArray();
-      var indentLevel = 0;
-      function arrayMD(thisArray) {
-          var thisOutput = "";
-          if (thisArray.length > 0) {
-              for (var i = 0; i < thisArray.length; i++) {
-                  var item = thisArray[i];
-                  for (var x = 0; x < indentLevel; x++) {
-                      thisOutput += "    ";
-                  }
-                  thisOutput += "* **" + item[0] + "** ";
-                  thisOutput += item[1] + "\n";
-                  if (item[2].length > 0) {
-                      indentLevel++;                      
-                      thisOutput += arrayMD(item[2]);
-                      indentLevel--;
-                  }
-              }
-
-          }
-          return thisOutput;
-      }
-      return arrayMD(mainArray);
-  }  
-  
-  // end RuleBuilder functions
-
-  // Removes all HTML content, replacing line break tags with newlines
-  function stripHTML(input) {
-      input = input.replace(/<br ?\/?>/ig, "\n").replace(/(<([^>]+)>)/ig,'');
-      return input;
-  }
-  
-  // Intercepts the paste event for editable fields and
-  // converts the pasted content to plain text,
-  // stripping styles and unwanted markup added by programs like Word.
-  function handleEditablePaste(event) {
-      try {
-          var pastedText = event.clipboardData
-              ? event.clipboardData.getData("text/plain")
-              : window.clipboardData.getData("Text"); // support IE
-          var cleanedText = cleanPastedText(pastedText);
-          // Pastes the cleaned up text.
-          if (document.queryCommandSupported('insertText')) {
-              document.execCommand('insertText', false, cleanedText);
-          } else { // support IE
-              document.execCommand('paste', false, cleanedText);
-          }
-          event.stopPropagation();
-          event.preventDefault();
-          return false;
-      } catch (err) {
-          // If anything goes wrong with browser compatibility,
-          // pass the event through without modification.
-          return true;
-      }
-  }
-
-  // Removes junk that comes with pasting from text editors like Word.
-  // taken from https://stackoverflow.com/questions/2875027/clean-microsoft-word-pasted-text-using-javascript
-  function cleanPastedText(text) {
-      return text.replace(/.*<!--.*-->/g, "");
-  }
-
-
-  // toggleVisible(id)
-  // Toggles the visibility of a given element by given ID
-  function toggleVisible(id) {
-      var x = document.getElementById(id);
-      if (x.style.display === "none") {
-          x.style.display = "block";
-      } else {
-          x.style.display = "none";
-      }
-  }
-
-  // classDisplayAll(className, value)
-  // Assigns given display value to all elements with a given className
-  function classDisplayAll(className, value) {
-      var elements = document.getElementsByClassName(className);
-      for (var i = 0; i < elements.length; i++) {
-          elements[i].style.display = value;
-      } 
-  }
-
-  // toggleEditMode()
-  // Toggles whether editable fields are editable or not
-  // and removes editing tools.
-  function toggleEditMode() {
-      if (editMode === true) {
-          disableEditMode();
-        } else {
-          enableEditMode();
-      }
-    }
-
-  function disableEditMode() { // switch to preview mode
-        editMode = false;
-
-        document.getElementById("rulebox").classList.add('rulebox-preview');
-        document.getElementById("rulebox").classList.remove('rulebox-edit');
-
-        var editableFields = document.getElementsByClassName("editable");  
-        // de-editable-ize the editable fields
-        for (var i = 0; i < editableFields.length; i++) {
-            editableFields[i].contentEditable = "false";
-            // Remove empty fields entirely
-            var content = editableFields[i].innerHTML;
-            content = stripHTML(content);
-            if (content === "") {
-                // editableFields[i].style.display = "none";
-            }   
-        }
-
-        // RuleBuilder sections
-        if (builderEmpty()) {
-        } else {
-            document.getElementById("builder-field").innerHTML = displayBuilderHTML();
-        }
-        if (document.contains(document.getElementById("custom-field-container"))) {
-            document.getElementById("custom-field-container").remove();
-        }
-
-        // Handle author link
-        var authorName = document.getElementById("author-text").value;
-        var authorURL = document.getElementById("author-url").value;
-        if (authorName != "") {
-            if (authorURL != "") { // both author and URL present                
-                document.getElementById("authorship-result").innerHTML = "<a href='" + authorURL +"'>" + authorName + "</a>";
-            } else { // only authorName present
-                document.getElementById("authorship-result").innerHTML = authorName;
-            }
-            document.getElementById("authorship").style.display = "";
-        } else {
-            document.getElementById("authorship").style.display = "none";
-        }
-        // Finally, change button name
-        document.getElementById("editToggle").innerHTML = "Customize";
-    }
-
-  function enableEditMode() { // Switch to editMode
-        editMode = true;
-
-        document.getElementById("rulebox").classList.remove('rulebox-preview');
-        document.getElementById("rulebox").classList.add('rulebox-edit');
-
-        // RuleBuilder handling
-        document.getElementById("builder-field").innerHTML = "";
-        // make all editable fields visible
-        var editableFields = document.getElementsByClassName("editable");
-        for (var i = 0; i < editableFields.length; i++) {
-            editableFields[i].contentEditable = "true";
-        }
-        // Change button name
-        document.getElementById("editToggle").innerHTML = "Preview";
-    }
-
-  // toggleDisplayMode()
-  // toggles full displayMode, the Rule-only display for a published Rule
-  // first, initialize variable:
-  var displayMode = false;
-  function toggleDisplayMode() {
-      if (displayMode == false) {
-          document.body.classList.add("display_rule");
-          editMode = true;
-          disableEditMode(); // turns off editMode
-          classDisplayAll("site-nav","none");
-          classDisplayAll("post-header","none");
-          classDisplayAll("site-footer","none");
-          document.getElementById("attribution").style.display = "block";
-          document.getElementById("fork").style.display = "inline-block";
-          document.getElementById("discuss-button").style.display = "inline-block";
-          document.getElementById("publishRule").style.display = "none";
-          document.getElementById("trash").style.display = "inline-block";
-          // Turn on RuleWriter if there's content
-          if ("" != document.getElementById("rulewriter").innerHTML) {
-              document.getElementById("rulewriter-box").style.display = "inline-block";
-          }
-          // Finish
-          displayMode = true;
-      } else {
-          document.body.classList.remove("display_rule");
-          enableEditMode() // turns on editMode
-          classDisplayAll("site-nav","block");
-          classDisplayAll("post-header","block");
-          classDisplayAll("site-footer","block");
-          document.getElementById("attribution").style.display = "none";
-          document.getElementById("fork").style.display = "none";
-          document.getElementById("discuss-button").style.display = "none";
-          document.getElementById("publishRule").style.display = "inline-block";
-          document.getElementById("trash").style.display = "none";
-          displayMode = false;
-      }
-      if (document.getElementById("lineage-list").innerHTML != "") {
-          document.getElementById("lineage").style.display = "block";
-      }
-  }
-
-  // textOutput()
-  // Produces Markdown rendition of Rule from Export button
-  function textOutput() {
-      var filename = 'GOVERNANCE.md';
-      // First, add title, whether there is one or not
-      var content = '# '+ document.getElementById('communityname').innerHTML + '\n\n';
-      content = stripHTML(content);
-      // Next, add structure field
-      var structure = document.getElementById('structure').innerHTML;
-      if (structure != "") {
-          content += stripHTML(structure) + '\n\n';
-      }
-      // Add Builder content
-      if (!builderEmpty()) {
-          content += displayBuilderMD() + "\n\n";          
-      }
-      // Now, begin adding Writer elements
-      var elements = document.getElementsByClassName('output');
-      for (var i = 2; i < elements.length; i++) { // start after structure
-          var thisBit = elements[i].innerHTML;
-          thisBit = stripHTML(thisBit);
-          if (thisBit != "") {
-              if (elements[i].classList.contains("subhead")) {
-                  // Before printing subhead, make sure it's not empty
-                  var i2 = i + 1;
-                  while ((i2 < elements.length) &&
-                         (!(elements[i2].classList.contains("subhead")))) {
-                      if (elements[i2].innerHTML != "") {
-                          // in this case, it's not empty, so print and move on
-                          content += '## ';
-                          content += thisBit + '\n\n';
-                          break;
-                      } else { i2++; }
-                  } // won't print anything if a subhead has only empty children
-              } else {
-                  // Non-subhead elements can just go ahead and print
-                  content += thisBit + '\n\n';
-              }
-          }
-      }
-      // Add authorship block
-      var authorName = document.getElementById("author-text").value;
-      var authorURL = document.getElementById("author-url").value;
-      var authorshipBlock = "---\n\nCreated by ";
-      if (authorName != "") {
-          if (authorURL != "") { // both author and URL present                
-              authorshipBlock += ("[" + authorName + "](" + authorURL + ")");
-          } else { // only authorName present
-              authorshipBlock += authorName;
-          }
-          content += (authorshipBlock + "\n");
-      } 
-      // Add attribution block
-      content += document.getElementById('attributionMD').innerHTML;
-      // Starting here, see https://stackoverflow.com/a/33542499
-      var blob = new Blob([content], {type: 'text/plain'});
-      if(window.navigator.msSaveOrOpenBlob) {
-          window.navigator.msSaveBlob(blob, filename);
-      }
-      else{
-          var elem = window.document.createElement('a');
-          elem.href = window.URL.createObjectURL(blob);
-          elem.download = filename;
-          document.body.appendChild(elem);
-          elem.click();
-          document.body.removeChild(elem);
-          URL.revokeObjectURL(); // This needs an arg but I can't figure out what
-      }
-      var myFile = new Blob([fileContent], {type: 'text/plain'});
-      window.URL = window.URL || window.webkitURL; document.getElementById('download').setAttribute('href',window.URL.createObjectURL(myFile));
-      document.getElementById('download').setAttribute('download', fileName);
-  }
-
-  // BEGIN Publish tools, via SteinHQ.com
-
-  // publishRule()
-  // Publishes existing fields to new page, /builder/?rule=[ruleID]
-  // Opens new page in Display mode
-  function publishRule() {
-      // Confirm user knows what they're getting into
-      var r = confirm("Publish to the public Library?");
-      if (r == false) { return; }
-      // Proceed with publication
-      var now = new Date();
-      // Numerical ID for published Rule
-      var timeID = now.getTime();
-      // Readable UTC timestamp
-      var dateTime = now.getUTCFullYear()+'.'+(now.getUTCMonth()+1)+'.'+now.getUTCDate()
-          +' '+now.getUTCHours()+":"+ now.getUTCMinutes()+":"+now.getUTCSeconds()
-          + ' UTC';
-      // Check if ruleID exists; while yes, replace and repeat
-      var rule = [{
-          ruleID: timeID,
-          timestamp: dateTime,
-      }];
-      // begin adding data
-      // first, RuleBuilder data
-      document.getElementById("builder-field").innerHTML = ""; // so it doesn't publish
-      if (!builderEmpty()) {
-          rule[0]["modules"] = document.getElementById("module-input").innerHTML;
-      }
-      // next, RuleWriter data
-      var fields = document.getElementsByClassName("editable");
-      for (var i = 0; i < fields.length; i++) {
-          var key = fields[i].id;
-          var value = "";
-          // including input fields
-          if (fields[i].nodeName == "INPUT") { // for <input>
-              value = fields[i].value.replace(/(<([^>]+)>)/ig,"");
-          } else { // for other fields
-              value = fields[i].innerHTML.replace(/(<([^>]+)>)/ig,"");
-          }
-          rule[0][key] = value;
-      }
-      // add to lineage (if it is a fork)
-      if (rID) {
-          rule[0]["lineage"] = document.getElementById("lineage-list").innerHTML;
-      }
-      // add to database
-      const store = new SteinStore(
-          "https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5"
-      );
-      store.append("library", rule).then(data => {
-          window.open("/create/?r=" + timeID, "_self", false);
-      });
-  }
-
-  // addLineage
-  // Adds the current page to the lineage
-  function addLineage() {
-      var communityname = document.getElementById("communityname").innerHTML;
-      var newLineage = " < " + '<a href="/create/?r=' + rID + '">'
-          + communityname + '</a>';
-      var oldLineage = document.getElementById("lineage-list").innerHTML;
-      document.getElementById("lineage-list").innerHTML = newLineage + oldLineage;
-  }
-
-  // fork()
-  // Forks the current Rule and updates the derivation lineage
-  function fork() {
-      document.getElementById("lineage").style.display = "block";
-      addLineage();
-      toggleDisplayMode();
-  }
-  
-  // displayRule(ID)
-  // Displays content based on ID
-  function displayRule(ID) {
-      const store = new SteinStore(
-          "https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5"
-      );
-      (async () => {
-      var sheets = ["library","templates"];
-      // create empty set of rules (should only get one member)
-      var ruleArray = [];
-      // read values from all sheets
-      for (var i = 0; i < sheets.length; i++) {
-          await store.read(sheets[i], { search: { ruleID: ID } }).then(data => {
-              // test if there's anything in data
-              if (data.length > 0) {
-                  ruleArray = data;
-              }
-          });
-      }
-      // Only runs the rest if the array has something
-      if (ruleArray.length > 0) {
-          var rule = ruleArray[0];
-          var fields = document.getElementsByClassName("editable");
-          for (var i = 0; i < fields.length; i++) {
-              var key = fields[i].id;
-              var value = rule[key];
-              if (typeof value === "undefined") {
-                  value = "";
-              } else if (key.includes("-")) { // links
-                  document.getElementById(key).value = value;
-              } else {
-                  document.getElementById(key).innerHTML = value;
-              }
-          }
-          // Add Builder content
-          document.getElementById("module-input").innerHTML = rule["modules"];
-          // Add lineage
-          var lineage = rule["lineage"];
-          if (typeof lineage === "undefined") {
-              document.getElementById("lineage-list").innerHTML = "";
-          } else {
-              document.getElementById("lineage-list").innerHTML = lineage;
-              document.getElementById("lineage").display = "block";
-          }
-          // Publish timestamp to Rule
-          document.getElementById('dateTime').innerHTML = rule['timestamp'];
-          // Finish
-          displayMode = false;
-          toggleDisplayMode();
-          document.title = rule['communityname'] + " / CommunityRule";
-      }
-          })();
-  }
-
-  // deleteRule()
-  // A temporary placeholder that sends an email requesting rule deletion
-  function deleteRule() {
-      var urlParamz = new URLSearchParams(window.location.search);      
-      var rID = urlParamz.get('r');
-      window.open("mailto:medlab@colorado.edu?subject=Delete Rule request ("
-                  + rID + ")&body=Please explain your rationale:\n");
-  }
-  
-  // END Publish tools
-
-var rID;
-  window.onload = function() {
-  // FINALLY, Page loading
-  // First, grab the current URL
-  var urlParams = new URLSearchParams(window.location.search);
-  // Determine if it is a published Rule
-  if (urlParams.has('r')) {
-      // If so, grab the Rule from database and enter displayMode
-      rID = urlParams.get('r');
-      document.body.classList.add("display_rule");
-      displayRule(rID);
-  } else {
-      // Otherwise, open in editMode as default
-      enableEditMode();
-  }
-  // eqip editable fields to remove formatting from pasted content    
-      var editableElements = document.getElementsByClassName("editable");
-      for (var i = 0; i < editableElements.length; i++ ) {
-          editableElements[i].addEventListener("paste", handleEditablePaste);
-      } 
-  }
-</script>

+ 0 - 161
_layouts/rule.html

@@ -1,161 +0,0 @@
----
-layout: default
-# This is where most of the code for CommunityRule lives
-# Follow comments below in various sections for further explanation
----
-
-{% include rule-scripts.html %}
-
-<article class="post">
-
-  <header class="post-header">
-    <h1 class="post-title" id="title">      
-        {{ page.title }}
-    </h1>
-
-    <button class="pushButton" id="editToggle" onclick="toggleEditMode()">
-      Preview</button>
-
-    <div class="post-content">    
-      {{ content }}
-    </div>
-
-  </header>
-  
-  <div id="rulebox" class="rulebox">
-
-    <span class="prompt">What is the community’s name?</span>
-    <h1 contenteditable="true" class="editable output" id="communityname">{{ page.community-name }}</h1>
-
-    <span class="prompt">Summarize its structure:</span>
-    <p contenteditable="true" class="editable output" id="structure">{{ page.structure }}</p>
-    
-    <!-- RuleBuilder -->
-    <div id="rule-builder" class="rulebuilder">
-      <div id="module-input" class="rulebuilder_input"
-           ondrop="drop(event)" ondragover="allowDrop(event)">
-        <span class="prompt" id="drag-directions">Browse modules from below and drag them here.</span>
-      </div>
-
-      <div id="builder-field" class="rulebuilder_list">
-      </div>
-      
-      <div id="module-menu" class="rulebuilder_modules">
-        
-		    <!-- Load preset modules from _modules/ -->
-        {% assign modules_array = site.modules | sort: "type" %}
-        {% assign last_type = "" %}
-        {% for module in modules_array %}
-        {% if module.layout == "module" %}
-        {% if module.type != last_type %}
-        {% if last_type != "" %}</div>{% endif %} 
-        <div class="module-type-header">
-          <img src="{{ site.baseurl }}/{{ site.data.module_types[module.type].icon }}" />
-        {{ module.type }}
-          <button onclick="toggleVisible('module-type-{{ module.type }}')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button>
-        </div>
-        <p class="module-questions">{{ site.data.module_types[module.type].question }}</p>
-        <div id="module-type-{{ module.type }}" class="module-type" style="display: none;">
-          {% endif %}
-          {% assign last_type = module.type %}
-		      <span class="module" id="module-{{ module.title | slugify }}"
-            draggable="true" ondragstart="drag(event)" ondragend="dragEnd()">
-            <span id="module-name" title="{{ module.summary }}">{{ module.title }}</span>
-            <a target="_blank" href="{{ module.url }}" class="module-info">
-              <img title="More info" draggable="false" class="module-logo"
-                src="{% link assets/tabler_icons/info-circle.svg %}" /></a>
-            <a onclick="this.parentNode.remove()" class="delete-module">
-            <img src="{% link assets/tabler_icons/x.svg %}" /></a>
-          </span>
-        {% endif %}
-        {% endfor %}
-        </div>
-
-        <div class="module-type-header">
-        <img src="{% link assets/tabler_icons/bulb.svg %}" />
-        Custom Module
-        </div>
-        <p class="module-questions">Create your own module.</p>
-        <div id="module-custom-container" class="module-type">
-          <!-- Customizable module -->
-          <span class="module module-custom" id="module-custom"
-                draggable="true" ondragstart="drag(event)" ondragend="dragEnd()">
-            <img class="module-info" src="{% link assets/tabler_icons/grid-dots.svg %}" draggable="false" />
-            <input contenteditable="true" draggable="false" placeholder="Custom module..."/>
-            <img src="{% link assets/tabler_icons/bulb.svg %}"
-                draggable="false" />
-            <a onclick="this.parentNode.remove()" class="delete-module">
-            <img src="{% link assets/tabler_icons/x.svg %}" /></a>
-          </span>
-        </div>
-      </div><!-- END module-menu -->
-    </div><!-- END RuleBuilder -->
-
-    <div id="rulewriter-box">
-      <span class="prompt"></span>
-      <p id="rulewriter" contenteditable="true" class="editable output"></p>
-    </div>
-
-    <div class="metadata_input">
-      <p class="prompt"><img src="{% link assets/tabler_icons/pencil.svg %}" /> Created by</p>
-      <input contenteditable="true" class="editable" id="author-text" placeholder="Creator Name" />
-      
-      <p class="prompt"><img src="{% link assets/tabler_icons/globe.svg %}" /> Creator URL</p>
-      <input contenteditable="true" class="editable" id="author-url" placeholder="Creator URL (http://, https://)" type="url" pattern="http://.*|https://.*" />
-    </div>
-  
-    <div id="authorship" class="linkbox metadata_display">
-      <span>Created by</span>
-      <span class="link-divider"><img src="{% link assets/tabler_icons/pencil.svg %}" title="Add link" /></span>
-      <span id="authorship-result" class="linkbox_result"></span>
-    </div>
-  
-  </div><!--#rulebox-->
-
-  <div id="lineage">
-    <img src="{% link assets/tabler_icons/file.svg %}" title="Derivation lineage" />
-    <span id="lineage-list"></span>
-  </div>
-
-
-  <button class="pushButton" id="publishRule" onclick="publishRule()"
-        title="Add to the public Library">Publish</button>
-  <button class="pushButton" id="fork" onclick="fork()"
-        title="Edit this Rule into a new one">Fork</button>
-  <button class="pushButton" id="discuss-button" onclick="toggleVisible('discuss')"
-        title="Discuss this Rule with others">Discuss</button>
-  <button class="pushButton" onclick="textOutput()"
-        title="Download this Rule as a Markdown text file">Export</button>
-  <button class="pushButton" id="trash" onclick="deleteRule()">
-    <img src="{% link assets/tabler_icons/trash.svg %}" title="Rule deletion request" />
-  </button>
-
-  <div id="discuss" style="display:none;">
-    <button onclick="toggleVisible('discuss')" class="button chevrons" style="display:block;"><img src="{% link assets/tabler_icons/chevrons-up.svg %}" title="Hide" /></button>
-    <p>Comment function in development! Thanks for your patience.</p>
-    <!--
-    <script defer
-            src="https://comment.medlab.host/js/commento.js"
-            data-no-fonts="true"
-            >
-    </script>
-    <div id="commento"></div>
-    -->
-  </div>
-  
-  <div id="attribution" style="display:none;">
-    <br />
-    <p><a href="https://communityrule.info">
-        <img src="https://communityrule.info{% link assets/CommunityRule-derived-000000.svg %}" alt="CommunityRule derived"></a></p>
-    <p id="dateTime"></p>
-    <p>Created with <a href="https://communityrule.info">CommunityRule</a>,
-      <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons BY-SA</a></p>
-    <p><strong>The Publish feature is experimental. Rules may be removed without notice</strong></p>
-  </div>
-  <div id="attributionMD" style="display:none;">
-[![CommunityRule derived](https://communityrule.info{% link assets/CommunityRule-derived-000000.svg %})](https://communityrule.info)   
-[Creative Commons BY-SA](https://creativecommons.org/licenses/by-sa/4.0/)
-</div>
-
-
-</article>

+ 4 - 1
assets/js/vue.rules.js

@@ -489,6 +489,9 @@ const app = Vue.createApp({
           modules: (rule.modules) ? JSON.parse(rule.modules) : []
           modules: (rule.modules) ? JSON.parse(rule.modules) : []
         }
         }
 
 
+        /** Add name to <title> for v3+ rules */
+        document.title = rule.name + " / CommunityRule"
+          
         this.loading = false;
         this.loading = false;
         
         
       })();
       })();
@@ -1047,4 +1050,4 @@ function displayBuilderHTML() {
 function stripHTML(input) {
 function stripHTML(input) {
   input = input.replace(/<br ?\/?>/ig, "\n").replace(/(<([^>]+)>)/ig, '');
   input = input.replace(/<br ?\/?>/ig, "\n").replace(/(<([^>]+)>)/ig, '');
   return input;
   return input;
-}
+}