注释墙

选中内容后添加注释并复制到剪贴板, 同时在本地的服务其中新建一个副本, 参见 https://github.com/ezirmusitua/snippet-board

目前为 2017-04-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name annotation-board
  3. // @name:zh-CN 注释墙
  4. // @description allow you to add annotation after selected content and copy to clipboard and save to local server
  5. // @description:zh-CN 选中内容后添加注释并复制到剪贴板, 同时在本地的服务其中新建一个副本, 参见 https://github.com/ezirmusitua/snippet-board
  6. // @version 0.1.5
  7. // @author jferroal
  8. // @license GPL-3.0
  9. // @include http://*
  10. // @include https://*
  11. // @grant GM_xmlhttpRequest
  12. // @run-at document-start
  13. // @namespace https://greasyfork.org/users/34556
  14. // ==/UserScript==
  15.  
  16. function createSnippet(snippetContent, _host, _port) {
  17. let host = _host;
  18. if (!host) {
  19. host = 'http://127.0.0.1';
  20. }
  21. var port = _port;
  22. if (!port) {
  23. port = 5000;
  24. }
  25. const body = {
  26. link: window.location.href,
  27. raw_content: snippetContent,
  28. }
  29. GM_xmlhttpRequest({
  30. method: "POST",
  31. url: host + ':' + port.toString() + "/snippet/api/v0.1.0",
  32. data: JSON.stringify(body),
  33. headers: { "Content-Type": "application/json" },
  34. onreadystatechange: function (response) {
  35. console.log('trying to create snippet ... ');
  36. },
  37. onload: function (response) {
  38. console.log('snippet created! ');
  39. },
  40. onerror: function (response) {
  41. console.log('something wrong while creating snippet. ');
  42. },
  43. ontimeout: function (response) {
  44. console.log('request timeout! ');
  45. },
  46. onabort: function (response) {
  47. console.log('request aborted. ');
  48. }
  49. });
  50. }
  51.  
  52. class AnnotationBoardStyle {
  53. constructor() { }
  54. static containerStyle(containerStyleObj, position) {
  55. containerStyleObj.fontFamily = 'Noto';
  56. containerStyleObj.display = 'flex';
  57. containerStyleObj.flexDirection = 'column';
  58. containerStyleObj.border = '4px';
  59. containerStyleObj.boxShadow = '0px 3px 8px 1px rgba(0, 0, 0, 0.26)';
  60. containerStyleObj.position = 'absolute';
  61. containerStyleObj.backgroundColor = 'rgba(0, 0, 0, 0.56)';
  62. containerStyleObj.padding = '16px 4px 8px 4px';
  63. }
  64. static textareaStyle(textareaStyleObj) {
  65. textareaStyleObj.fontFamily = 'Noto';
  66. textareaStyleObj.width = '240px';
  67. textareaStyleObj.height = '128px';
  68. textareaStyleObj.backgroundColor = 'rgba(255, 255, 255, 0.87)';
  69. textareaStyleObj.marginBottom = '8px';
  70. textareaStyleObj.borderRadius = '4px';
  71. textareaStyleObj.color = 'rgba(0, 0, 0, 0.76)'
  72. textareaStyleObj.fontSize = '12px';
  73. }
  74. static saveButtonStyle(buttonStyleObj) {
  75. buttonStyleObj.fontFamily = 'Noto';
  76. buttonStyleObj.border = 'none';
  77. buttonStyleObj.borderRadius = '4px';
  78. buttonStyleObj.height = '24px';
  79. buttonStyleObj.backgroundColor = 'rgba(255, 255, 255, 0.87)'
  80. buttonStyleObj.color = 'rgba(0, 0, 0, 0.76)'
  81. buttonStyleObj.fontSize = '14px';
  82. }
  83. }
  84.  
  85. const formatDate = (dateObj) => {
  86. const year = dateObj.getFullYear();
  87. const month = dateObj.getMonth();
  88. const date = dateObj.getDate();
  89. const hour = dateObj.getHours();
  90. const minute = dateObj.getMinutes();
  91. return `Date: ${year}-${month + 1}-${date}[${hour}:${minute > 9 ? minute : '0' + minute}]`;
  92. }
  93.  
  94. const AnnotationBoardId = {
  95. CONTAINER: 'annotation-container',
  96. TEXTAREA: 'annotation-textarea',
  97. BUTTON: 'annotation-button'
  98. };
  99.  
  100. class Selection {
  101. constructor() {
  102. this.createAtStr = formatDate(new Date());
  103. this.selectionText = document.getSelection().toString();
  104. }
  105. concat() {
  106. return this.createAtStr + '\n' + `Content: \n${this.selectionText}`;
  107. }
  108. content(previousContent) {
  109. if (previousContent) {
  110. return previousContent + '\n' + '========' + '\n' + this.concat() + '\n';
  111. } else {
  112. return this.concat() + '\n';
  113. }
  114. }
  115. isEmpty() {
  116. return !this.selectionText;
  117. }
  118. static copyToClipboard(button, textarea) {
  119. document.getSelection().removeAllRanges();
  120. const range = document.createRange();
  121. range.selectNode(textarea);
  122. document.getSelection().addRange(range);
  123. try {
  124. document.execCommand('copy');
  125. } catch (err) {
  126. console.log('Oops, unable to copy');
  127. }
  128. document.getSelection().removeAllRanges();
  129. }
  130. }
  131.  
  132. class Position {
  133. constructor(rect, offset) {
  134. this._left = (rect.left + (offset || 32)) + 'px';
  135. this._top = (rect.top + (offset || 16)) + 'px';
  136. }
  137. top() {
  138. return this._top;
  139. }
  140. left() {
  141. return this._left;
  142. }
  143. }
  144.  
  145. class AnnotationTextArea {
  146. constructor() {
  147. this.container = document.createElement('div');
  148. this.container.id = AnnotationBoardId.CONTAINER;
  149. AnnotationBoardStyle.containerStyle(this.container.style);
  150. this.textarea = document.createElement('textarea');
  151. this.textarea.id = AnnotationBoardId.TEXTAREA;
  152. AnnotationBoardStyle.textareaStyle(this.textarea.style);
  153. this.saveBtn = document.createElement('button');
  154. this.saveBtn.innerHTML = '复制到剪贴板';
  155. this.saveBtn.id = AnnotationBoardId.BUTTON;
  156. AnnotationBoardStyle.saveButtonStyle(this.saveBtn.style);
  157. this.isShowing = false;
  158. }
  159. getPosition() {
  160. const focusNode = document.getSelection().focusNode;
  161. if (!focusNode) throw new Error('no selection, should not create node');
  162. const focusParentElement = focusNode.parentElement;
  163. return new Position(focusParentElement.getBoundingClientRect());
  164. }
  165. updateContainer() {
  166. try {
  167. const pos = this.getPosition();
  168. this.container.style.left = pos.left();
  169. this.container.style.top = pos.top();
  170. } catch (err) {
  171. console.error(err);
  172. }
  173. }
  174. updateSaveButton() {
  175. this.saveBtn.addEventListener('click', (event) => {
  176. Selection.copyToClipboard(this.saveBtn, this.textarea);
  177. createSnippet(this.textarea.value);
  178. this.hide();
  179. })
  180. }
  181. updateTextarea(selection) {
  182. this.textarea.value = selection.content(this.textarea.value);
  183. }
  184. show() {
  185. this.updateTextarea(new Selection());
  186. this.updateContainer();
  187. if (!this.isShowed) {
  188. this.container.appendChild(this.textarea);
  189. this.updateSaveButton();
  190. this.container.appendChild(this.saveBtn);
  191. }
  192. document.body.appendChild(this.container);
  193. this.isShowing = true;
  194. this.isShowed = true;
  195. }
  196. shouldShow() {
  197. return !this.isShowing && !(new Selection()).isEmpty();
  198. }
  199. hide() {
  200. this.isShowing = false;
  201. document.body.removeChild(this.container)
  202. }
  203. shouldHide(event) {
  204. const target = event && event.target;
  205. if (!target && this.iShowing) {
  206. return true;
  207. } else {
  208. const inContainer = event.target.id === AnnotationBoardId.CONTAINER;
  209. const inTextarea = event.target.id === AnnotationBoardId.TEXTAREA;
  210. const inButton = event.target.id === AnnotationBoardId.BUTTON;
  211. return !inContainer && !inTextarea && !inButton && this.isShowing;
  212. }
  213. }
  214. destory() {
  215. this.container.removeChild(this.saveBtn);
  216. this.saveBtn = null;
  217. this.container.removeChild(this.textarea);
  218. this.textarea = null;
  219. document.body.removeChild(this.container);
  220. this.container = null;
  221. }
  222. }
  223.  
  224. class AnnotationBoard {
  225. constructor() {
  226. this.annotationTextArea = new AnnotationTextArea();
  227. }
  228. run() {
  229. this.eventBinding();
  230. }
  231. eventBinding() {
  232. window.addEventListener('mouseup', (event) => {
  233. this.handleMouseUp(event);
  234. }, false);
  235. }
  236. handleMouseUp(event) {
  237. if (this.annotationTextArea.shouldShow()) {
  238. this.annotationTextArea.show();
  239. } else if (this.annotationTextArea.shouldHide(event)) {
  240. this.annotationTextArea.hide();
  241. }
  242. }
  243. }
  244. const annotationBoard = new AnnotationBoard();
  245. annotationBoard.run();