Skip to content

Commit

Permalink
feat: Add shortcut buttons (whale4113#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
whale4113 committed Nov 10, 2024
1 parent b59bf0c commit 1eae92c
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 78 deletions.
10 changes: 10 additions & 0 deletions .changeset/hot-poets-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@dolphin/chrome-extension': minor
---

feat: Add shortcut buttons

Contributors:

- @augustVino
- @whale4113
12 changes: 0 additions & 12 deletions apps/chrome-extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,6 @@
"run_at": "document_end"
}
],
"web_accessible_resources": [
{
"resources": ["bundles/content.js"],
"matches": [
"https://*.feishu.cn/*",
"https://*.feishu.net/*",
"https://*.larksuite.com/*",
"https://*.feishu-pre.net/*",
"https://*.larkoffice.com/*"
]
}
],
"background": {
"service_worker": "bundles/background.js",
"type": "module"
Expand Down
305 changes: 239 additions & 66 deletions apps/chrome-extension/src/content.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,252 @@
function initContent() {
const box = document.createElement('div')

Object.entries({
bottom: '100px',
right: '15px',
marginRight: '22px',
position: 'fixed',
width: '80px',
height: '36px',
zIndex: 80,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
color: '#1f2329',
}).forEach(([attr, value]) => {
box.style[attr] = value
})
const COMMENT_BUTTON_CLASS = '.docx-comment__first-comment-btn'
const HELP_BLOCK_CLASS = '.help-block'

let disposables: (() => void)[] = []

const dispose = () => {
disposables.forEach(disposable => disposable())

disposables = []
}

interface Button {
element: HTMLElement
width: number
height: number
}

const initButtons = () => {
const root = document.body

const isReady = () => {
const commentButton = root.querySelector(COMMENT_BUTTON_CLASS)
if (!commentButton) {
return false
}

const helpBlock = root.querySelector(HELP_BLOCK_CLASS)
if (!helpBlock) {
return false
}

return true
}

const operates = [
{
type: 'copy',
innerHtml: `<svg aria-hidden="true" focusable="false" role="img" class="octicon octicon-copy" viewBox="0 0 16 16" width="16"
height="16" fill="currentColor">
const render = () => {
const style = document.createElement('style')
style.innerHTML = `
[data-CDC-button-type] {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--line-border-card);
border-radius: 50%;
background-color: var(--bg-body);
cursor: pointer;
text-align: center;
color: var(--text-title);
z-index: 3;
}
[data-CDC-button-type]:hover {
color: var(--colorful-blue);
}
`

const operates = [
{
type: 'copy',
innerHtml: `<svg aria-hidden="true" focusable="false" role="img" class="octicon octicon-copy" viewBox="0 0 16 16" width="16"
height="16" fill="currentColor">
<path
d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z">
</path>
<path
d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z">
</path>
</svg>`,
action: () => {
chrome.runtime.sendMessage({ flag: 'copy_docx_as_markdown' })
},
},
{
type: 'download',
innerHtml: `<svg aria-hidden="true" focusable="false" role="img" class="octicon octicon-download" viewBox="0 0 16 16"
width="16" height="16" fill="currentColor">
<path
d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z">
d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z">
</path>
<path
d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z">
d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z">
</path>
</svg>`,
action: () => {
chrome.runtime.sendMessage({ flag: 'copy_docx_as_markdown' })
},
},
{
type: 'download',
innerHtml: `<svg aria-hidden="true" focusable="false" role="img" class="octicon octicon-download" viewBox="0 0 16 16"
width="16" height="16" fill="currentColor">
<path
d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z">
</path>
<path
d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z">
</path>
</svg>`,
action: () => {
chrome.runtime.sendMessage({ flag: 'download_docx_as_markdown' })
</svg>`,
action: () => {
chrome.runtime.sendMessage({ flag: 'download_docx_as_markdown' })
},
},
},
]

operates.forEach(({ type, innerHtml, action }) => {
const btn = document.createElement('span')
btn.setAttribute('data-type', type)
btn.innerHTML = innerHtml

Object.entries({
display: 'inline-block',
width: '36px',
height: '36px',
borderRadius: '50%',
backgroundColor: '#fff',
cursor: 'pointer',
border: `1px solid #dee0e3`,
textAlign: 'center',
lineHeight: '36px',
}).forEach(([attr, value]) => {
btn.style[attr] = value
]

const buttons = operates.map<Button>(({ type, innerHtml, action }) => {
const btn = document.createElement('button')
btn.type = 'button'
btn.setAttribute('data-CDC-button-type', type)
btn.innerHTML = innerHtml

btn.style.width = '36px'
btn.style.height = '36px'

btn.addEventListener('click', action)

return {
element: btn,
width: 36,
height: 36,
}
})

btn.addEventListener('click', action)
box.append(btn)
const layout = (buttons: Button[]) => {
const commentButton = root.querySelector(COMMENT_BUTTON_CLASS)
if (!commentButton) {
return
}

const helpBlock = root.querySelector(HELP_BLOCK_CLASS)
if (!helpBlock) {
return
}

if (
commentButton instanceof HTMLElement &&
helpBlock instanceof HTMLElement
) {
const commentButtonRect = commentButton.getBoundingClientRect()
const helpBlockRect = helpBlock.getBoundingClientRect()

if (commentButtonRect.right === helpBlockRect.right) {
// vertical arrangement
let startBottom = 124
const startRight = 22
const gap = 14

buttons.forEach(button => {
button.element.style.right = `${startRight}px`
button.element.style.bottom = `${startBottom + gap}px`

startBottom += gap + button.height
})
} else if (commentButtonRect.bottom === helpBlockRect.bottom) {
// horizontal arrangement
let startRight = 22
const startBottom = 93
const gap = 22

buttons.forEach(button => {
button.element.style.right = `${startRight}px`
button.element.style.bottom = `${startBottom}px`

startRight += gap + button.width
})
}
}
}

// When the width of the docx's visible content is too narrow, the position of the comment button changes.
const autoLayoutObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'class'
) {
layout(buttons)
}
}
})
const autoLayout = () => {
const commentButton = root.querySelector(COMMENT_BUTTON_CLASS)
if (!commentButton) {
return
}

autoLayoutObserver.observe(commentButton, {
attributes: true,
attributeFilter: ['class'],
})
}

layout(buttons)
autoLayout()

root.appendChild(style)
buttons.forEach(button => {
root.appendChild(button.element)
})

disposables.push(() => {
autoLayoutObserver.disconnect()

buttons.forEach(button => {
if (root.contains(button.element)) {
root.removeChild(button.element)
}
})

if (root.contains(style)) {
root.removeChild(style)
}
})
}

const init = () => {
render()
}

const initObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (
node instanceof HTMLElement &&
(node.matches(COMMENT_BUTTON_CLASS) ||
node.matches(HELP_BLOCK_CLASS)) &&
isReady()
) {
init()
}
}
}
}
})

document.body.append(box)
isReady()
? init()
: initObserver.observe(root, {
childList: true,
subtree: true,
})

disposables.push(() => {
initObserver.disconnect()
})
}

function initContent() {
initButtons()
}

window.addEventListener('load', initContent, false)

// For SPA, some page content updates do not trigger page reloads
let lastPathname: string = location.pathname
const urlChangeObserver = new MutationObserver(() => {
const pathname = location.pathname

if (pathname !== lastPathname) {
lastPathname = pathname

dispose()

initContent()
}
})
urlChangeObserver.observe(document.body, { childList: true })

0 comments on commit 1eae92c

Please sign in to comment.