rule.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. ---
  2. layout: default
  3. # This is where most of the code for CommunityRule lives
  4. # Follow comments below in various sections for further explanation
  5. ---
  6. <script>
  7. // Enter JavaScript-land!
  8. // First, some functions, followed by initialization commands
  9. // Module-related drag-and-drop functions
  10. // source: https://www.codecanal.com/html5-drag-and-copy/
  11. function allowDrop(ev) {
  12. ev.preventDefault();
  13. }
  14. function drag(ev) {
  15. ev.dataTransfer.setData("text", ev.target.id);
  16. }
  17. function drop(ev) {
  18. ev.preventDefault();
  19. var target = ev.target;
  20. // Prevents dropping into text field
  21. if (target.id == "drag-directions") {
  22. target = target.parentElement;
  23. }
  24. // Set up transfer
  25. var data = ev.dataTransfer.getData("text");
  26. // Iff module is from the menu clone it
  27. var module = document.getElementById(data);
  28. if (module.parentElement.id == "module-menu") {
  29. module = module.cloneNode(true);
  30. }
  31. // append name
  32. module.id += "-dropped";
  33. // display the deletion button
  34. module.children[2].style.display = "inline";
  35. // pop it in!
  36. target.appendChild(module);
  37. // be sure the dummy text is gone
  38. document.getElementById("drag-directions").style.display = "none";
  39. // Send field contents to console
  40. // To aid in database stuff
  41. console.log(document.getElementById("module-input"));
  42. }
  43. // toggleVisible(id)
  44. // Toggles the visibility of a given element by given ID
  45. function toggleVisible(id) {
  46. var x = document.getElementById(id);
  47. if (x.style.display === "none") {
  48. x.style.display = "block";
  49. } else {
  50. x.style.display = "none";
  51. }
  52. }
  53. // classDisplayAll(className, value)
  54. // Assigns given display value to all elements with a given className
  55. function classDisplayAll(className, value) {
  56. var elements = document.getElementsByClassName(className);
  57. for (var i = 0; i < elements.length; i++) {
  58. elements[i].style.display = value;
  59. }
  60. }
  61. // toggleEditMode()
  62. // Toggles whether editable fields are editable or not
  63. // and removes editing tools.
  64. function toggleEditMode() {
  65. if (editMode === true) { // switch to preview mode
  66. editMode = false;
  67. classDisplayAll("section","block");
  68. classDisplayAll("button","none");
  69. classDisplayAll("question","none");
  70. var editableFields = document.getElementsByClassName("editable");
  71. // de-editable-ize the editable fields
  72. for (var i = 0; i < editableFields.length; i++) {
  73. editableFields[i].contentEditable = "false";
  74. editableFields[i].style.borderStyle = "none";
  75. // Remove empty fields entirely
  76. var content = editableFields[i].innerHTML;
  77. content = content.replace(/(<([^>]+)>)/ig,''); // strips stray tags
  78. if (content === "") {
  79. editableFields[i].style.display = "none";
  80. }
  81. }
  82. // Remove headers of empty sections
  83. // Inefficient! Might be merged with the above iteration
  84. var sections = document.getElementsByClassName("section");
  85. for (var i = 0; i < sections.length; i++) {
  86. var sectionQuestions = sections[i].getElementsByClassName("editable");
  87. var blanks = 0;
  88. for (var x = 0; x < sectionQuestions.length; x++) {
  89. var content = sectionQuestions[x].innerHTML;
  90. content = content.replace(/(<([^>]+)>)/ig,''); // strips stray tags
  91. if (content == "") { blanks++; }
  92. if (blanks == sectionQuestions.length) {
  93. var headerID = "header-s" + (i + 1);
  94. document.getElementById(headerID).style.display = "none";
  95. }
  96. }
  97. }
  98. // Handle links
  99. // TKTK
  100. // Handle author link
  101. var authorName = document.getElementById("author-text").value;
  102. var authorURL = document.getElementById("author-url").value;
  103. if (authorName != "") {
  104. document.getElementById("authorship-words").style.display = "inline";
  105. if (authorURL != "") { // both author and URL present
  106. document.getElementById("authorship-result").innerHTML = "<a href='" + authorURL +"'>" + authorName + "</a>";
  107. document.getElementById("authorship-result").style.display = "inline";
  108. } else { // only authorName present
  109. document.getElementById("authorship-result").innerHTML = authorName;
  110. document.getElementById("authorship-result").style.display = "inline";
  111. }
  112. } else {
  113. document.getElementById("authorship").style.display = "none";
  114. }
  115. // Finally, change button name
  116. document.getElementById("editToggle").innerHTML = "Customize";
  117. } else { // Switch to editMode
  118. editMode = true;
  119. classDisplayAll("button","block");
  120. classDisplayAll("question","block");
  121. classDisplayAll("editable","block");
  122. classDisplayAll("header","block");
  123. classDisplayAll("section","none");
  124. classDisplayAll("link-text","inline");
  125. classDisplayAll("link-url","inline");
  126. // link handling TKTK
  127. // author handling
  128. document.getElementById("authorship-result").style.display = "none";
  129. document.getElementById("authorship-words").style.display = "none";
  130. document.getElementById("authorship").style.display = "block";
  131. // make all editable fields visible
  132. var editableFields = document.getElementsByClassName("editable");
  133. for (var i = 0; i < editableFields.length; i++) {
  134. editableFields[i].style.borderStyle = "none none dashed none";
  135. editableFields[i].contentEditable = "true";
  136. }
  137. // Change button name
  138. document.getElementById("editToggle").innerHTML = "Preview";
  139. }
  140. }
  141. // toggleDisplayMode()
  142. // toggles full displayMode, the Rule-only display for a published Rule
  143. // first, initialize variable:
  144. var displayMode = false;
  145. function toggleDisplayMode() {
  146. if (displayMode == false) {
  147. editMode = true;
  148. toggleEditMode(); // turns off editMode
  149. classDisplayAll("site-nav","none");
  150. classDisplayAll("post-header","none");
  151. classDisplayAll("site-footer","none");
  152. document.getElementById("attribution").style.display = "block";
  153. document.getElementById("toggleDisplayMode").style.display = "inline-block";
  154. document.getElementById("publishRule").style.display = "none";
  155. document.getElementById("trash").style.display = "inline-block";
  156. displayMode = true;
  157. } else {
  158. toggleEditMode() // turns on editMode
  159. classDisplayAll("site-nav","block");
  160. classDisplayAll("post-header","block");
  161. classDisplayAll("site-footer","block");
  162. document.getElementById("attribution").style.display = "none";
  163. document.getElementById("toggleDisplayMode").style.display = "none";
  164. document.getElementById("publishRule").style.display = "inline-block";
  165. document.getElementById("trash").style.display = "none";
  166. displayMode = false;
  167. }
  168. }
  169. // textOutput()
  170. // Produces Markdown rendition of Rule from Export button
  171. function textOutput() {
  172. var filename = 'GOVERNANCE.md';
  173. // First, add title, whether there is one or not
  174. var content = '# '+ document.getElementById('communityname').innerHTML + '\n\n';
  175. content = content.replace(/(<([^>]+)>)/ig,''); // strips stray tags
  176. // Now, begin adding other elements
  177. var elements = document.getElementsByClassName('output');
  178. for (var i = 1; i < elements.length; i++) {
  179. var thisBit = elements[i].innerHTML;
  180. thisBit = thisBit.replace(/(<([^>]+)>)/ig,''); // strips stray tags
  181. if (thisBit != "") {
  182. if (elements[i].classList.contains("subhead")) {
  183. // Before printing subhead, make sure it's not empty
  184. var i2 = i + 1;
  185. while ((i2 < elements.length) &&
  186. (!(elements[i2].classList.contains("subhead")))) {
  187. if (elements[i2].innerHTML != "") {
  188. // in this case, it's not empty, so print and move on
  189. content += '## ';
  190. content += thisBit + '\n\n';
  191. break;
  192. } else { i2++; }
  193. } // won't print anything if a subhead has only empty children
  194. } else {
  195. // Non-subhead elements can just go ahead and print
  196. content += thisBit + '\n\n';
  197. }
  198. }
  199. }
  200. // Add authorship block
  201. var authorName = document.getElementById("author-text").value;
  202. var authorURL = document.getElementById("author-url").value;
  203. var authorshipBlock = "---\n\nCreated by ";
  204. if (authorName != "") {
  205. if (authorURL != "") { // both author and URL present
  206. authorshipBlock += ("[" + authorName + "](" + authorURL + ")");
  207. } else { // only authorName present
  208. authorshipBlock += authorName;
  209. }
  210. content += (authorshipBlock + "\n");
  211. }
  212. // Add attribution block
  213. content += document.getElementById('attributionMD').innerHTML;
  214. // Starting here, see https://stackoverflow.com/a/33542499
  215. var blob = new Blob([content], {type: 'text/plain'});
  216. if(window.navigator.msSaveOrOpenBlob) {
  217. window.navigator.msSaveBlob(blob, filename);
  218. }
  219. else{
  220. var elem = window.document.createElement('a');
  221. elem.href = window.URL.createObjectURL(blob);
  222. elem.download = filename;
  223. document.body.appendChild(elem);
  224. elem.click();
  225. document.body.removeChild(elem);
  226. URL.revokeObjectURL(); // This needs an arg but I can't figure out what
  227. }
  228. var myFile = new Blob([fileContent], {type: 'text/plain'});
  229. window.URL = window.URL || window.webkitURL; document.getElementById('download').setAttribute('href',window.URL.createObjectURL(myFile));
  230. document.getElementById('download').setAttribute('download', fileName);
  231. }
  232. // BEGIN Publish tools, via SteinHQ.com
  233. // publishRule()
  234. // Publishes existing fields to new page, /create/?rule=[ruleID]
  235. // Opens new page in Display mode
  236. function publishRule() {
  237. // Confirm user knows what they're getting into
  238. var r = confirm("Publish to the public Library?");
  239. if (r == false) { return; }
  240. // Proceed with publication
  241. var now = new Date();
  242. // Numerical ID for published Rule
  243. var timeID = now.getTime();
  244. // Readable UTC timestamp
  245. var dateTime = now.getUTCFullYear()+'.'+(now.getUTCMonth()+1)+'.'+now.getUTCDate()
  246. +' '+now.getUTCHours()+":"+ now.getUTCMinutes()+":"+now.getUTCSeconds()
  247. + ' UTC';
  248. // TKTK: Check if ruleID exists; while yes, replace and repeat
  249. var rule = [{
  250. ruleID: timeID,
  251. timestamp: dateTime,
  252. }];
  253. var fields = document.getElementsByClassName("editable");
  254. for (var i = 0; i < fields.length; i++) {
  255. var key = fields[i].id;
  256. var value = "";
  257. if (fields[i].nodeName == "INPUT") { // for <input>
  258. value = fields[i].value.replace(/(<([^>]+)>)/ig,"");
  259. } else { // for other fields
  260. value = fields[i].innerHTML.replace(/(<([^>]+)>)/ig,"");
  261. }
  262. rule[0][key] = value;
  263. }
  264. const store = new SteinStore(
  265. "https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5"
  266. );
  267. store.append("rules", rule).then(data => {
  268. window.open("/create/?r=" + timeID, "_self", false);
  269. });
  270. }
  271. // displayRule(ID)
  272. // Displays content based on ID
  273. function displayRule(ID) {
  274. const store = new SteinStore(
  275. "https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5"
  276. );
  277. store.read("rules", { search: { ruleID: ID } }).then(data => {
  278. // only runs when we have the data from Goog:
  279. var rule = data[0];
  280. var fields = document.getElementsByClassName("editable");
  281. for (var i = 0; i < fields.length; i++) {
  282. var key = fields[i].id;
  283. var value = rule[key];
  284. if (typeof value === "undefined") {
  285. value = "";
  286. } else if (key.includes("-")) { // links
  287. document.getElementById(key).value = value;
  288. } else {
  289. document.getElementById(key).innerHTML = value;
  290. }
  291. }
  292. // Publish timestamp to Rule
  293. document.getElementById('dateTime').innerHTML = rule['timestamp'];
  294. // Finish
  295. displayMode = false;
  296. toggleDisplayMode();
  297. document.title = rule['communityname'] + " / CommunityRule";
  298. });
  299. }
  300. // deleteRule()
  301. // A temporary placeholder that sends an email requesting rule deletion
  302. function deleteRule() {
  303. var urlParamz = new URLSearchParams(window.location.search);
  304. var rID = urlParamz.get('r');
  305. window.open("mailto:medlab@colorado.edu?subject=Delete Rule request ("
  306. + rID + ")&body=Please explain your rationale:\n");
  307. }
  308. // END Publish tools
  309. // FINALLY, Page loading
  310. // First, grab the current URL
  311. var urlParams = new URLSearchParams(window.location.search);
  312. // Determine if it is a published Rule
  313. if (urlParams.has('r')) {
  314. // If so, grab the Rule from database and enter displayMode
  315. var rID = urlParams.get('r');
  316. displayRule(rID);
  317. } else {
  318. // Otherwise, open in editMode as default
  319. var editMode = true;
  320. // switch out of editMode in special cases
  321. window.onload = function() {
  322. if ((window.location.href.indexOf("/templates/") != -1) ||
  323. (window.location.href.indexOf("/about/") != -1)) {
  324. toggleEditMode();
  325. }
  326. }
  327. }
  328. </script>
  329. <article class="post">
  330. <header class="post-header">
  331. <h1 class="post-title" id="title">
  332. {{ page.title }}
  333. </h1>
  334. <button class="pushButton" id="editToggle" onclick="toggleEditMode()">
  335. Preview</button>
  336. <div class="post-content">
  337. {{ content }}
  338. </div>
  339. </header>
  340. <div id="rulebox">
  341. <span class="question">What is the community’s name?</span>
  342. <h1 contenteditable="true" class="editable output" id="communityname">{{ page.community-name }}</h1>
  343. <p class="question"><strong>RuleBuilder</strong></p>
  344. <!-- Modules -->
  345. <div ondrop="drop(event)" ondragover="allowDrop(event)"
  346. class="modulebox" id="module-input">
  347. <span class="question" id="drag-directions">
  348. Drag modules from the icon at right</span>
  349. <div class="button">
  350. <img src="{% link assets/tabler_icons/tool.svg %}" title="Modules" />
  351. <div class="tooltiptext" id="module-menu">
  352. <!-- Customizable module -->
  353. <span class="module" id="module-custom"
  354. draggable="true" ondragstart="drag(event)">
  355. <input contenteditable="true" placeholder="Custom..." />
  356. <img src="{% link assets/tabler_icons/bulb.svg %}"
  357. draggable="false" />
  358. <a onclick="this.parentNode.remove()" id="delete-module"
  359. style="display:none">
  360. <img src="{% link assets/tabler_icons/x.svg %}" /></a>
  361. </span>
  362. <!-- Load preset modules from _data/modules.csv -->
  363. {% for module in site.data.modules %}
  364. <span class="module fixed" id="module-{{ module.id }}"
  365. draggable="true" ondragstart="drag(event)">
  366. <span>{{ module.name }}</span>
  367. <a target="_blank" href="{{ module.url }}">
  368. <img src="{% link assets/tabler_icons/link.svg %}"
  369. draggable="false" /></a>
  370. <a onclick="this.parentNode.remove()" id="delete-module"
  371. style="display:none">
  372. <img src="{% link assets/tabler_icons/x.svg %}" /></a>
  373. </span>
  374. {% endfor %}
  375. </div>
  376. </div>
  377. </div>
  378. <p class="question"><strong>RuleWriter</strong></p>
  379. <!-- SECTION S1: BASICS -->
  380. <h2 id="header-s1" class="header">
  381. <img src="{% link assets/tabler_icons/info-circle.svg %}"
  382. class="icons" />
  383. <span class="subhead output">Basics</span>
  384. <button onclick="toggleVisible('s1')" class="button plus"><img src="{% link assets/tabler_icons/plus.svg %}" title="Expand" /></button>
  385. </h2>
  386. <div class="section" id="s1" style="display:none">
  387. <span class="question">What is the basic structure of the community?</span>
  388. <p contenteditable="true" class="editable output" id="structure">{{ page.structure }}</p>
  389. <span class="question">What is the community’s mission?</span>
  390. <p contenteditable="true" class="editable output" id="mission">{{ page.mission }}</p>
  391. <span class="question">What core values does the community hold?</span>
  392. <p contenteditable="true" class="editable output" id="values">{{ page.values }}</p>
  393. <span class="question">What is the legal status of the community’s assets and creations?</span>
  394. <p contenteditable="true" class="editable output" id="legal">{{ page.legal }}</p>
  395. </div><!--hiding section s1-->
  396. <!-- SECTION s2: PARTICIPANTS -->
  397. <h2 id="header-s2" class="header">
  398. <img src="{% link assets/tabler_icons/user.svg %}"
  399. class="icons" />
  400. <span class="subhead output">Participants</span>
  401. <button onclick="toggleVisible('s2')" class="button plus"><img src="{% link assets/tabler_icons/plus.svg %}" title="Expand" /></button>
  402. </h2>
  403. <div class="section" id="s2" style="display:none">
  404. <span class="question">How does someone become a participant?</span>
  405. <p contenteditable="true" class="editable output" id="membership">{{ page.membership }}</p>
  406. <span class="question">How are participants suspended or removed?</span>
  407. <p contenteditable="true" class="editable output" id="removal">{{ page.removal }}</p>
  408. <span class="question">What special roles can participants hold, and how are roles assigned?</span>
  409. <p contenteditable="true" class="editable output" id="roles">{{ page.roles }}</p>
  410. <span class="question">Are there limits on the terms or powers of participant roles?</span>
  411. <p contenteditable="true" class="editable output" id="limits">{{ page.limits }}</p>
  412. </div><!--hiding section s2-->
  413. <!--SECTION s3: POLICY-->
  414. <h2 id="header-s3" class="header">
  415. <img src="{% link assets/tabler_icons/news.svg %}"
  416. class="icons" />
  417. <span class="subhead output">Policy</span>
  418. <button onclick="toggleVisible('s3')" class="button plus"><img src="{% link assets/tabler_icons/plus.svg %}" title="Expand" /></button>
  419. </h2>
  420. <div class="section" id="s3" style="display:none">
  421. <span class="question">What basic rights does this Rule guarantee?</span>
  422. <p contenteditable="true" class="editable output" id="rights">{{ page.rights }}</p>
  423. <span class="question">Who has the capacity to decide on policies, and how do they do so?</span>
  424. <p contenteditable="true" class="editable output" id="decision">{{ page.decision }}</p>
  425. <span class="question">How are policies implemented?</span>
  426. <p contenteditable="true" class="editable output" id="implementation">{{ page.implementation }}</p>
  427. <span class="question">How does the community monitor and evaluate its policy implementation? </span>
  428. <p contenteditable="true" class="editable output" id="oversight">{{ page.oversight }}</p>
  429. </div><!--hiding section s3-->
  430. <!-- SECTION s4: PROCESS -->
  431. <h2 id="header-s4" class="header">
  432. <img src="{% link assets/tabler_icons/refresh.svg %}"
  433. class="icons" />
  434. <span class="subhead output">Process</span>
  435. <button onclick="toggleVisible('s4')" class="button plus"><img src="{% link assets/tabler_icons/plus.svg %}" title="Expand" /></button>
  436. </h2>
  437. <div class="section" id="s4" style="display:none">
  438. <span class="question">Where does the community deliberate about policies and governance?</span>
  439. <p contenteditable="true" class="editable output" id="deliberation">{{ page.deliberation }}</p>
  440. <span class="question">How does the community manage access to administrative accounts and other tools?</span>
  441. <p contenteditable="true" class="editable output" id="access">{{ page.access }}</p>
  442. <span class="question">How does the community manage funds and economic flows?</span>
  443. <p contenteditable="true" class="editable output" id="economics">{{ page.economics }}</p>
  444. <span class="question">How are grievances among participants addressed?</span>
  445. <p contenteditable="true" class="editable output" id="grievances">{{ page.grievances }}</p>
  446. </div><!--hiding section s4-->
  447. <!-- SECTION s5: EVOLUTION -->
  448. <h2 id="header-s5" class="header">
  449. <img src="{% link assets/tabler_icons/adjustments.svg %}"
  450. class="icons" />
  451. <span class="subhead output">Evolution</span>
  452. <button onclick="toggleVisible('s5')" class="button plus"><img src="{% link assets/tabler_icons/plus.svg %}" title="Expand" /></button>
  453. </h2>
  454. <div class="section" id="s5" style="display:none">
  455. <span class="question">Where are policies and records kept?</span>
  456. <p contenteditable="true" class="editable output" id="records">{{ page.records }}</p>
  457. <span class="question">How can this Rule be modified?</span>
  458. <p contenteditable="true" class="editable output" id="modification">{{ page.modification }}</p>
  459. </div><!--hiding section s5-->
  460. <div id="authorship" class="linkbox">
  461. <span id="authorship-words">Created by</span>
  462. <input contenteditable="true" class="editable link-text" id="author-text" placeholder="Created by" />
  463. <span class="link-divider"><img src="{% link assets/tabler_icons/pencil.svg %}" title="Add link" /></span>
  464. <input contenteditable="true" class="editable link-url" id="author-url" placeholder="Creator URL (http://, https://)" type="url" pattern="http://.*|https://.*" />
  465. <span id="authorship-result"></span>
  466. </div>
  467. </div><!--#rulebox-->
  468. <div id="attribution" style="display:none;">
  469. <br />
  470. <p><a href="https://communityrule.info">
  471. <img src="https://communityrule.info{% link assets/CommunityRule-derived-000000.svg %}" alt="CommunityRule derived"></a></p>
  472. <p id="dateTime"></p>
  473. <p>Created with <a href="https://communityrule.info">CommunityRule</a><br />
  474. <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons BY-SA</a></p>
  475. <p><strong>The Publish feature is experimental. Rules may be removed without notice</strong></p>
  476. </div>
  477. <div id="attributionMD" style="display:none;">
  478. ---
  479. [![CommunityRule derived](https://communityrule.info{% link assets/CommunityRule-derived-000000.svg %})](https://communityrule.info)
  480. [Creative Commons BY-SA](https://creativecommons.org/licenses/by-sa/4.0/)</div>
  481. <button class="pushButton" id="publishRule" onclick="publishRule()"
  482. title="Add to the public Library">Publish</button>
  483. <button class="pushButton" id="toggleDisplayMode" onclick="toggleDisplayMode()"
  484. title="Edit this Rule into a new one">Fork</button>
  485. <button class="pushButton" onclick="textOutput()"
  486. title="Download this Rule as a Markdown text file">Export</button>
  487. <button class="pushButton" id="trash" onclick="deleteRule()">
  488. <img src="{% link assets/tabler_icons/trash.svg %}" title="Rule deletion request" />
  489. </button>
  490. <button class="pushButton"
  491. onclick="javascript:location.href='https://www.colorado.edu/lab/medlab/content/communityrule-user-feedback'">
  492. Feedback
  493. </button>
  494. </article>