--- layout: default # This is where most of the code for CommunityRule lives # Follow comments below in various sections for further explanation --- <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); } function drop(ev) { ev.preventDefault(); var target = ev.target; // First, confirm target location is valid function targetCheck () { 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 = 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.onClick = "moduleEditField(this.parentNode.id)"; customText.id = "module-name"; customText.append(name); module.prepend(customText); } // append id with unique timestamp var nowModule = new Date(); module.id += "-" + nowModule.getTime(); } // display the deletion button module.children[2].style.display = "inline"; // pop it in! target.appendChild(module); // set up the editing 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 = "Explain how the <strong>" + moduleName + "</strong> module works."; var destination = document.getElementById("builder-field"); if (moduleName == null) { moduleName = ""; } var output = '\n<div id="custom-field-container">'; output += '<span class="question">' + 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); } 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 } return arrayHTML(mainArray); } // end RuleBuilder functions // Removes all HTML content function stripHTML(input) { input = input.replace(/(<([^>]+)>)/ig,''); return input; } // 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) { // switch to preview mode editMode = false; classDisplayAll("section","block"); classDisplayAll("button","none"); classDisplayAll("question","none"); classDisplayAll("metaheader","none"); classDisplayAll("delete-module","none"); var editableFields = document.getElementsByClassName("editable"); // de-editable-ize the editable fields for (var i = 0; i < editableFields.length; i++) { editableFields[i].contentEditable = "false"; editableFields[i].style.borderStyle = "none"; // Remove empty fields entirely var content = editableFields[i].innerHTML; content = stripHTML(content); if (content === "") { editableFields[i].style.display = "none"; } } // RuleBuilder sections if (builderEmpty()) { document.getElementById("rule-builder").style.display = "none"; } if (document.contains(document.getElementById("custom-field-container"))) { document.getElementById("custom-field-container").remove(); } document.getElementById("module-menu").style.display = "none"; document.getElementById("builder-field").innerHTML = displayBuilderHTML(); // RuleWriter: Remove headers of empty sections var sections = document.getElementsByClassName("section"); for (var i = 0; i < sections.length; i++) { var sectionQuestions = sections[i].getElementsByClassName("editable"); var blanks = 0; for (var x = 0; x < sectionQuestions.length; x++) { var content = sectionQuestions[x].innerHTML; content = content.replace(/(<([^>]+)>)/ig,''); // strips stray tags if (content == "") { blanks++; } if (blanks == sectionQuestions.length) { var headerID = "header-s" + (i + 1); document.getElementById(headerID).style.display = "none"; } } } // Handle links // TKTK // Handle author link var authorName = document.getElementById("author-text").value; var authorURL = document.getElementById("author-url").value; if (authorName != "") { document.getElementById("authorship-words").style.display = "inline"; if (authorURL != "") { // both author and URL present document.getElementById("authorship-result").innerHTML = "<a href='" + authorURL +"'>" + authorName + "</a>"; document.getElementById("authorship-result").style.display = "inline"; } else { // only authorName present document.getElementById("authorship-result").innerHTML = authorName; document.getElementById("authorship-result").style.display = "inline"; } } else { document.getElementById("authorship").style.display = "none"; } // Finally, change button name document.getElementById("editToggle").innerHTML = "Customize"; } else { // Switch to editMode editMode = true; classDisplayAll("button","block"); classDisplayAll("question","block"); classDisplayAll("editable","block"); classDisplayAll("header","block"); classDisplayAll("section","none"); classDisplayAll("metaheader","block"); classDisplayAll("link-text","inline"); classDisplayAll("link-url","inline"); classDisplayAll("delete-module","inline"); // builder handling document.getElementById("rule-builder").style.display = "block"; document.getElementById("builder-field").innerHTML = ""; // author handling document.getElementById("authorship-result").style.display = "none"; document.getElementById("authorship-words").style.display = "none"; document.getElementById("authorship").style.display = "block"; // make all editable fields visible var editableFields = document.getElementsByClassName("editable"); for (var i = 0; i < editableFields.length; i++) { editableFields[i].style.borderStyle = "none none dashed none"; 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) { editMode = true; toggleEditMode(); // turns off editMode classDisplayAll("site-nav","none"); classDisplayAll("post-header","none"); classDisplayAll("site-footer","none"); document.getElementById("attribution").style.display = "block"; document.getElementById("toggleDisplayMode").style.display = "inline-block"; document.getElementById("publishRule").style.display = "none"; document.getElementById("trash").style.display = "inline-block"; displayMode = true; } else { toggleEditMode() // turns on editMode classDisplayAll("site-nav","block"); classDisplayAll("post-header","block"); classDisplayAll("site-footer","block"); document.getElementById("attribution").style.display = "none"; document.getElementById("toggleDisplayMode").style.display = "none"; document.getElementById("publishRule").style.display = "inline-block"; document.getElementById("trash").style.display = "none"; displayMode = false; } } // 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 = content.replace(/(<([^>]+)>)/ig,''); // strips stray tags // Now, begin adding other elements var elements = document.getElementsByClassName('output'); for (var i = 1; i < elements.length; i++) { var thisBit = elements[i].innerHTML; thisBit = thisBit.replace(/(<([^>]+)>)/ig,''); // strips stray tags 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'; // TKTK: Check if ruleID exists; while yes, replace and repeat var rule = [{ ruleID: timeID, timestamp: dateTime, }]; var fields = document.getElementsByClassName("editable"); for (var i = 0; i < fields.length; i++) { var key = fields[i].id; var value = ""; 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; } const store = new SteinStore( "https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5" ); store.append("rules", rule).then(data => { window.open("/create/?r=" + timeID, "_self", false); }); } // displayRule(ID) // Displays content based on ID function displayRule(ID) { const store = new SteinStore( "https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5" ); store.read("rules", { search: { ruleID: ID } }).then(data => { // only runs when we have the data from Goog: var rule = data[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; } } // 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 // 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 var rID = urlParams.get('r'); displayRule(rID); } else { // Otherwise, open in editMode as default var editMode = true; // switch out of editMode in special cases window.onload = function() { if ((window.location.href.indexOf("/templates/") != -1) || (window.location.href.indexOf("/about/") != -1)) { toggleEditMode(); } } } </script> <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"> <span class="question">What is the community’s name?</span> <h1 contenteditable="true" class="editable output" id="communityname">{{ page.community-name }}</h1> <!-- BUILDER --> <h2 id="header-rb" class="metaheader"> <span class="subhead output">RuleBuilder</span> <button onclick="toggleVisible('rule-builder')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div id="rule-builder"> <button id="module-toggle" onclick="toggleVisible('module-menu')" class="button" title="Show/hide"> <img src="{% link assets/tabler_icons/tool.svg %}" title="Modules" /> </button> <div id="module-input" ondrop="drop(event)" ondragover="allowDrop(event)"> <span class="question" id="drag-directions"> </span> </div> <div id="builder-field"> </div> <div id="module-menu" style="display:none;"> <!-- Customizable module --> <span class="module" id="module-custom" draggable="true" ondragstart="drag(event)"> <input contenteditable="true" placeholder="Custom..."/> <img src="{% link assets/tabler_icons/bulb.svg %}" draggable="false" /> <a onclick="this.parentNode.remove()" class="delete-module" style="display:none"> <img src="{% link assets/tabler_icons/x.svg %}" /></a> </span> <!-- Load preset modules from _data/modules.csv --> {% for module in site.data.modules %} <span class="module" id="module-{{ module.id }}" draggable="true" ondragstart="drag(event)"> <span id="module-name" onclick="moduleEditField(this.parentNode.id)">{{ module.name }}</span> <a target="_blank" href="{{ module.url }}" class="module-link"> <img title="{{ module.type }}" draggable="false" {% if module.type == "structure" %} src="{% link assets/tabler_icons/building.svg %}" {% endif %} {% if module.type == "process" %} src="{% link assets/tabler_icons/rotate.svg %}" {% endif %} {% if module.type == "decision" %} src="{% link assets/tabler_icons/thumb-up.svg %}" {% endif %} {% if module.type == "culture" %} src="{% link assets/tabler_icons/palette.svg %}" {% endif %} /></a> <a onclick="this.parentNode.remove()" class="delete-module" style="display:none"> <img src="{% link assets/tabler_icons/x.svg %}" /></a> </span> {% endfor %} </div> </div> <h2 id="header-rw" class="metaheader"> <span class="subhead output">RuleWriter</span> <button onclick="toggleVisible('rule-writer')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div id="rule-writer"> <!-- SECTION S1: BASICS --> <h2 id="header-s1" class="header"> <img src="{% link assets/tabler_icons/info-circle.svg %}" class="icons" /> <span class="subhead output">Basics</span> <button onclick="toggleVisible('s1')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div class="section" id="s1" style="display:none"> <span class="question">What is the basic structure of the community?</span> <p contenteditable="true" class="editable output" id="structure">{{ page.structure }}</p> <span class="question">What is the community’s mission?</span> <p contenteditable="true" class="editable output" id="mission">{{ page.mission }}</p> <span class="question">What core values does the community hold?</span> <p contenteditable="true" class="editable output" id="values">{{ page.values }}</p> <span class="question">What is the legal status of the community’s assets and creations?</span> <p contenteditable="true" class="editable output" id="legal">{{ page.legal }}</p> </div><!--hiding section s1--> <!-- SECTION s2: PARTICIPANTS --> <h2 id="header-s2" class="header"> <img src="{% link assets/tabler_icons/user.svg %}" class="icons" /> <span class="subhead output">Participants</span> <button onclick="toggleVisible('s2')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div class="section" id="s2" style="display:none"> <span class="question">How does someone become a participant?</span> <p contenteditable="true" class="editable output" id="membership">{{ page.membership }}</p> <span class="question">How are participants suspended or removed?</span> <p contenteditable="true" class="editable output" id="removal">{{ page.removal }}</p> <span class="question">What special roles can participants hold, and how are roles assigned?</span> <p contenteditable="true" class="editable output" id="roles">{{ page.roles }}</p> <span class="question">Are there limits on the terms or powers of participant roles?</span> <p contenteditable="true" class="editable output" id="limits">{{ page.limits }}</p> </div><!--hiding section s2--> <!--SECTION s3: POLICY--> <h2 id="header-s3" class="header"> <img src="{% link assets/tabler_icons/news.svg %}" class="icons" /> <span class="subhead output">Policy</span> <button onclick="toggleVisible('s3')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div class="section" id="s3" style="display:none"> <span class="question">What basic rights does this Rule guarantee?</span> <p contenteditable="true" class="editable output" id="rights">{{ page.rights }}</p> <span class="question">Who has the capacity to decide on policies, and how do they do so?</span> <p contenteditable="true" class="editable output" id="decision">{{ page.decision }}</p> <span class="question">How are policies implemented?</span> <p contenteditable="true" class="editable output" id="implementation">{{ page.implementation }}</p> <span class="question">How does the community monitor and evaluate its policy implementation? </span> <p contenteditable="true" class="editable output" id="oversight">{{ page.oversight }}</p> </div><!--hiding section s3--> <!-- SECTION s4: PROCESS --> <h2 id="header-s4" class="header"> <img src="{% link assets/tabler_icons/refresh.svg %}" class="icons" /> <span class="subhead output">Process</span> <button onclick="toggleVisible('s4')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div class="section" id="s4" style="display:none"> <span class="question">Where does the community deliberate about policies and governance?</span> <p contenteditable="true" class="editable output" id="deliberation">{{ page.deliberation }}</p> <span class="question">How does the community manage access to administrative accounts and other tools?</span> <p contenteditable="true" class="editable output" id="access">{{ page.access }}</p> <span class="question">How does the community manage funds and economic flows?</span> <p contenteditable="true" class="editable output" id="economics">{{ page.economics }}</p> <span class="question">How are grievances among participants addressed?</span> <p contenteditable="true" class="editable output" id="grievances">{{ page.grievances }}</p> </div><!--hiding section s4--> <!-- SECTION s5: EVOLUTION --> <h2 id="header-s5" class="header"> <img src="{% link assets/tabler_icons/adjustments.svg %}" class="icons" /> <span class="subhead output">Evolution</span> <button onclick="toggleVisible('s5')" class="button chevrons"><img src="{% link assets/tabler_icons/chevrons-down.svg %}" title="Show/hide" /></button> </h2> <div class="section" id="s5" style="display:none"> <span class="question">Where are policies and records kept?</span> <p contenteditable="true" class="editable output" id="records">{{ page.records }}</p> <span class="question">How can this Rule be modified?</span> <p contenteditable="true" class="editable output" id="modification">{{ page.modification }}</p> </div><!--hiding section s5--> </div><!--end: rule-writer--> <div id="authorship" class="linkbox"> <span id="authorship-words">Created by</span> <input contenteditable="true" class="editable link-text" id="author-text" placeholder="Created by" /> <span class="link-divider"><img src="{% link assets/tabler_icons/pencil.svg %}" title="Add link" /></span> <input contenteditable="true" class="editable link-url" id="author-url" placeholder="Creator URL (http://, https://)" type="url" pattern="http://.*|https://.*" /> <span id="authorship-result"></span> </div> </div><!--#rulebox--> <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><br /> <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;"> --- [](https://communityrule.info) [Creative Commons BY-SA](https://creativecommons.org/licenses/by-sa/4.0/)</div> <button class="pushButton" id="publishRule" onclick="publishRule()" title="Add to the public Library">Publish</button> <button class="pushButton" id="toggleDisplayMode" onclick="toggleDisplayMode()" title="Edit this Rule into a new one">Fork</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> <button class="pushButton" onclick="javascript:location.href='https://www.colorado.edu/lab/medlab/content/communityrule-user-feedback'"> Feedback </button> <div class="question">Publish and Export are not yet available for the RuleBuilder</div> </article>