├── 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(``)
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 |
--------------------------------------------------------------------------------