- // ==UserScript==
- // @name annotation-board
- // @name:zh-CN 注释墙
- // @description allow you to add annotation after selected content and copy to clipboard and save to local server
- // @description:zh-CN 选中内容后添加注释并复制到剪贴板, 同时在本地的服务其中新建一个副本, 参见 https://github.com/ezirmusitua/snippet-board
- // @version 0.1.5
- // @author jferroal
- // @license GPL-3.0
- // @include http://*
- // @include https://*
- // @grant GM_xmlhttpRequest
- // @run-at document-start
- // @namespace https://greasyfork.org/users/34556
- // ==/UserScript==
-
- function createSnippet(snippetContent, _host, _port) {
- let host = _host;
- if (!host) {
- host = 'http://127.0.0.1';
- }
- var port = _port;
- if (!port) {
- port = 5000;
- }
- const body = {
- link: window.location.href,
- raw_content: snippetContent,
- }
- GM_xmlhttpRequest({
- method: "POST",
- url: host + ':' + port.toString() + "/snippet/api/v0.1.0",
- data: JSON.stringify(body),
- headers: { "Content-Type": "application/json" },
- onreadystatechange: function (response) {
- console.log('trying to create snippet ... ');
- },
- onload: function (response) {
- console.log('snippet created! ');
- },
- onerror: function (response) {
- console.log('something wrong while creating snippet. ');
- },
- ontimeout: function (response) {
- console.log('request timeout! ');
- },
- onabort: function (response) {
- console.log('request aborted. ');
- }
- });
- }
-
- class AnnotationBoardStyle {
- constructor() { }
- static containerStyle(containerStyleObj, position) {
- containerStyleObj.fontFamily = 'Noto';
- containerStyleObj.display = 'flex';
- containerStyleObj.flexDirection = 'column';
- containerStyleObj.border = '4px';
- containerStyleObj.boxShadow = '0px 3px 8px 1px rgba(0, 0, 0, 0.26)';
- containerStyleObj.position = 'absolute';
- containerStyleObj.backgroundColor = 'rgba(0, 0, 0, 0.56)';
- containerStyleObj.padding = '16px 4px 8px 4px';
- }
- static textareaStyle(textareaStyleObj) {
- textareaStyleObj.fontFamily = 'Noto';
- textareaStyleObj.width = '240px';
- textareaStyleObj.height = '128px';
- textareaStyleObj.backgroundColor = 'rgba(255, 255, 255, 0.87)';
- textareaStyleObj.marginBottom = '8px';
- textareaStyleObj.borderRadius = '4px';
- textareaStyleObj.color = 'rgba(0, 0, 0, 0.76)'
- textareaStyleObj.fontSize = '12px';
- }
- static saveButtonStyle(buttonStyleObj) {
- buttonStyleObj.fontFamily = 'Noto';
- buttonStyleObj.border = 'none';
- buttonStyleObj.borderRadius = '4px';
- buttonStyleObj.height = '24px';
- buttonStyleObj.backgroundColor = 'rgba(255, 255, 255, 0.87)'
- buttonStyleObj.color = 'rgba(0, 0, 0, 0.76)'
- buttonStyleObj.fontSize = '14px';
- }
- }
-
- const formatDate = (dateObj) => {
- const year = dateObj.getFullYear();
- const month = dateObj.getMonth();
- const date = dateObj.getDate();
- const hour = dateObj.getHours();
- const minute = dateObj.getMinutes();
- return `Date: ${year}-${month + 1}-${date}[${hour}:${minute > 9 ? minute : '0' + minute}]`;
- }
-
- const AnnotationBoardId = {
- CONTAINER: 'annotation-container',
- TEXTAREA: 'annotation-textarea',
- BUTTON: 'annotation-button'
- };
-
- class Selection {
- constructor() {
- this.createAtStr = formatDate(new Date());
- this.selectionText = document.getSelection().toString();
- }
- concat() {
- return this.createAtStr + '\n' + `Content: \n${this.selectionText}`;
- }
- content(previousContent) {
- if (previousContent) {
- return previousContent + '\n' + '========' + '\n' + this.concat() + '\n';
- } else {
- return this.concat() + '\n';
- }
- }
- isEmpty() {
- return !this.selectionText;
- }
- static copyToClipboard(button, textarea) {
- document.getSelection().removeAllRanges();
- const range = document.createRange();
- range.selectNode(textarea);
- document.getSelection().addRange(range);
- try {
- document.execCommand('copy');
- } catch (err) {
- console.log('Oops, unable to copy');
- }
- document.getSelection().removeAllRanges();
- }
- }
-
- class Position {
- constructor(rect, offset) {
- this._left = (rect.left + (offset || 32)) + 'px';
- this._top = (rect.top + (offset || 16)) + 'px';
- }
- top() {
- return this._top;
- }
- left() {
- return this._left;
- }
- }
-
- class AnnotationTextArea {
- constructor() {
- this.container = document.createElement('div');
- this.container.id = AnnotationBoardId.CONTAINER;
- AnnotationBoardStyle.containerStyle(this.container.style);
- this.textarea = document.createElement('textarea');
- this.textarea.id = AnnotationBoardId.TEXTAREA;
- AnnotationBoardStyle.textareaStyle(this.textarea.style);
- this.saveBtn = document.createElement('button');
- this.saveBtn.innerHTML = '复制到剪贴板';
- this.saveBtn.id = AnnotationBoardId.BUTTON;
- AnnotationBoardStyle.saveButtonStyle(this.saveBtn.style);
- this.isShowing = false;
- }
- getPosition() {
- const focusNode = document.getSelection().focusNode;
- if (!focusNode) throw new Error('no selection, should not create node');
- const focusParentElement = focusNode.parentElement;
- return new Position(focusParentElement.getBoundingClientRect());
- }
- updateContainer() {
- try {
- const pos = this.getPosition();
- this.container.style.left = pos.left();
- this.container.style.top = pos.top();
- } catch (err) {
- console.error(err);
- }
- }
- updateSaveButton() {
- this.saveBtn.addEventListener('click', (event) => {
- Selection.copyToClipboard(this.saveBtn, this.textarea);
- createSnippet(this.textarea.value);
- this.hide();
- })
- }
- updateTextarea(selection) {
- this.textarea.value = selection.content(this.textarea.value);
- }
- show() {
- this.updateTextarea(new Selection());
- this.updateContainer();
- if (!this.isShowed) {
- this.container.appendChild(this.textarea);
- this.updateSaveButton();
- this.container.appendChild(this.saveBtn);
- }
- document.body.appendChild(this.container);
- this.isShowing = true;
- this.isShowed = true;
- }
- shouldShow() {
- return !this.isShowing && !(new Selection()).isEmpty();
- }
- hide() {
- this.isShowing = false;
- document.body.removeChild(this.container)
- }
- shouldHide(event) {
- const target = event && event.target;
- if (!target && this.iShowing) {
- return true;
- } else {
- const inContainer = event.target.id === AnnotationBoardId.CONTAINER;
- const inTextarea = event.target.id === AnnotationBoardId.TEXTAREA;
- const inButton = event.target.id === AnnotationBoardId.BUTTON;
- return !inContainer && !inTextarea && !inButton && this.isShowing;
- }
- }
- destory() {
- this.container.removeChild(this.saveBtn);
- this.saveBtn = null;
- this.container.removeChild(this.textarea);
- this.textarea = null;
- document.body.removeChild(this.container);
- this.container = null;
- }
- }
-
- class AnnotationBoard {
- constructor() {
- this.annotationTextArea = new AnnotationTextArea();
- }
- run() {
- this.eventBinding();
- }
- eventBinding() {
- window.addEventListener('mouseup', (event) => {
- this.handleMouseUp(event);
- }, false);
- }
- handleMouseUp(event) {
- if (this.annotationTextArea.shouldShow()) {
- this.annotationTextArea.show();
- } else if (this.annotationTextArea.shouldHide(event)) {
- this.annotationTextArea.hide();
- }
- }
- }
- const annotationBoard = new AnnotationBoard();
- annotationBoard.run();