Workflowy computable values

Compute things with sublists and display values on an item name.

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/22313/143441/Workflowy%20computable%20values.js

  1. var parseSelector = window.Parser.parse
  2.  
  3. window.funcs = []
  4. window.nodeCache = {}
  5. var observer
  6.  
  7. // observe dom mutations to make it run every time an item is opened / closed
  8. function start () {
  9. GM_addStyle(`
  10. .project .content {
  11. display: inline-block !important;
  12. padding-right: 27px;
  13. }
  14.  
  15. .project .result {
  16. display: inline;
  17. color: blue;
  18. margin-left: 10px;
  19. }
  20. .project .result:first-child {
  21. margin-left: 0;
  22. }
  23. `)
  24.  
  25.  
  26. document.body.addEventListener('click', function (e) {
  27. if (e.target.id === 'expandButton') {
  28. setTimeout(itemOpenedOrClosed, 1000)
  29. }
  30. })
  31.  
  32. window.addEventListener('hashchange', throttle(itemOpenedOrClosed, 2000))
  33.  
  34. itemOpenedOrClosed()
  35. }
  36.  
  37. function throttle (callback, limit) {
  38. var wait = false
  39. return function () {
  40. if (!wait) {
  41. wait = true
  42. setTimeout(function () {
  43. callback.call()
  44. wait = false
  45. }, limit)
  46. }
  47. }
  48. }
  49.  
  50. function itemOpenedOrClosed () {
  51. for (let i = 0; i < funcs.length; i++) {
  52. let script = funcs[i]
  53. execScript(script)
  54. }
  55. }
  56.  
  57. function execScript ({script, selector, id, color}) {
  58. let items = findItems(selector)
  59. for (let i = 0; i < items.length; i++) {
  60. let item = items[i]
  61. execForItem(item, script, id, color)
  62. }
  63. }
  64.  
  65. function execForItem (item, fun, id, color) {
  66. var children = []
  67. let par = item.querySelector('.children')
  68. for (let p = 0; p < par.children.length; p++) {
  69. if (par.children[p].classList.contains('project')) {
  70. children.push(par.children[p])
  71. }
  72. }
  73.  
  74. var args = []
  75. for (let i = 0; i < children.length; i++) {
  76. let child = children[i]
  77. arg = nodeToArg(child)
  78. args.push(arg)
  79. }
  80.  
  81. var result = item.querySelector('.result.script-' + id)
  82. if (!result) {
  83. let content = item.querySelector('.name .content')
  84. result = document.createElement('span')
  85. result.className = 'result script-' + id
  86. result.title = id
  87. result.style.color = color
  88. content.parentNode.insertBefore(result, content.nextSibling)
  89. }
  90. let resultvalue = fun(args, nodeToArg(item))
  91. result.innerHTML = (resultvalue || '').toString()
  92. }
  93.  
  94. function nodeToArg (node) {
  95. let name = node.querySelector('.name .content').innerText.trim()
  96. let note = node.querySelector('.notes').innerText.trim()
  97.  
  98. var arg = {name, note}
  99.  
  100. let results = node.querySelector('.name').querySelectorAll('.result')
  101. for (let r = 0; r < results.length; r++) {
  102. let result = results[r]
  103. arg[result.classList.item(1)] = result.innerText.trim()
  104. }
  105.  
  106. return arg
  107. }
  108.  
  109. function findItems (selector) {
  110. let parsed = parseSelector(selector)
  111. var items = null
  112. for (let l = 0; l < parsed.length; l++) {
  113. let layer = parsed[l]
  114. items = select(layer, items)
  115. }
  116. return items || []
  117. }
  118.  
  119. function select (layer, base) {
  120. if (!base) {
  121. base = document.querySelectorAll('.mainTreeRoot')
  122. }
  123.  
  124. let [connector, selector] = layer
  125. return getChildren(base, selector, connector === 'directchild')
  126. }
  127.  
  128. function getChildren (base, selector, onlydirect=false) {
  129. var filter
  130. switch (selector.type) {
  131. case 'id':
  132. filter = node =>
  133. node.getAttribute('projectid') === selector.val ||
  134. node.querySelector('.bullet').href.split('#/')[1] === selector.val
  135. break
  136. case 'regex':
  137. filter = node =>
  138. node.querySelector('.name .content').innerText.search(selector.val) !== -1
  139. break
  140. case 'name':
  141. filter = node =>
  142. node.querySelector('.name .content').innerText.trim() === selector.val
  143. break
  144. case 'any':
  145. filter = () => true
  146. break
  147. default:
  148. throw new Error('INVALID SELECTOR: ', selector)
  149. }
  150.  
  151. var children = []
  152. for (let i = 0; i < base.length; i++) {
  153. if (onlydirect) {
  154. let par = base[i].querySelector('.children')
  155. for (let p = 0; p < par.children.length; p++) {
  156. if (par.children[p].classList.contains('project') && par.children[p].classList.contains('open')) {
  157. children.push(par.children[p])
  158. }
  159. }
  160. } else {
  161. let all = base[i].querySelectorAll('.children > .project.open')
  162. for (let i = 0; i < all.length; i++) {
  163. children.push(all[i])
  164. }
  165. }
  166. }
  167.  
  168. return children.filter(filter)
  169. }
  170.  
  171. function registerScript (s) {
  172. funcs.push(s)
  173. }
  174.  
  175. function waitFor (selector, callback) {
  176. let res = document.querySelector(selector)
  177. if (res) return callback()
  178.  
  179. setTimeout(() => {
  180. waitFor(selector, callback)
  181. }, 1000)
  182. }