Notion.so v3 Trash Cleaner

Provides a pop up where you can select a workspace in Notion.so to clear its trash

目前为 2019-08-09 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Notion.so v3 Trash Cleaner
  3. // @namespace https://github.com/bjxpen
  4. // @version 0.1
  5. // @description Provides a pop up where you can select a workspace in Notion.so to clear its trash
  6. // @author Jiaxing Peng
  7. // @license MIT
  8. // @match *://www.notion.so/*
  9. // @require https://cdn.jsdelivr.net/npm/redom@3.24.0/dist/redom.min.js
  10. // @require https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js
  11. // @run-at document-idle
  12. // @grant GM_xmlhttpRequest
  13. // ==/UserScript==
  14.  
  15. /*jshint esversion: 6 */
  16.  
  17. class Component {
  18. setState(state) {
  19. if (this.state === undefined) {
  20. this.state = {}
  21. }
  22. Object.assign(this.state, state)
  23. this.update()
  24. }
  25.  
  26. update() {}
  27. }
  28.  
  29. class Menu extends Component {
  30. constructor() {
  31. super()
  32. this.state = {
  33. msg: ""
  34. }
  35. this.render()
  36. this.fetchSpaces()
  37. }
  38.  
  39. fetchSpaces() {
  40. this.setState({
  41. fetchSpaceState: "fetching"
  42. })
  43. postJSON('api/v3/loadUserContent')
  44. .catch(err => {
  45. console.log(err)
  46. this.setState({
  47. fetchSpaceState: "error"
  48. })
  49. })
  50. .then(res => [... function*() {
  51. const spaceView = res.recordMap.space_view
  52. for (let _ in spaceView) {
  53. yield res.recordMap.space[spaceView[_].value.space_id].value
  54. }
  55. }()])
  56. .then(spaces => {
  57. this.spaces = spaces
  58. this.setState({
  59. fetchSpaceState: "fetched"
  60. })
  61. })
  62. }
  63.  
  64. render() {
  65. this.el = redom.el("div#_del-trash-menu")
  66. }
  67.  
  68. setMsg(msg) {
  69. setTimeout(() => this.setState({
  70. msg
  71. }), 0)
  72. }
  73.  
  74. update() {
  75. const msg = (() => {
  76. if (this.state.fetchSpaceState === "fetched" && this.state.msg !== "") {
  77. return this.state.msg
  78. }
  79. switch (this.state.fetchSpaceState) {
  80. case "fetching":
  81. return "Fetching workspace metadata..."
  82. case "fetched":
  83. return "Choose workspace to delete:"
  84. case "error":
  85. return "Network error: Failed fetching workspace data"
  86. default:
  87. return this.state.msg
  88. }
  89. })()
  90.  
  91. redom.setChildren(this.el, [
  92. redom.el("div", "(Turn off this script to close the pop up)"),
  93. redom.el("pre", msg),
  94. this.state.fetchSpaceState === "fetched" &&
  95. redom.el("ul", this.spaces.map(space => new Space({
  96. space,
  97. setMsg: this.setMsg.bind(this)
  98. }))),
  99. ]);
  100. }
  101. }
  102.  
  103. class Space extends Component {
  104. constructor({
  105. space,
  106. setMsg
  107. }) {
  108. super()
  109. this.space = space
  110. this.setMsg = setMsg
  111. this.render()
  112. }
  113.  
  114. render() {
  115. this.el = redom.el("li", this.space.name)
  116. this.el.addEventListener("click", this.onClick.bind(this))
  117. }
  118.  
  119. onClick(ev) {
  120. let deletedPostCount = 0
  121.  
  122. const recurDel = () => {
  123. this.setMsg(`Workspace "${this.space.name}":\nDeleting posts (done: ${deletedPostCount}) (takes a while) ...`)
  124. postJSON("api/v3/searchTrashPages", {
  125. query: "",
  126. limit: 20,
  127. spaceId: this.space.id
  128. })
  129. .catch(err => {
  130. console.log(err)
  131. this.setMsg(`Workspace "${this.space.name}":\nNetwork error: Failed fetching trash posts`)
  132. })
  133. .then(res => res.results)
  134. .then(pageIds => {
  135. if (pageIds.length > 0) {
  136. postJSON("api/v3/deleteBlocks", {
  137. blockIds: pageIds,
  138. permanentlyDelete: true
  139. })
  140. .catch(err => {
  141. console.log(err)
  142. this.setMsg(`Workspace "${this.space.name}":\nNetwork error: Failed deleting posts`)
  143. })
  144. .then(_ => {
  145. deletedPostCount += pageIds.length
  146. recurDel()
  147. })
  148. } else {
  149. this.setMsg(`Workspace "${this.space.name}":\nTrash is cleared`)
  150. }
  151. })
  152. }
  153.  
  154. recurDel()
  155. }
  156. }
  157.  
  158. function loadScript(url) {
  159. return new Promise((res, rej) => {
  160. const script = document.createElement("script")
  161. document.body.appendChild(script)
  162. script.addEventListener("load", (ev) => {
  163. res(url)
  164. })
  165. script.src = url
  166. })
  167. }
  168.  
  169. function loadCSS(css) {
  170. const elm = document.createElement("style")
  171. elm.innerHTML = css
  172. document.body.appendChild(elm)
  173. }
  174.  
  175. function postJSON(url, jsonPayload = null) {
  176. const config = {
  177. method: "POST"
  178. }
  179. if (jsonPayload) {
  180. Object.assign(config, {
  181. headers: {
  182. "Content-Type": "application/json"
  183. },
  184. body: JSON.stringify(jsonPayload)
  185. })
  186. }
  187. return axios.post(url, jsonPayload).then(res => res.data)
  188. }
  189.  
  190. Promise.all([
  191. window.redom || loadScript("https://cdn.jsdelivr.net/npm/redom@3.24.0/dist/redom.min.js"),
  192. window.axios || loadScript("https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js")
  193. ])
  194. .then(() => {
  195. loadCSS(`
  196. #_del-trash-menu {
  197. position: absolute;
  198. color: rgba(55, 53, 47, 0.6);
  199. background: rgb(247, 246, 243);
  200. padding: 1em;
  201. top: 0;
  202. left: calc(50% - 160px);
  203. width: 320px;
  204. min-height: 200px;
  205. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
  206. font-size: 14px;
  207. z-index: 9999;
  208. }
  209. #_del-trash-menu ul, #_del-trash-menu li {
  210. color: black;
  211. list-style: none;
  212. margin: 0;
  213. padding: 0;
  214. }
  215. #_del-trash-menu li {
  216. margin: 12px 0;
  217. padding: 6px;
  218. }
  219. #_del-trash-menu li:hover {
  220. cursor: pointer;
  221. background: white;
  222. }
  223. `)
  224. document.body.appendChild(new Menu().el)
  225. })