├── zip └── .gitkeep ├── browser ├── main.css ├── build │ └── .keep ├── icon.png ├── options.html ├── index.html ├── background.html ├── manifest.json └── content_scripts_css │ └── scrapbox-io │ └── main.css ├── .node-version ├── .babelrc ├── zip-firefox.sh ├── materials ├── a.png ├── b.png ├── rel.png ├── small.png ├── text.png └── beaver.png ├── src ├── browser.js ├── index.js ├── gyazo-com │ ├── manage.js │ └── text-bubble.js └── scrapbox-io │ ├── icon-button.js │ ├── manage.js │ ├── paste-webpage-url.js │ ├── rel-cards-bubble.js │ └── text-bubble.b.js ├── client ├── option.js ├── gyazo-com.js ├── scrapbox-io.js └── background.js ├── .gitignore ├── README.md └── package.json /zip/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /browser/main.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /browser/build/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16.15.1 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } -------------------------------------------------------------------------------- /zip-firefox.sh: -------------------------------------------------------------------------------- 1 | cd ./browser 2 | zip -r -FS ../zip/firefox.zip * 3 | cd ../ 4 | -------------------------------------------------------------------------------- /browser/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/browser/icon.png -------------------------------------------------------------------------------- /materials/a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/materials/a.png -------------------------------------------------------------------------------- /materials/b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/materials/b.png -------------------------------------------------------------------------------- /materials/rel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/materials/rel.png -------------------------------------------------------------------------------- /materials/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/materials/small.png -------------------------------------------------------------------------------- /materials/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/materials/text.png -------------------------------------------------------------------------------- /materials/beaver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiiz/ScrapScripts/HEAD/materials/beaver.png -------------------------------------------------------------------------------- /browser/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Options 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | 2 | exports.isChrome = () => { 3 | return /Chrome/.test(navigator.userAgent) 4 | } 5 | 6 | exports.isFirefox = () => { 7 | return /Firefox/.test(navigator.userAgent) 8 | } 9 | -------------------------------------------------------------------------------- /browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ChromeScripts 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/option.js: -------------------------------------------------------------------------------- 1 | document.querySelector('#btn-main-project').addEventListener('click', function () { 2 | var p = document.querySelector('#main-project').value; 3 | if (p && p.length > 0) localStorage['main-project'] = p; 4 | }, false); 5 | -------------------------------------------------------------------------------- /browser/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/gyazo-com.js: -------------------------------------------------------------------------------- 1 | window.app = (/Chrome/.test(navigator.userAgent)) ? chrome : browser 2 | 3 | $(function () { 4 | window.daiizGyazo.manage.install() 5 | .then(projectName => { 6 | window.daiizGyazo.textBubble.enable(projectName) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /client/scrapbox-io.js: -------------------------------------------------------------------------------- 1 | window.app = (/Chrome/.test(navigator.userAgent)) ? chrome : browser 2 | 3 | $(function () { 4 | window.daiizScrapbox.manage.install() 5 | window.daiizScrapbox.iconButton.enable() 6 | window.daiizScrapbox.relCardsBubble.enable() 7 | window.daiizScrapbox.textBubble.enable() 8 | window.daiizScrapbox.pasteWebpageUrl.enable() 9 | }) 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | window.$ = require('jquery') 2 | 3 | window.daiizGyazo = { 4 | manage: require('./gyazo-com/manage'), 5 | textBubble: require('./gyazo-com/text-bubble') 6 | } 7 | 8 | window.daiizScrapbox = { 9 | manage: require('./scrapbox-io/manage'), 10 | relCardsBubble: require('./scrapbox-io/rel-cards-bubble'), 11 | textBubble: require('./scrapbox-io/text-bubble.b'), 12 | iconButton: require('./scrapbox-io/icon-button'), 13 | pasteWebpageUrl: require('./scrapbox-io/paste-webpage-url'), 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | zip/* 3 | !zip/.gitkeep 4 | browser/build/*.js 5 | test/build/* 6 | node_modules/* 7 | *.crx 8 | *.pem 9 | *.zip 10 | *.min.* 11 | ### https://raw.github.com/github/gitignore/7e04a3f05c5b913b5767ca88fc61734494ca97e2/Global/OSX.gitignore 12 | 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | 17 | # Icon must end with two \r 18 | Icon 19 | 20 | 21 | # Thumbnails 22 | ._* 23 | 24 | # Files that might appear in the root of a volume 25 | .DocumentRevisions-V100 26 | .fseventsd 27 | .Spotlight-V100 28 | .TemporaryItems 29 | .Trashes 30 | .VolumeIcon.icns 31 | 32 | # Directories potentially created on remote AFP share 33 | .AppleDB 34 | .AppleDesktop 35 | Network Trash Folder 36 | Temporary Items 37 | .apdisk 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/gyazo-com/manage.js: -------------------------------------------------------------------------------- 1 | // Gyazo 2 | let ROOT_PROJECT_NAME = null 3 | const DAIIZ_GYAZO_TEXT_BUBBLE = 'daiiz-gyazo-text-bubble' 4 | 5 | exports.detectProject = function () { 6 | return ROOT_PROJECT_NAME 7 | } 8 | 9 | exports.install = () => { 10 | return new Promise(resolve => { 11 | window.app.runtime.sendMessage({ 12 | command: 'get-project-name', 13 | func_names: ['daiiz-gyazo-text-bubble'] 14 | }, function (projectNames) { 15 | console.info('ScrapScripts', projectNames) 16 | if (projectNames[DAIIZ_GYAZO_TEXT_BUBBLE]) { 17 | ROOT_PROJECT_NAME = projectNames[DAIIZ_GYAZO_TEXT_BUBBLE] 18 | // daiizGyazoTextBubbleMain($appRoot, ROOT_PROJECT_NAME) 19 | resolve(ROOT_PROJECT_NAME) 20 | } 21 | }) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScrapScripts 2 | 3 | Unofficial browser extension for [Scrapbox](https://scrapbox.io). 4 | 5 | ## Install from extension store 6 | ### Chrome Extension 7 | - [ScrapScripts for Chrome](https://chrome.google.com/webstore/detail/scrapscripts/pmpjhaeadhebhjmninnnpikcdogmjgok) 8 | 9 | ### Firefox Add-on 10 | - [ScrapScripts for Firefox](https://addons.mozilla.org/ja/firefox/addon/scrap-scripts/) 11 | 12 | ## Clone from GitHub 13 | 1. Clone this repository 14 | 2. Run below 15 | ``` 16 | $ cd ScrapScripts/ 17 | $ npm install 18 | $ npm run build 19 | ``` 20 | 3. Launch Chrome and open chrome://extensions/, then load the unpackaged extension: `browser/` 21 | 22 | ## Features 23 | - Bubbles 24 | - Show related 2-hop-link cards 25 | - Preview for link destination page 26 | - IconButton 27 | - Integration with Gyazo 28 | - Pasting URLs in the pageLink notation `[URL title]` (beta) 29 | 30 | ## Settings 31 | - https://scrapbox.io/daiiz/ScrapScripts#5b4a2b1dadf4e70000e8f31e 32 | 33 | ## Author 34 | - Daiki Iizuka 35 | 36 | ## License 37 | MIT 38 | 39 | ## Disclaimer 40 | This is not an official browser extension for Scrapbox. 41 | -------------------------------------------------------------------------------- /src/scrapbox-io/icon-button.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery') 2 | const daiizScrapboxManage = require('./manage') 3 | const installed = daiizScrapboxManage.installed 4 | const detectProject = daiizScrapboxManage.detectProject 5 | 6 | exports.enable = () => { 7 | var $appRoot = $('#app-container') 8 | 9 | $appRoot.on('click', 'img.icon', e => { 10 | if (!installed('daiiz-icon-button')) return 11 | var projectName = detectProject() 12 | 13 | var $t = $(e.target).closest('img.icon') 14 | var src = $t.attr('src') 15 | var iconProject = src.split('/api/pages/')[1].split('/')[0] 16 | if (iconProject !== projectName) return 17 | 18 | // 自分のプロジェクトの管理下のscriptだけ実行できる 19 | var iconName = $t.attr('title').match(/[^\s/]+-button$/g) 20 | if (iconName) { 21 | const scriptFilePath = `${location.origin}/api/code/${projectName}/${iconName[0]}/button.js` 22 | const xhr = new XMLHttpRequest() 23 | xhr.open('GET', scriptFilePath) 24 | xhr.onload = function () { 25 | const {response} = this 26 | eval(response) 27 | } 28 | xhr.send() 29 | 30 | return false 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrap-script", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:daiz713/ScrapScripts.git", 6 | "author": "daiiz ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "run-s build:**", 10 | "build:babel": "babel client/ --out-dir browser/build/ --minified --source-maps false", 11 | "build:browserify": "browserify -t [ babelify ] src/index.js -o browser/build/bundle.js -v", 12 | "watch": "run-p watch:**", 13 | "watch:babel": "npm run build:babel -- --watch", 14 | "watch:browserify": "watchify -t [ babelify ] src/index.js -o browser/build/bundle.js -v", 15 | "zip": "run-s zip:**", 16 | "zip:chrome": "zip -r -FS ./zip/chrome.zip browser/", 17 | "zip:firefox": "sh zip-firefox.sh", 18 | "test": "run-s test:**", 19 | "test:standard": "standard --fix src/*.js src/**/*.js" 20 | }, 21 | "dependencies": { 22 | "babel-cli": "^6.24.1", 23 | "babel-plugin-transform-runtime": "^6.23.0", 24 | "babel-preset-es2015": "^6.24.1", 25 | "babelify": "^7.3.0", 26 | "jquery": "^3.3.1", 27 | "npm-run-all": "^4.0.2", 28 | "watchify": "^3.9.0" 29 | }, 30 | "devDependencies": { 31 | "ava": "^0.20.0", 32 | "standard": "^10.0.2" 33 | }, 34 | "standard": { 35 | "global": [ 36 | "chrome", 37 | "window", 38 | "document", 39 | "$" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /browser/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "page": "background.html" 4 | }, 5 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; connect-src * data: blob:;", 6 | "options_ui": { 7 | "page": "options.html" 8 | }, 9 | "description": "Unofficial browser extension for Scrapbox", 10 | "icons": { 11 | "128": "icon.png", 12 | "48" : "icon.png", 13 | "16" : "icon.png" 14 | }, 15 | "manifest_version": 2, 16 | "name": "ScrapScripts", 17 | 18 | "content_scripts": [ 19 | { 20 | "matches": [""], 21 | "js": [ 22 | "build/bundle.js" 23 | ], 24 | "run_at": "document_start" 25 | }, 26 | { 27 | "matches": ["*://scrapbox.io/*", "http://localhost/*"], 28 | "css": [ 29 | "content_scripts_css/scrapbox-io/main.css" 30 | ], 31 | "js": [ 32 | "build/scrapbox-io.js" 33 | ], 34 | "run_at": "document_end" 35 | }, 36 | { 37 | "matches": ["*://gyazo.com/*"], 38 | "css": [ 39 | "content_scripts_css/scrapbox-io/main.css" 40 | ], 41 | "js": [ 42 | "build/gyazo-com.js" 43 | ], 44 | "run_at": "document_end" 45 | } 46 | ], 47 | 48 | "permissions": [ 49 | "contextMenus", 50 | "tabs", 51 | "activeTab", 52 | "clipboardRead", 53 | "" 54 | ], 55 | 56 | "version": "1.2.2" 57 | } 58 | -------------------------------------------------------------------------------- /browser/content_scripts_css/scrapbox-io/main.css: -------------------------------------------------------------------------------- 1 | #daiiz-rel-cards-bubble, .daiiz-text-bubble { 2 | background-color: #fff; 3 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 4 | 0 3px 1px -2px rgba(0,0,0,.2), 5 | 0 1px 5px 0 rgba(0,0,0,.12); 6 | position: absolute; 7 | display: none; 8 | padding: 10px; 9 | box-sizing: content-box; 10 | z-index: 10000; 11 | } 12 | 13 | .daiiz-cards { 14 | height: 100%; 15 | overflow-x: auto; 16 | overflow-y: hidden; 17 | white-space: nowrap; 18 | } 19 | 20 | .daiiz-cards li { 21 | float: none !important; 22 | } 23 | 24 | .daiiz-cards li .title { 25 | float: left; 26 | } 27 | 28 | #daiiz-rel-cards-bubble .page-list-item { 29 | display: inline-block; 30 | white-space: normal; 31 | margin: 0 10px 10px 0; 32 | } 33 | 34 | .daiiz-text-bubble { 35 | color: #565656; 36 | padding: 5px; 37 | font-size: 9pt; 38 | cursor: default; 39 | border: 1px solid #fff; 40 | position: absolute; 41 | } 42 | 43 | .daiiz-text-bubble .empty-page-link { 44 | color: #ed8362; 45 | } 46 | 47 | .daiiz-backquote { 48 | background-color: rgba(0,0,0,0.04); 49 | border-radius: 2px; 50 | box-sizing: border-box; 51 | padding: 1px; 52 | font-size: 8.5pt; 53 | } 54 | 55 | .daiiz-external-project { 56 | background-color: #fafafa; 57 | } 58 | 59 | .daiiz-line-permalink { 60 | background-color: #fff277; 61 | } 62 | 63 | .daiz-underline { 64 | text-decoration: underline; 65 | } 66 | 67 | .daiiz-tiny-icon { 68 | height: 11pt; 69 | } 70 | 71 | .daiiz-small-img { 72 | max-height: 40px; 73 | } 74 | 75 | .daiiz-ref-link { 76 | text-decoration: underline; 77 | } 78 | 79 | .daiiz-ref-link img { 80 | border-style: none none solid; 81 | border-width: 1px; 82 | border-color: #7ba1f8; 83 | } 84 | 85 | #daiiz-ctrlv { 86 | position: fixed; 87 | bottom: 0; 88 | left: 0; 89 | visibility: hidden; 90 | height: 0; 91 | } 92 | -------------------------------------------------------------------------------- /src/scrapbox-io/manage.js: -------------------------------------------------------------------------------- 1 | // Scrapbox 2 | const $ = require('jquery') 3 | const ESC_KEY_CODE = 27 4 | const DAIIZ_GYAZO_TEXT_BUBBLE = 'daiiz-gyazo-text-bubble' 5 | 6 | // XXX: 直したい 7 | exports.installed = function (functionName) { 8 | var d = `data-${functionName}` 9 | var defaulfValue = { 10 | 'daiiz-text-bubble': 's', // South 11 | 'daiiz-rel-bubble': 'n', // North 12 | 'daiiz-icon-button': true, 13 | 'daiiz-paste-url-title': 'ctrl' 14 | } 15 | if ($('body').attr(d)) { 16 | d = $('body').attr(d) 17 | if (d === 'off') return false 18 | if (d === 'on') { 19 | return defaulfValue[functionName] 20 | } else if (d.length >= 1) { 21 | // n, s 22 | // ctrl 23 | return d 24 | } 25 | } 26 | return false 27 | } 28 | 29 | exports.detectProject = function () { 30 | const r = window.location.href.match(/scrapbox\.io\/([^/.]*)/) 31 | || window.location.href.match(/localhost\:\d+\/([^/.]*)/) 32 | if (r && r.length >= 2) return encodeURIComponent(r[1]) 33 | return 'daiiz' 34 | } 35 | 36 | var enableDaiizScript = function (pairs) { 37 | window.app.runtime.sendMessage({ 38 | command: 'enable-daiiz-script', 39 | func_project_pairs: pairs 40 | }) 41 | } 42 | 43 | exports.install = () => { 44 | var mo = new window.MutationObserver(function (mutationRecords) { 45 | var pairs = {} 46 | for (var i = 0; i < mutationRecords.length; i++) { 47 | var record = mutationRecords[i] 48 | var attr = record.attributeName 49 | if (attr.startsWith('data-daiiz-')) { 50 | var projectName = $('body').attr(attr) 51 | var funcName = attr.replace(/^data-/, '') 52 | if (funcName === DAIIZ_GYAZO_TEXT_BUBBLE) { 53 | pairs[funcName] = projectName 54 | } 55 | } 56 | } 57 | if (Object.keys(pairs).length > 0) enableDaiizScript(pairs) 58 | }) 59 | mo.observe($('body')[0], { 60 | attributes: true 61 | }) 62 | 63 | $('body').on('keydown', function (e) { 64 | if (e.keyCode === ESC_KEY_CODE) $('.daiiz-card-root').remove() 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/gyazo-com/text-bubble.js: -------------------------------------------------------------------------------- 1 | // Gyazo 2 | var $ = require('jquery') 3 | var textBubble = require('../scrapbox-io/text-bubble.b.js') 4 | // var daiizGyazoManage = require('./manage') 5 | 6 | var daiizGyazoDescLink = function ($appRoot, projectName) { 7 | // Gyazoの写真の説明文の [] をリンク化する 8 | $appRoot.on('mouseover', '.image-desc-display', function (e) { 9 | var $t = $(e.target).closest('.image-desc-display') 10 | var desc = $t[0].innerHTML 11 | var keywords = desc.match(/\[[^[\]]+\]/gi) 12 | if (!keywords) return 13 | 14 | for (var i = 0; i < keywords.length; i++) { 15 | var keyword = keywords[i].replace('[', '').replace(']', '') 16 | var projectPage = `/${projectName}/${keyword}` 17 | desc = desc.replace(keywords[i], `${keyword}`) 19 | } 20 | $t.html(desc) 21 | }) 22 | } 23 | 24 | var daiizGyazoTextBubbleInit = function ($appRoot, targetSelector, projectName) { 25 | var timer = null 26 | $appRoot.on('mouseenter', targetSelector, function (e) { 27 | var $a = $(e.target).closest(targetSelector) 28 | $a.attr('title', '') 29 | var $parentBubble = $(e.target).closest('div.daiiz-text-bubble') 30 | 31 | var $bubble = textBubble.$getTextBubble() 32 | var rect = $a[0].getBoundingClientRect() 33 | $bubble.css({ 34 | 'max-width': $('.container-editbox')[0].offsetWidth - $a[0].offsetLeft, 35 | 'left': rect.left + window.pageXOffset, 36 | 'top': rect.top + window.pageYOffset + $a[0].offsetHeight + 3, 37 | 'border-color': '#5f616a' 38 | }) 39 | var pos = `${$bubble.css('top')}_${$bubble.css('left')}` 40 | $bubble.attr('data-pos', pos) 41 | 42 | // すでに表示されているならば,何もしない 43 | if ($(`.daiiz-text-bubble[data-pos="${pos}"]`).length > 0) { 44 | return 45 | } 46 | if ($a.attr('rel') && $a.attr('rel') === 'route') { 47 | $(`.daiiz-text-bubble:not([data-pos="${pos}"])`).remove() 48 | } 49 | var tag = $a[0].innerText 50 | 51 | timer = window.setTimeout(function () { 52 | if ($parentBubble.length > 0) projectName = $parentBubble.attr('data-project') 53 | textBubble.$getRefTextBody(tag.trim(), $appRoot, $bubble, projectName) 54 | }, 650) 55 | }) 56 | 57 | $appRoot.on('mouseleave', targetSelector, function (e) { 58 | window.clearTimeout(timer) 59 | }) 60 | 61 | $appRoot.on('mouseleave', '.daiiz-card', function (e) { 62 | // var $bubble = $('.daiiz-card') 63 | window.clearTimeout(timer) 64 | }) 65 | 66 | $appRoot.on('click', function (e) { 67 | window.clearTimeout(timer) 68 | var $bubble = $('.daiiz-card') 69 | var $t = $(e.target).closest('.daiiz-card') 70 | if ($(e.target)[0].tagName.toLowerCase() === 'a') { 71 | $bubble.remove() 72 | } else if ($t.length > 0) { 73 | $t.remove() 74 | } else { 75 | $bubble.remove() 76 | } 77 | }) 78 | } 79 | 80 | exports.enable = (projectName) => { 81 | var $appRoot = $('body') 82 | daiizGyazoTextBubbleInit($appRoot, 'a.hashtag', projectName) 83 | daiizGyazoTextBubbleInit($appRoot, 'a.page-link', projectName) 84 | daiizGyazoDescLink($appRoot, projectName) 85 | } 86 | -------------------------------------------------------------------------------- /src/scrapbox-io/paste-webpage-url.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery') 2 | const {isChrome, isFirefox} = require('../browser') 3 | const daiizScrapboxManage = require('./manage') 4 | const {installed} = daiizScrapboxManage 5 | 6 | const keys = { 7 | ctrl: 17, 8 | alt: 18, 9 | v: 86 10 | } 11 | 12 | const execPasteChrome = () => { 13 | // background scriptに処理を依頼する 14 | window.app.runtime.sendMessage({ 15 | command: 'get-clipboard-page' 16 | }, text => { 17 | if (!text) return 18 | insertTextToScrapboxCursor(text) 19 | }) 20 | } 21 | 22 | const execPasteFirefox = async () => { 23 | // background scriptに処理を依頼できない 24 | // 拡張機能用のtextareaを生成してbody末尾に挿入 25 | let textarea = document.querySelector('#daiiz-ctrlv') 26 | if (!textarea) { 27 | textarea = document.createElement('textarea') 28 | textarea.setAttribute('id', 'daiiz-ctrlv') 29 | document.body.appendChild(textarea) 30 | } 31 | 32 | // clipboardが保持する内容を流し込んで値を取得 33 | let rawText 34 | const onPaste = event => {rawText = event.clipboardData.getData('text/plain')} 35 | textarea.value = '' 36 | textarea.focus() 37 | document.addEventListener('paste', onPaste, false) 38 | document.execCommand('paste') 39 | document.removeEventListener('paste', onPaste, false) 40 | textarea.remove() 41 | // fetch APIの実行してtextを解決する処理はbackground scriptに依頼する 42 | window.app.runtime.sendMessage({ 43 | command: 'fetch-page-title', 44 | rawText 45 | }, text => { 46 | if (!text) return 47 | insertTextToScrapboxCursor(text) 48 | }) 49 | } 50 | 51 | const insertTextToScrapboxCursor = text => { 52 | // Scrapboxで入力を待ち受けているtextarea要素 53 | const textInput = document.querySelector('#text-input') 54 | if (isChrome()) { 55 | textInput.focus() 56 | document.execCommand('insertText', false, text) 57 | } else { 58 | // Firefoxでは document.execCommand('insertText') が使えない 59 | // 代わりに自前で生成したUIEventを発行すればいい 60 | // https://www.everythingfrontend.com/posts/insert-text-into-textarea-at-cursor-position.html 61 | const start = textInput.selectionStart // in this case maybe 0 62 | textInput.setRangeText(text) 63 | textInput.selectionStart = textInput.selectionEnd = start + text.length 64 | const uiEvent = document.createEvent('UIEvent') 65 | uiEvent.initEvent('input', true, false) 66 | textInput.dispatchEvent(uiEvent) 67 | } 68 | } 69 | 70 | exports.enable = () => { 71 | let c = 0 72 | 73 | $(window).on('keydown', event => { 74 | const key = installed('daiiz-paste-url-title') 75 | if (!key) return 76 | const { keyCode } = event 77 | if (keyCode === keys[key]) c = 1 78 | }) 79 | 80 | $(window).on('keydown', event => { 81 | if (!installed('daiiz-paste-url-title')) return 82 | const { keyCode } = event 83 | if (keyCode !== keys.v || c !== 1) { 84 | return 85 | } 86 | event.preventDefault() 87 | event.stopPropagation() 88 | 89 | if (isChrome()) { 90 | execPasteChrome() 91 | } else if (isFirefox()) { 92 | execPasteFirefox() 93 | } 94 | }) 95 | 96 | $(window).on('keyup', () => {c = 0}) 97 | 98 | window.app.runtime.onMessage.addListener((request, sender, sendResponse) => { 99 | const {command, externalLink} = request 100 | 101 | if (command === 're:get-clipboard-page') { 102 | insertTextToScrapboxCursor(externalLink) 103 | } 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /client/background.js: -------------------------------------------------------------------------------- 1 | // background 2 | 3 | const isChrome = () => { 4 | return /Chrome/.test(navigator.userAgent) 5 | } 6 | 7 | window.app = isChrome() ? chrome : browser 8 | 9 | window.app.runtime.onMessage.addListener(function (request, sender, sendResponse) { 10 | var cmd = request.command; 11 | 12 | // 外部サイトで発動する機能を有効にする 13 | if (cmd === 'enable-daiiz-script') { 14 | var funcProjectPairs = request.func_project_pairs; 15 | 16 | var funcNames = Object.keys(funcProjectPairs); 17 | for (var i = 0; i < funcNames.length; i++) { 18 | var funcName = funcNames[i]; 19 | var projectName = funcProjectPairs[funcName]; 20 | 21 | if (!funcName || funcName.length === 0) return; 22 | if (!projectName || projectName.length === 0) { 23 | localStorage.removeItem(funcName); 24 | }else if (projectName.length > 0) { 25 | localStorage[funcName] = projectName; 26 | } 27 | } 28 | return; 29 | } 30 | 31 | // 設定された値を返す 32 | if (cmd === 'get-project-name') { 33 | var funcNames = request.func_names; 34 | var projectNames = {}; 35 | for (var i = 0; i < funcNames.length; i++) { 36 | var funcName = funcNames[i]; 37 | if (localStorage[funcName]) { 38 | projectNames[funcName] = localStorage[funcName]; 39 | } 40 | } 41 | sendResponse(projectNames); 42 | return; 43 | } 44 | 45 | // Clipboardに保持されたURLのページタイトルを返却する 46 | if (cmd === 'get-clipboard-page') { 47 | const bg = window.app.extension.getBackgroundPage() 48 | const textarea = document.querySelector('#daiiz-ctrlv') 49 | textarea.value = '' 50 | textarea.focus() 51 | bg.document.execCommand('paste') 52 | resopondWebpageTitleOrRawText(textarea.value, sendResponse) 53 | } 54 | 55 | // URLのページタイトルを返却する 56 | if (cmd === 'fetch-page-title') { 57 | const text = request.rawText 58 | resopondWebpageTitleOrRawText(text, sendResponse) 59 | } 60 | }) 61 | 62 | const resopondWebpageTitleOrRawText = (text, sendResponse) => { 63 | if (text.match(/\n/)) return sendResponse(text) 64 | if (text.match(/^https?:\/\/scrapbox\.io\//)) return sendResponse(text) 65 | if (text.match(/gyazo\.com\//)) return sendResponse(text) 66 | if (text.match(/www\.youtube\.com\//)) return sendResponse(text) 67 | if (text.match(/www\.google/) && text.match(/\/maps\//)) return sendResponse(text) 68 | if (text.match(/^https?:\/\//)) { 69 | fetchPage(text) 70 | return 71 | } 72 | sendResponse(text) 73 | } 74 | 75 | const fetchPage = async (url) => { 76 | const res = await fetch(url, { 77 | credentials: 'include' 78 | }) 79 | const body = await res.text() 80 | const parser = new DOMParser() 81 | const doc = parser.parseFromString(body, 'text/html') 82 | console.log(doc.title) 83 | 84 | let externalLink = url 85 | const title = doc.title || null 86 | if (title) externalLink = `[${url} ${title}]` 87 | 88 | console.log(externalLink) 89 | 90 | if (isChrome()) { 91 | window.app.tabs.getSelected(null, tab => { 92 | window.app.tabs.sendMessage(tab.id, { 93 | command: 're:get-clipboard-page', 94 | externalLink 95 | }) 96 | }) 97 | } else { 98 | // Firefox extension 99 | const tab = await window.app.tabs.query({ 100 | currentWindow: true, 101 | active: true 102 | }) 103 | window.app.tabs.sendMessage(tab[0].id, { 104 | command: 're:get-clipboard-page', 105 | externalLink 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/scrapbox-io/rel-cards-bubble.js: -------------------------------------------------------------------------------- 1 | const daiizScrapboxManage = require('./manage') 2 | const installed = daiizScrapboxManage.installed 3 | const detectProject = daiizScrapboxManage.detectProject 4 | 5 | exports.enable = function () { 6 | var $appRoot = $('#app-container') 7 | /* 関連カード */ 8 | var timer = null 9 | $appRoot.on('mouseenter', 'a.page-link', function (e) { 10 | var $relationLabels = $('li.relation-label') 11 | var pos = installed('daiiz-rel-bubble') 12 | if (pos === false) return 13 | if ($relationLabels.length === 0) return 14 | 15 | var relLabelHeight = 120 // +($relationLabels.css('height').split('px')[0]) 16 | var $a = $(e.target).closest('a.page-link') 17 | if ($a.hasClass('empty-page-link')) return 18 | if (!$a.attr('rel') && $a.attr('rel') !== 'route') return 19 | var $bubble = $getRelCardBubble($appRoot) 20 | var rect = $a[0].getBoundingClientRect() 21 | 22 | var pad = 10 // main.cssでの設定値 23 | var top = rect.top + window.pageYOffset - relLabelHeight - (pad * 2) - 29 // 'n' 24 | if (pos === 's') { 25 | top = rect.top + window.pageYOffset + $a[0].offsetHeight - 22 26 | } 27 | 28 | $bubble.css({ 29 | 'max-width': $('.editor')[0].offsetWidth - $a[0].offsetLeft, 30 | 'left': rect.left + window.pageXOffset, 31 | 'top': 18 + top, 32 | 'background-color': $('body').css('background-color') 33 | }) 34 | var tag = $a[0].innerText.replace(/^#/gi, '').split('#')[0] 35 | if (tag.startsWith('/')) { 36 | $bubble.hide() 37 | return 38 | } 39 | var $cards = $getRelCards(tag) 40 | if ($cards.children().length === 0) { 41 | $bubble.hide() 42 | return 43 | } 44 | $bubble.find('.daiiz-cards').remove() 45 | $bubble.append($cards) 46 | $bubble.css({ 47 | 'height': relLabelHeight 48 | }) 49 | 50 | timer = window.setTimeout(function () { 51 | $bubble.show() 52 | }, 650) 53 | }) 54 | 55 | $appRoot.on('mouseleave', 'a.page-link', function (e) { 56 | window.clearTimeout(timer) 57 | }) 58 | 59 | $appRoot.on('mouseleave', '#daiiz-rel-cards-bubble', function (e) { 60 | var $bubble = $getRelCardBubble($appRoot) 61 | window.clearTimeout(timer) 62 | $bubble.hide() 63 | }) 64 | 65 | $appRoot.on('click', function () { 66 | var $bubble = $getRelCardBubble($appRoot) 67 | window.clearTimeout(timer) 68 | $bubble.hide() 69 | }) 70 | } 71 | 72 | /* 関連カード */ 73 | var $getRelCardBubble = function ($appRoot) { 74 | var $relCardsBubble = $('#daiiz-rel-cards-bubble') 75 | if ($relCardsBubble.length === 0) { 76 | $relCardsBubble = $('') 77 | $appRoot.find('.page').append($relCardsBubble) 78 | } 79 | return $relCardsBubble 80 | } 81 | 82 | /* 関連カード */ 83 | const $getRelCards = function (title) { 84 | var project = encodeURIComponent(window.location.href.match(/scrapbox.io\/([^\/.]*)/)[1]) 85 | var $fillUpIcon = function ($clonedLi) { 86 | if ($clonedLi.find('img.lazy-load-img').length === 0) { 87 | var cardTitle = encodeURIComponent($clonedLi.find('div.title').text()) 88 | $clonedLi.find('div.icon').append( 89 | ``) 91 | } 92 | return $clonedLi 93 | } 94 | 95 | $('.daiiz-cards').remove() 96 | var relationLabels = $('.relation-label') 97 | var $cards = $('
') 98 | for (var i = 0; i < relationLabels.length; i++) { 99 | var $label = $(relationLabels[i]) 100 | var label = $label.find('.title')[0].innerText 101 | if (label === title) { 102 | // TODO: 書き直したい 103 | var $li = $label.next('li.page-list-item') 104 | var $clonedLi = $li.clone(true) 105 | $cards.append($fillUpIcon($clonedLi)) 106 | $clonedLi.css({ 107 | width: 120, 108 | height: 120 109 | }) 110 | var c = 0 111 | while ($li.length === 1 && c < 200) { 112 | $li = $li.next('li.page-list-item') 113 | var $clonedLi = $li.clone(true) 114 | $clonedLi.css({ 115 | width: 120, 116 | height: 120 117 | }) 118 | $cards.append($fillUpIcon($clonedLi)) 119 | c++ 120 | } 121 | } 122 | } 123 | return $cards 124 | } 125 | -------------------------------------------------------------------------------- /src/scrapbox-io/text-bubble.b.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery') 2 | const daiizScrapboxManage = require('./manage') 3 | const {installed, detectProject} = daiizScrapboxManage 4 | 5 | var BRACKET_OPEN = '[' 6 | var DOUBLE_BRACKET_OPEN = '[[' 7 | var BRACKET_CLOSE = ']' 8 | var DOUBLE_BRACKET_CLOSE = ']]' 9 | var INLINE_CODE = '`' 10 | var openInlineCode = false 11 | var openCodeBlock = false 12 | 13 | var PROJECT_NAME = null 14 | var EMPTY_LINKS = [] 15 | 16 | exports.$getTextBubble = function () { 17 | var $textBubble = $(``) 18 | return $textBubble 19 | } 20 | 21 | var decorate = function (str, strOpenMark, depth) { 22 | var html = '' 23 | var tagOpen = [] 24 | var tagClose = [] 25 | if (strOpenMark === BRACKET_OPEN) { 26 | // リンク,装飾 27 | var body = str.replace(/^\[/, '').replace(/\]$/, '') 28 | var words = body.split(' ') 29 | if (words.length >= 2) { 30 | var pair = makePair(words) 31 | var p0 = pair[0] 32 | var p1 = pair[1] 33 | if (p0.startsWith('http')) { 34 | // リンク(別名記法) 35 | body = p1 36 | var href = p0 37 | tagOpen.push(``) 38 | tagClose.push('') 39 | var img = makeImageTag(body) 40 | if (img[1]) { 41 | body = img[0] 42 | } else { 43 | body = spans(p1) 44 | } 45 | } else { 46 | var f = true 47 | body = p1 48 | 49 | // 太字, 斜体, 打ち消し 50 | var o = !p0.match(/[^\-\*\/\_]/gi) 51 | if (o && p0.indexOf('*') >= 0) { 52 | tagOpen.push('') 53 | tagClose.push('') 54 | f = false 55 | } 56 | if (o && p0.indexOf('/') >= 0) { 57 | tagOpen.push('') 58 | tagClose.push('') 59 | f = false 60 | } 61 | if (o && p0.indexOf('-') >= 0) { 62 | tagOpen.push('') 63 | tagClose.push('') 64 | f = false 65 | } 66 | if (o && p0.indexOf('_') >= 0) { 67 | tagOpen.push('') 68 | tagClose.push('') 69 | f = false 70 | } 71 | 72 | if (f) { 73 | // 半角空白を含むタイトルのページ 74 | body = words.join(' ') 75 | var href = (body[0] === '/') ? body : `/${PROJECT_NAME}/${body}` 76 | var target = (PROJECT_NAME !== detectProject()) ? '_blank' : '_self' 77 | var classEmptyLink = '' 78 | if (EMPTY_LINKS.indexOf(body) !== -1) classEmptyLink = 'empty-page-link' 79 | body = spans(body) 80 | tagOpen.push(``) 82 | tagClose.push('') 83 | } 84 | } 85 | var img = makeImageTag(body) 86 | if (img[1]) body = img[0] 87 | } else { 88 | // [ ] 内に空白を含まない 89 | if (body.length === 0) { 90 | body = '[]' 91 | } else { 92 | // リンク, 画像 93 | var pageLink = makePageLink(body, tagOpen, tagClose) 94 | tagOpen = pageLink.tagOpen 95 | tagClose = pageLink.tagClose 96 | body = pageLink.body 97 | } 98 | } 99 | } else if (strOpenMark === DOUBLE_BRACKET_OPEN) { 100 | var body = str.replace(/^\[\[/, '').replace(/\]\]$/, '') 101 | tagOpen.push('') 102 | tagClose.push('') 103 | var img = makeImageTag(body) 104 | if (img[1]) body = img[0] 105 | } else if (strOpenMark === INLINE_CODE) { 106 | var code = str.replace(/^\`/, '').replace(/\`$/, '') 107 | body = `${spans(code)}` 108 | } 109 | 110 | return `${tagOpen.join('')}${body}${tagClose.reverse().join('')}` 111 | } 112 | 113 | var spans = function (txt) { 114 | var body = '' 115 | for (var k = 0; k < txt.length; k++) { 116 | body += `${txt[k]}` 117 | } 118 | return body 119 | } 120 | 121 | var getScrapboxUrl = url => { 122 | return 'https://scrapbox.io' + url 123 | } 124 | exports.getScrapboxUrl = getScrapboxUrl 125 | 126 | var makePageLink = (body, tagOpen, tagClose) => { 127 | var link = {tagOpen: [], tagClose: [], body: ''} 128 | 129 | var href = getScrapboxUrl(`/${PROJECT_NAME}/${body}`) 130 | var startsWithHttp = false 131 | if (body[0] === '/') { 132 | href = getScrapboxUrl(body) 133 | } else if (body.startsWith('http')) { 134 | href = body 135 | startsWithHttp = true 136 | } 137 | 138 | var className = 'page-link' 139 | var target = (PROJECT_NAME !== detectProject()) ? '_blank' : '_self' 140 | if (body.startsWith('http')) { 141 | className = 'daiiz-ref-link' 142 | target = '_blank' 143 | } 144 | var img = makeImageTag(body) 145 | if (img[1]) { 146 | link.tagOpen = [] 147 | link.tagClose = [] 148 | link.body = img[0] 149 | } else { 150 | if (EMPTY_LINKS.indexOf(body) !== -1) className += ' empty-page-link' 151 | link.body = spans(body) 152 | link.tagOpen.push(``) 153 | link.tagClose.push('') 154 | } 155 | return link 156 | } 157 | 158 | var makePair = function (words) { 159 | var w0 = words[0] 160 | var wL = words[words.length - 1] 161 | var pair = [] 162 | if (wL.startsWith('http')) { 163 | pair.push(wL) 164 | pair.push(words.slice(0, words.length - 1).join(' ')) 165 | } else { 166 | pair.push(w0) 167 | pair.push(words.slice(1, words.length).join(' ')) 168 | } 169 | 170 | if (pair[0].startsWith('http') && pair[1].startsWith('http')) { 171 | var a = (pair[0].endsWith('.jpg') || pair[0].endsWith('.png') || pair[0].endsWith('.gif')) 172 | var b = (pair[0].match(/^https{0,1}:\/\/gyazo.com\/.{24,32}$/) !== null) 173 | if (a || b) { 174 | pair.reverse() 175 | } 176 | } 177 | return pair 178 | } 179 | 180 | var encodeHref = function (url, startsWithHttp) { 181 | var tt = url.match(/scrapbox\.io\/([^\/]+)\/(.+)/) 182 | if (startsWithHttp || tt === null) { 183 | url = url.replace(//gi, '%3E').replace(/;/gi, '%3B') 184 | return url 185 | } 186 | if (tt !== null) { 187 | var pageName = tt[2] 188 | var pageRowNum = pageName.match(/#.{24,32}$/) 189 | if (pageRowNum) { 190 | // 行リンク 191 | var n = pageRowNum[0] 192 | pageName = encodeURIComponent(pageName.split(n)[0]) + n 193 | } else { 194 | pageName = encodeURIComponent(pageName) 195 | } 196 | return url.replace(tt[2], pageName) 197 | } 198 | } 199 | 200 | // 画像になる可能性があるものに対処 201 | var makeImageTag = function (keyword) { 202 | keyword = keyword.trim() 203 | var img = '' 204 | var isImg = true 205 | if (keyword.match(/\.icon\**\d*$/gi)) { 206 | var iconName = keyword.split('.icon')[0] 207 | if (iconName.charAt(0) !== '/') { 208 | iconName = '/' + PROJECT_NAME + '/' + iconName 209 | } 210 | var toks = keyword.split('*') 211 | var times = 1 212 | if (toks.length === 2) times = +toks[1] 213 | for (var i = 0; i < times; i++) { 214 | img += `` 215 | } 216 | } else if (keyword.endsWith('.jpg') || keyword.endsWith('.png') || keyword.endsWith('.gif')) { 217 | img = `` 218 | } else if (keyword.match(/^https{0,1}:\/\/gyazo.com\/.{24,32}$/)) { 219 | img = `` 220 | } else { 221 | img = keyword 222 | isImg = false 223 | } 224 | return [img, isImg] 225 | } 226 | 227 | /** Scrapboxの行単位での記法解析 */ 228 | var dicts = [] 229 | var parse = function (fullStr, startIdx, depth, seekEnd) { 230 | fullStr = fullStr.trim() 231 | var l = fullStr.length 232 | var startIdxkeep = startIdx 233 | while (startIdx < l) { 234 | var subStr = fullStr.substring(startIdx, l) 235 | 236 | if (subStr.startsWith(DOUBLE_BRACKET_OPEN) && !openInlineCode) { 237 | var token = parse(fullStr, startIdx + DOUBLE_BRACKET_OPEN.length, depth + 1, DOUBLE_BRACKET_CLOSE) 238 | var str = DOUBLE_BRACKET_OPEN + fullStr.substring(token[0], token[1]) + DOUBLE_BRACKET_CLOSE 239 | var res = decorate(str, DOUBLE_BRACKET_OPEN, depth) 240 | var trans = {} 241 | trans[str] = res 242 | dicts.push(trans) 243 | startIdx = token[1] 244 | } else if (subStr.startsWith(BRACKET_OPEN) && !openInlineCode) { 245 | var token = parse(fullStr, startIdx + BRACKET_OPEN.length, depth + 1, BRACKET_CLOSE) 246 | var str = BRACKET_OPEN + fullStr.substring(token[0], token[1]) + BRACKET_CLOSE 247 | var res = decorate(str, BRACKET_OPEN, depth) 248 | var trans = {} 249 | trans[str] = res 250 | dicts.push(trans) 251 | startIdx = token[1] 252 | } else if (subStr.startsWith(INLINE_CODE) && !openInlineCode) { 253 | openInlineCode = true 254 | // このマークは入れ子構造をとり得ないことに注意 255 | var token = parse(fullStr, startIdx + INLINE_CODE.length, depth + 1, INLINE_CODE) 256 | var str = INLINE_CODE + fullStr.substring(token[0], token[1]) + INLINE_CODE 257 | var res = decorate(str, INLINE_CODE, depth) 258 | var trans = {} 259 | trans[str] = res 260 | dicts.push(trans) 261 | startIdx = token[1] 262 | } 263 | 264 | // 探していた閉じマークが見つかった 265 | if (subStr.startsWith(seekEnd)) { 266 | if (seekEnd === INLINE_CODE) openInlineCode = false 267 | return [startIdxkeep, startIdx] 268 | } 269 | 270 | startIdx++ 271 | } 272 | 273 | // 置換する順番に格納されている 274 | // HTML文字列を作成する 275 | dicts.push(fullStr) 276 | dicts.reverse() 277 | var html = fullStr 278 | for (var i = 1; i < dicts.length; i++) { 279 | var key = Object.keys(dicts[i])[0] 280 | html = html.replace(key, dicts[i][key]) 281 | } 282 | return html 283 | } 284 | 285 | /* ======================== */ 286 | /* Main: 行単位の記法解析 */ 287 | /* ======================== */ 288 | var parseRow = function (row) { 289 | if (row.length === 0) return null 290 | var t0 = row.charAt(0) 291 | row = row.trim() 292 | // コードブロックを無視する処理 293 | if (row.startsWith('code:')) { 294 | openCodeBlock = true 295 | return null 296 | } 297 | if (openCodeBlock) { 298 | if (t0 == ' ' || t0 == '\t') { 299 | return null 300 | } else { 301 | openCodeBlock = false 302 | } 303 | } 304 | 305 | // シェル記法の対応 306 | if (row.charAt(0) === '$') return makeShellStr(row) 307 | // 括弧を用いる記法の解析 308 | dicts = [] 309 | var res = parse(row, 0, 0, null) 310 | // プレーンテキストに埋め込まれたリンクに対応する 311 | res = makePlainLinks(res) 312 | // ハッシュタグをリンク化する 313 | res = makeHashTagLinks(res) 314 | 315 | // scriptタグを無効化 316 | var html = '' 317 | for (var j = 0; j < res.length; j++) { 318 | var c = res.charAt(j) 319 | if (c === '<' && res.substring(j + 1, res.length).startsWith('script')) html += spans('<') 320 | else if (c === '<' && res.substring(j + 1, res.length).startsWith('/script')) html += spans('<') 321 | else if (c === ';') html += spans(';') 322 | else html += c 323 | } 324 | return html 325 | } 326 | 327 | var makeHashTagLinks = function (row) { 328 | row = ' ' + row + ' ' 329 | var hashTags = row.match(/(^| )\#[^ ]+/gi) 330 | if (hashTags) { 331 | for (var i = 0; i < hashTags.length; i++) { 332 | var hashTag = hashTags[i].trim() 333 | var keyword = encodeURIComponent(hashTag.substring(1, hashTag.length)) 334 | var target = (PROJECT_NAME !== detectProject()) ? '_blank' : '_self' 335 | var a = ` ${spans(hashTag)} ` 337 | row = row.replace(` ${hashTag} `, a) 338 | } 339 | } 340 | return row.substring(1, row.length - 1) 341 | } 342 | 343 | var makePlainLinks = function (row) { 344 | row = ' ' + row + ' ' 345 | var words = row.split(' ') 346 | var res = [] 347 | for (var k = 0; k < words.length; k++) { 348 | var word = words[k].trim() 349 | if (word.startsWith('http')) { 350 | var a = ` ${word} ` 351 | row = row.replace(` ${word} `, a) 352 | } 353 | } 354 | return row.substring(1, row.length - 1) 355 | } 356 | 357 | var makeShellStr = function (row) { 358 | return `${spans(row)}` 359 | } 360 | 361 | /* ================ */ 362 | /* 表示コントール */ 363 | /* ================ */ 364 | var previewPageText = function ($root, $bubble, title, rowHash) { 365 | var externalProject = false 366 | var extraClassName = '' 367 | if (PROJECT_NAME !== detectProject()) externalProject = true 368 | $.ajax({ 369 | type: 'GET', 370 | contentType: 'application/json', 371 | url: `https://scrapbox.io/api/pages/${PROJECT_NAME}/${title}` 372 | }).done(data => { 373 | EMPTY_LINKS = data.emptyLinks || [] 374 | if (externalProject) $bubble.addClass('daiiz-external-project') 375 | $bubble.attr('data-project', PROJECT_NAME) 376 | $root.append($bubble) 377 | 378 | var lines = data.lines 379 | var contents = [] 380 | for (var l = 1; l < lines.length; l++) { 381 | var line = lines[l] 382 | if (rowHash) { 383 | if (line.id === rowHash) { 384 | extraClassName = 'daiiz-line-permalink' 385 | var row = parseRow(line.text) 386 | if (row) contents.push(row) 387 | break 388 | } 389 | } else { 390 | var row = parseRow(line.text) 391 | if (row) contents.push(row) 392 | } 393 | } 394 | if (contents.length > 0) { 395 | $bubble.html(`
${contents.join('
')}
`) 396 | $bubble.show() 397 | } 398 | }) 399 | } 400 | 401 | exports.$getRefTextBody = function (title, $root, $bubble, projectName) { 402 | title = title.replace(/^\#/, '') 403 | var t = title.match(/\#.{24,32}$/) 404 | var lineHash = null 405 | if (t !== null) { 406 | title = title.replace(/\#.{24,32}$/, '') 407 | lineHash = t[0].replace('#', '') 408 | } 409 | 410 | if (title.startsWith('/')) { 411 | console.log('...') 412 | return 413 | // 外部プロジェクト名とページ名を抽出 414 | var tt = title.match(/\/([^\/]+)\/(.+)/) 415 | if (!tt) return 416 | var projectName = tt[1] 417 | var title = tt[2] 418 | } 419 | title = encodeURIComponent(title) 420 | PROJECT_NAME = projectName 421 | 422 | previewPageText($root, $bubble, title, lineHash) 423 | } 424 | 425 | exports.enable = function () { 426 | const $appRoot = $('#app-container') 427 | const self = this 428 | let timer = null 429 | 430 | $appRoot.on('mouseenter', 'a.page-link', function (e) { 431 | var pos = installed('daiiz-text-bubble') 432 | if (pos === false) return 433 | 434 | var $a = $(e.target).closest('a.page-link') 435 | var $parentBubble = $(e.target).closest('div.daiiz-text-bubble') 436 | var $root = $appRoot.find('.page') 437 | 438 | if ($a.hasClass('empty-page-link')) return 439 | var $bubble = self.$getTextBubble() 440 | var rect = $a[0].getBoundingClientRect() 441 | $bubble.css({ 442 | 'max-width': $('.editor')[0].offsetWidth - $a[0].offsetLeft, 443 | 'left': rect.left + window.pageXOffset, 444 | 'top': 18 + rect.top + window.pageYOffset + $a[0].offsetHeight + 3 - 24, 445 | 'border-color': $('body').css('background-color') 446 | }) 447 | var pos = `${$bubble.css('top')}_${$bubble.css('left')}` 448 | $bubble.attr('data-pos', pos) 449 | 450 | // すでに表示されているならば,何もしない 451 | if ($(`.daiiz-text-bubble[data-pos="${pos}"]`).length > 0) return 452 | if ($a.attr('rel') && $a.attr('rel') == 'route') { 453 | $(`.daiiz-text-bubble:not([data-pos="${pos}"])`).remove() 454 | } 455 | var keyword = $a[0].innerText 456 | 457 | timer = setTimeout(function () { 458 | let projectName = detectProject() 459 | if ($parentBubble.length > 0) projectName = $parentBubble.attr('data-project') 460 | self.$getRefTextBody(keyword.trim(), $root, $bubble, projectName) 461 | }, 650) 462 | }) 463 | 464 | $appRoot.on('mouseleave', 'a.page-link', function (e) { 465 | clearTimeout(timer) 466 | }) 467 | 468 | $appRoot.on('mouseleave', '.daiiz-card', function (e) { 469 | clearTimeout(timer) 470 | }) 471 | 472 | $appRoot.on('click', function (e) { 473 | clearTimeout(timer) 474 | var $bubble = $('.daiiz-card') 475 | var $t = $(e.target).closest('.daiiz-card') 476 | if ($(e.target)[0].tagName.toLowerCase() === 'a') { 477 | $bubble.remove() 478 | } else if ($t.length > 0) { 479 | $t.remove() 480 | } else { 481 | $bubble.remove() 482 | } 483 | }) 484 | } 485 | --------------------------------------------------------------------------------