mirror of
https://github.com/Jonnyan404/memos-bber.git
synced 2026-06-25 06:46:21 +09:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f05581f88a | |||
| d237a7f1c6 | |||
| 13cc7659ea | |||
| e9730b5839 | |||
| f2f1ff2c10 | |||
| 5fac00b5ce | |||
| 2b36fda137 | |||
| da150b8788 | |||
| 85cc964836 | |||
| adfd797e84 | |||
| 8f51bb399b |
@@ -5,8 +5,10 @@ Chrome 应用商店:<https://chrome.google.com/webstore/detail/memos-bber/cbhj
|
||||
一个通过浏览器插件发布 [Memos](https://usememos.com/) 的插件。基于 iSpeak-bber 修改,原作者为 [DreamyTZK](https://www.antmoe.com/)。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 20260308 向前兼容到0.18.0,可能再往前也行,只测试到0.18.0
|
||||
- 20260322 适配移动端竖屏窗口
|
||||
- 20260310 记忆拖拽窗口大小,移除拖拽窗口动画
|
||||
- 20260309 右键发送选中文本保持原格式,增加全屏和窗口放大功能
|
||||
### 20260308 向前兼容到0.15.0,可能再往前也行,只测试到0.15.0
|
||||
- 20260307 增加语言切换按钮以及韩语和日语支持,
|
||||
- 2026年03月06日 右键菜单发送选中文本附带原文链接
|
||||
- 2026年03月05日 向前兼容到0.24.0,可能再往前也行,因为只测试了0.24.0和0.25.0以及当前最新版本,如有更早版本需求,可issue反馈
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"message": "Save"
|
||||
},
|
||||
"supportedMemosVersion": {
|
||||
"message": "Compatible with Memos v0.18.0 - 0.26.x"
|
||||
"message": "Compatible with Memos v0.15.0 - 0.26.x"
|
||||
},
|
||||
"placeApiUrl":{
|
||||
"message": "Memos site URL"
|
||||
@@ -163,5 +163,11 @@
|
||||
},
|
||||
"langKorean": {
|
||||
"message": "한국어"
|
||||
},
|
||||
"tipFullscreen": {
|
||||
"message": "Open fullscreen editor"
|
||||
},
|
||||
"tipResize": {
|
||||
"message": "Drag to resize (min: default size)"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"message": "保存"
|
||||
},
|
||||
"supportedMemosVersion": {
|
||||
"message": "Memos v0.18.0 - 0.26.x に対応"
|
||||
"message": "Memos v0.15.0 - 0.26.x に対応"
|
||||
},
|
||||
"placeApiUrl": {
|
||||
"message": "Memos サイトURL"
|
||||
@@ -163,5 +163,11 @@
|
||||
},
|
||||
"langKorean": {
|
||||
"message": "한국어"
|
||||
},
|
||||
"tipFullscreen": {
|
||||
"message": "全画面で編集"
|
||||
},
|
||||
"tipResize": {
|
||||
"message": "ドラッグで拡大/縮小(最小:初期サイズ)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"message": "저장"
|
||||
},
|
||||
"supportedMemosVersion": {
|
||||
"message": "Memos v0.18.0 - 0.26.x 호환"
|
||||
"message": "Memos v0.15.0 - 0.26.x 호환"
|
||||
},
|
||||
"placeApiUrl": {
|
||||
"message": "Memos 사이트 URL"
|
||||
@@ -158,10 +158,16 @@
|
||||
"langChineseSimplified": {
|
||||
"message": "简体中文"
|
||||
},
|
||||
"langJapanese": {
|
||||
"langJapanese": {
|
||||
"message": "日本語"
|
||||
},
|
||||
"langKorean": {
|
||||
"message": "한국어"
|
||||
},
|
||||
"tipFullscreen": {
|
||||
"message": "전체화면 편집"
|
||||
},
|
||||
"tipResize": {
|
||||
"message": "드래그로 확대/축소(최소: 기본 크기)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"message": "保存"
|
||||
},
|
||||
"supportedMemosVersion": {
|
||||
"message": "兼容 Memos v0.18.0 - 0.26.x"
|
||||
"message": "兼容 Memos v0.15.0 - 0.26.x"
|
||||
},
|
||||
"placeApiUrl":{
|
||||
"message": "请填入 Memos 主页网址"
|
||||
@@ -163,5 +163,11 @@
|
||||
},
|
||||
"langKorean": {
|
||||
"message": "한국어"
|
||||
},
|
||||
"tipFullscreen": {
|
||||
"message": "全屏编辑"
|
||||
},
|
||||
"tipResize": {
|
||||
"message": "拖拽缩放编辑框(最小为默认大小)"
|
||||
}
|
||||
}
|
||||
+90
-8
@@ -20,7 +20,7 @@ input:focus::placeholder ,.common-editor-inputer:focus::placeholder {
|
||||
}
|
||||
|
||||
.body{
|
||||
min-width:460px;
|
||||
min-width:360px;
|
||||
background-color: #f6f5f4;
|
||||
padding:0 1rem 1rem;
|
||||
font-family: eafont,PingFang SC,Hiragino Sans GB,Microsoft YaHei,STHeiti,WenQuanYi Micro Hei,Helvetica,Arial,sans-serif;
|
||||
@@ -29,6 +29,7 @@ input:focus::placeholder ,.common-editor-inputer:focus::placeholder {
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a{color: #555;}
|
||||
.title{
|
||||
width: 100px;
|
||||
@@ -44,9 +45,49 @@ a{color: #555;}
|
||||
background-color: rgb(255,255,255);
|
||||
margin-top:0.8rem;
|
||||
padding: 0.6rem;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(.4,0,.2,1);
|
||||
transition-duration: .15s;
|
||||
}
|
||||
.memo-editor{
|
||||
position: relative;
|
||||
resize: none;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
contain: layout;
|
||||
}
|
||||
|
||||
.memo-editor-header{
|
||||
position: sticky;
|
||||
top: .5rem;
|
||||
z-index: 3;
|
||||
height: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#editor-resize-handle{
|
||||
position: absolute;
|
||||
right: .35rem;
|
||||
bottom: .35rem;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-right: 2px solid #bbb;
|
||||
border-bottom: 2px solid #bbb;
|
||||
cursor: nwse-resize;
|
||||
opacity: .8;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#editor-resize-handle:hover{
|
||||
opacity: 1;
|
||||
border-right-color: #888;
|
||||
border-bottom-color: #888;
|
||||
}
|
||||
|
||||
.body.fullscreen #editor-resize-handle{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.body.fullscreen #fullscreen{
|
||||
display: none;
|
||||
}
|
||||
.random-item{
|
||||
border: 1px solid rgb(229,231,235);
|
||||
@@ -66,6 +107,32 @@ a{color: #555;}
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;}
|
||||
.btns-container{text-align:right;}
|
||||
.memo-editor #fullscreen{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
border: 1px solid rgb(229,231,235);
|
||||
border-radius: .25rem;
|
||||
background-color: rgb(255,255,255);
|
||||
color: #666;
|
||||
font-size: .75rem;
|
||||
line-height: 1;
|
||||
padding: .25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.memo-editor #fullscreen svg{
|
||||
display: block;
|
||||
}
|
||||
.memo-editor #fullscreen:hover{
|
||||
opacity: 1;
|
||||
background-color: rgb(243,244,246);
|
||||
}
|
||||
.common-editor-inputer,input.inputer{
|
||||
font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
|
||||
height: 100%;
|
||||
@@ -77,10 +144,27 @@ a{color: #555;}
|
||||
background-color: transparent;
|
||||
font-size: 1rem;
|
||||
min-height: 40px;
|
||||
max-height: 400px;
|
||||
scrollbar-width: none;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.common-editor-inputer{
|
||||
padding-right: 1.5rem;
|
||||
height: auto;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.body.fullscreen{
|
||||
min-width: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.body.fullscreen .common-editor-inputer{
|
||||
min-height: 60vh;
|
||||
}
|
||||
input.inputer{border-bottom: 1px solid #ccc;width:75%;}
|
||||
|
||||
#saveKey{margin:0;flex:1;}
|
||||
@@ -248,9 +332,7 @@ input.inputer{border-bottom: 1px solid #ccc;width:75%;}
|
||||
padding: .15rem .35rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
#blog_info{
|
||||
|
||||
}
|
||||
|
||||
.tip{
|
||||
margin-left: 36%;
|
||||
@@ -454,4 +536,4 @@ input.inputer{border-bottom: 1px solid #ccc;width:75%;}
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity))
|
||||
}
|
||||
}
|
||||
|
||||
+118
-27
@@ -23,6 +23,89 @@ function updateContextMenu(id, update) {
|
||||
})
|
||||
}
|
||||
|
||||
function pageReadSelectionText() {
|
||||
try {
|
||||
const active = document.activeElement
|
||||
const isTextInput =
|
||||
active &&
|
||||
(active.tagName === 'TEXTAREA' ||
|
||||
(active.tagName === 'INPUT' &&
|
||||
/^(text|search|url|tel|email|password)$/i.test(active.type || 'text')))
|
||||
|
||||
if (isTextInput && typeof active.selectionStart === 'number' && typeof active.selectionEnd === 'number') {
|
||||
return String(active.value || '').slice(active.selectionStart, active.selectionEnd).replace(/\r\n?/g, '\n')
|
||||
}
|
||||
|
||||
const sel = window.getSelection && window.getSelection()
|
||||
if (!sel) return ''
|
||||
return String(sel.toString() || '').replace(/\r\n?/g, '\n')
|
||||
} catch (_) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectionTextFromTab(tabId, fallbackText) {
|
||||
return new Promise((resolve) => {
|
||||
const fallback = typeof fallbackText === 'string' ? fallbackText : ''
|
||||
if (!tabId || !chrome.scripting || typeof chrome.scripting.executeScript !== 'function') {
|
||||
resolve(fallback)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
chrome.scripting.executeScript(
|
||||
{
|
||||
target: { tabId },
|
||||
func: pageReadSelectionText
|
||||
},
|
||||
(results) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
resolve(fallback)
|
||||
return
|
||||
}
|
||||
const first = Array.isArray(results) ? results[0] : null
|
||||
const text = first && typeof first.result === 'string' ? first.result : ''
|
||||
resolve(text || fallback)
|
||||
}
|
||||
)
|
||||
} catch (_) {
|
||||
resolve(fallback)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function tryOpenActionPopup(tab) {
|
||||
try {
|
||||
if (!chrome.action || typeof chrome.action.openPopup !== 'function') return
|
||||
const windowId = tab && typeof tab.windowId === 'number' ? tab.windowId : undefined
|
||||
|
||||
const open = () => {
|
||||
try {
|
||||
if (typeof windowId === 'number') {
|
||||
chrome.action.openPopup({ windowId }, () => void chrome.runtime.lastError)
|
||||
} else {
|
||||
chrome.action.openPopup({}, () => void chrome.runtime.lastError)
|
||||
}
|
||||
} catch (_) {
|
||||
// best-effort only
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid: "Cannot show popup for an inactive window".
|
||||
if (typeof windowId === 'number' && chrome.windows && typeof chrome.windows.update === 'function') {
|
||||
chrome.windows.update(windowId, { focused: true }, () => {
|
||||
void chrome.runtime.lastError
|
||||
open()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
open()
|
||||
} catch (_) {
|
||||
// best-effort only
|
||||
}
|
||||
}
|
||||
|
||||
let cachedUiLanguage = null
|
||||
let cachedOverrideMessages = null
|
||||
|
||||
@@ -93,35 +176,43 @@ chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||
refreshContextMenus()
|
||||
})
|
||||
|
||||
chrome.contextMenus.onClicked.addListener((info) => {
|
||||
let tempCont = ''
|
||||
switch (info.menuItemId) {
|
||||
case 'Memos-send-selection':
|
||||
tempCont =
|
||||
info.selectionText +
|
||||
'\n' +
|
||||
`[Reference Link](${info.linkUrl || info.pageUrl})` +
|
||||
'\n'
|
||||
break
|
||||
case 'Memos-send-link':
|
||||
tempCont = (info.linkUrl || info.pageUrl) + '\n'
|
||||
break
|
||||
case 'Memos-send-image':
|
||||
tempCont = `` + '\n'
|
||||
break
|
||||
}
|
||||
|
||||
chrome.storage.sync.get(
|
||||
{ open_action: 'save_text', open_content: '' },
|
||||
function (items) {
|
||||
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||
const appendContent = (tempCont, { openPopup } = { openPopup: false }) => {
|
||||
chrome.storage.sync.get({ open_action: 'save_text', open_content: '' }, function (items) {
|
||||
if (items.open_action === 'upload_image') {
|
||||
t('picPending').then((m) => alert(m))
|
||||
} else {
|
||||
chrome.storage.sync.set({
|
||||
return
|
||||
}
|
||||
|
||||
chrome.storage.sync.set(
|
||||
{
|
||||
open_action: 'save_text',
|
||||
open_content: items.open_content + tempCont
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
function () {
|
||||
if (openPopup) tryOpenActionPopup(tab)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (info.menuItemId === 'Memos-send-selection') {
|
||||
const ref = info.linkUrl || info.pageUrl
|
||||
const tabId = tab && tab.id
|
||||
|
||||
getSelectionTextFromTab(tabId, info.selectionText).then((selectionText) => {
|
||||
const tempCont = selectionText + '\n' + `[Reference Link](${ref})` + '\n'
|
||||
appendContent(tempCont, { openPopup: true })
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (info.menuItemId === 'Memos-send-link') {
|
||||
appendContent((info.linkUrl || info.pageUrl) + '\n')
|
||||
return
|
||||
}
|
||||
|
||||
if (info.menuItemId === 'Memos-send-image') {
|
||||
appendContent(`` + '\n')
|
||||
}
|
||||
})
|
||||
@@ -86,6 +86,8 @@ function applyStaticI18n() {
|
||||
setText('lockPublic', 'lockPublic')
|
||||
|
||||
setText('content_submit_text', 'submitBtn')
|
||||
const fullscreen = document.getElementById('fullscreen')
|
||||
if (fullscreen) fullscreen.setAttribute('aria-label', t('tipFullscreen'))
|
||||
|
||||
setPlaceholder('hideInput', 'placeHideInput')
|
||||
setPlaceholder('showInput', 'placeShowInput')
|
||||
@@ -111,6 +113,8 @@ function applyStaticI18n() {
|
||||
setTitle('search', 'tipSearch')
|
||||
setTitle('lock', 'tipVisibility')
|
||||
setTitle('content_submit_text', 'tipSend')
|
||||
setTitle('fullscreen', 'tipFullscreen')
|
||||
setTitle('editor-resize-handle', 'tipResize')
|
||||
}
|
||||
|
||||
async function setUiLanguage(nextLang, { persist = true } = {}) {
|
||||
|
||||
+229
-2
@@ -1,6 +1,207 @@
|
||||
dayjs.extend(window.dayjs_plugin_relativeTime)
|
||||
let currentMemoLock = ''
|
||||
|
||||
function isFullscreenMode() {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search || '')
|
||||
return params.get('mode') === 'full'
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function openFullscreenTab() {
|
||||
try {
|
||||
const url = chrome.runtime.getURL('popup.html?mode=full')
|
||||
chrome.tabs.create({ url })
|
||||
} catch (_) {
|
||||
// best-effort only
|
||||
}
|
||||
}
|
||||
|
||||
function initProportionalEditorResize() {
|
||||
try {
|
||||
if (isFullscreenMode()) return
|
||||
|
||||
const editor = document.querySelector('.memo-editor')
|
||||
const tools = document.querySelector('.common-tools-wrapper')
|
||||
const handle = document.getElementById('editor-resize-handle')
|
||||
if (!editor || !tools || !handle) return
|
||||
|
||||
const safety = 8
|
||||
const initialRect = editor.getBoundingClientRect()
|
||||
const baseW = Math.ceil(initialRect.width)
|
||||
const baseH = Math.ceil(initialRect.height)
|
||||
|
||||
// Lock the base size. Scaling will be applied by setting width/height.
|
||||
editor.style.width = `${baseW}px`
|
||||
editor.style.height = `${baseH}px`
|
||||
editor.style.minWidth = `${baseW}px`
|
||||
editor.style.minHeight = `${baseH}px`
|
||||
|
||||
const storageKey = 'popupEditorScale'
|
||||
|
||||
let maxScale = 1
|
||||
const computeMaxScale = () => {
|
||||
// In popup mode, allow scaling up to Chrome's max popup size.
|
||||
// Do not clamp by current window.innerWidth/innerHeight, otherwise the popup can't grow to the max.
|
||||
const viewportW = 800
|
||||
const viewportH = 600
|
||||
|
||||
const editorRect = editor.getBoundingClientRect()
|
||||
const toolsRect = tools.getBoundingClientRect()
|
||||
const toolsStyle = window.getComputedStyle(tools)
|
||||
const gap = parseFloat(toolsStyle.marginTop || '0') || 0
|
||||
|
||||
const availW = Math.max(0, viewportW - safety - editorRect.left)
|
||||
const availH = Math.max(0, viewportH - safety - toolsRect.height - editorRect.top - gap)
|
||||
|
||||
const scaleW = baseW > 0 ? availW / baseW : 1
|
||||
const scaleH = baseH > 0 ? availH / baseH : 1
|
||||
maxScale = Math.max(1, Math.min(scaleW, scaleH))
|
||||
}
|
||||
|
||||
computeMaxScale()
|
||||
window.addEventListener('resize', computeMaxScale)
|
||||
|
||||
let dragging = false
|
||||
let startX = 0
|
||||
let startY = 0
|
||||
let startScale = 1
|
||||
let rafId = 0
|
||||
let pendingScale = null
|
||||
|
||||
const parseScale = (raw) => {
|
||||
const s = typeof raw === 'number' && Number.isFinite(raw)
|
||||
? raw
|
||||
: typeof raw === 'string' && raw.trim() !== '' && !Number.isNaN(Number(raw))
|
||||
? Number(raw)
|
||||
: 1
|
||||
return s > 0 ? s : 1
|
||||
}
|
||||
|
||||
const readCurrentScale = () => {
|
||||
const w = parseFloat(editor.style.width || '')
|
||||
const h = parseFloat(editor.style.height || '')
|
||||
const sw = baseW > 0 && Number.isFinite(w) ? w / baseW : 1
|
||||
const sh = baseH > 0 && Number.isFinite(h) ? h / baseH : 1
|
||||
return Math.max(1, sw, sh)
|
||||
}
|
||||
|
||||
const applyScale = (scale) => {
|
||||
const s = Math.max(1, Math.min(maxScale, scale))
|
||||
editor.style.width = `${Math.round(baseW * s)}px`
|
||||
editor.style.height = `${Math.round(baseH * s)}px`
|
||||
}
|
||||
|
||||
const applyScaleInstant = (scale) => {
|
||||
// In case CSS transitions exist (or get reintroduced), keep restores immediate.
|
||||
const prevTransition = editor.style.transition
|
||||
editor.style.transition = 'none'
|
||||
applyScale(scale)
|
||||
window.requestAnimationFrame(function () {
|
||||
editor.style.transition = prevTransition
|
||||
})
|
||||
}
|
||||
|
||||
// Restore previously saved scale synchronously (localStorage) first.
|
||||
// This makes the popup *feel* synchronous because it can apply before async chrome.storage returns.
|
||||
let restoredFromLocal = false
|
||||
let localScale = 1
|
||||
try {
|
||||
const raw = window.localStorage ? window.localStorage.getItem(storageKey) : null
|
||||
const s = parseScale(raw)
|
||||
if (s && s !== 1) {
|
||||
localScale = s
|
||||
restoredFromLocal = true
|
||||
applyScaleInstant(s)
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Restore from chrome.storage.sync (best-effort) and keep localStorage in sync.
|
||||
try {
|
||||
chrome.storage.sync.get({ [storageKey]: 1 }, function (items) {
|
||||
const raw = items ? items[storageKey] : 1
|
||||
const s = parseScale(raw)
|
||||
const shouldApply = !restoredFromLocal || Math.abs(s - localScale) > 1e-6
|
||||
if (shouldApply) applyScaleInstant(s)
|
||||
try {
|
||||
if (window.localStorage) window.localStorage.setItem(storageKey, String(s))
|
||||
} catch (_) {}
|
||||
})
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const scheduleApply = () => {
|
||||
if (rafId) return
|
||||
rafId = window.requestAnimationFrame(() => {
|
||||
rafId = 0
|
||||
if (pendingScale == null) return
|
||||
const s = pendingScale
|
||||
pendingScale = null
|
||||
applyScale(s)
|
||||
})
|
||||
}
|
||||
|
||||
handle.addEventListener('pointerdown', (ev) => {
|
||||
dragging = true
|
||||
startX = ev.clientX
|
||||
startY = ev.clientY
|
||||
startScale = readCurrentScale()
|
||||
computeMaxScale()
|
||||
try { handle.setPointerCapture(ev.pointerId) } catch (_) {}
|
||||
ev.preventDefault()
|
||||
})
|
||||
|
||||
handle.addEventListener('pointermove', (ev) => {
|
||||
if (!dragging) return
|
||||
const dx = ev.clientX - startX
|
||||
const dy = ev.clientY - startY
|
||||
|
||||
// Proportional scale based on diagonal length for smoother, more linear feel.
|
||||
const diag0 = Math.hypot(baseW, baseH)
|
||||
const targetW = baseW * startScale + dx
|
||||
const targetH = baseH * startScale + dy
|
||||
const diag1 = Math.hypot(targetW, targetH)
|
||||
const next = diag0 > 0 ? diag1 / diag0 : startScale
|
||||
|
||||
pendingScale = next
|
||||
scheduleApply()
|
||||
})
|
||||
|
||||
const endDrag = () => {
|
||||
dragging = false
|
||||
|
||||
// Flush any pending RAF update before persisting.
|
||||
if (pendingScale != null) {
|
||||
applyScale(pendingScale)
|
||||
pendingScale = null
|
||||
}
|
||||
|
||||
// Persist current scale (best-effort).
|
||||
try {
|
||||
const s = readCurrentScale()
|
||||
if (typeof s === 'number' && Number.isFinite(s)) {
|
||||
try {
|
||||
if (window.localStorage) window.localStorage.setItem(storageKey, String(s))
|
||||
} catch (_) {}
|
||||
chrome.storage.sync.set({ [storageKey]: s })
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
handle.addEventListener('pointerup', endDrag)
|
||||
handle.addEventListener('pointercancel', endDrag)
|
||||
} catch (_) {
|
||||
// best-effort only
|
||||
}
|
||||
}
|
||||
|
||||
function msg(key) {
|
||||
if (typeof window.t === 'function') return window.t(key)
|
||||
return chrome.i18n.getMessage(key) || ''
|
||||
@@ -57,6 +258,10 @@ function updateLockNowText(lockType) {
|
||||
|
||||
applyDayjsLocaleByUiLanguage(typeof window.getUiLanguage === 'function' ? window.getUiLanguage() : 'auto')
|
||||
|
||||
if (isFullscreenMode()) {
|
||||
document.body.classList.add('fullscreen')
|
||||
}
|
||||
|
||||
window.addEventListener('i18n:changed', (ev) => {
|
||||
applyDayjsLocaleByUiLanguage(ev && ev.detail ? ev.detail.lang : 'auto')
|
||||
updateLockNowText(currentMemoLock)
|
||||
@@ -143,11 +348,14 @@ get_info(function (info) {
|
||||
//打开的时候就是上传图片
|
||||
uploadImage(info.open_content)
|
||||
} else {
|
||||
$("textarea[name=text]").val(info.open_content)
|
||||
const $textarea = $("textarea[name=text]")
|
||||
$textarea.val(info.open_content)
|
||||
focusTextareaToEnd($textarea)
|
||||
}
|
||||
|
||||
relistNow = Array.isArray(info.resourceIdList) ? info.resourceIdList : []
|
||||
renderUploadList(relistNow)
|
||||
initProportionalEditorResize()
|
||||
//从localstorage 里面读取数据
|
||||
setTimeout(get_info, 1)
|
||||
})
|
||||
@@ -161,7 +369,7 @@ chrome.storage.onChanged.addListener(function (changes, areaName) {
|
||||
renderUploadList(relistNow)
|
||||
})
|
||||
|
||||
$("textarea[name=text]").focus()
|
||||
// focus is handled after textarea content is set
|
||||
|
||||
//监听输入结束,保存未发送内容到本地
|
||||
$("textarea[name=text]").blur(function () {
|
||||
@@ -176,6 +384,11 @@ $("textarea[name=text]").on('keydown', function (ev) {
|
||||
}
|
||||
})
|
||||
|
||||
$('#fullscreen').on('click', function () {
|
||||
if (isFullscreenMode()) return
|
||||
openFullscreenTab()
|
||||
})
|
||||
|
||||
//监听拖拽事件,实现拖拽到窗口上传图片
|
||||
initDrag()
|
||||
|
||||
@@ -239,6 +452,20 @@ function escapeHtml(input) {
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
function focusTextareaToEnd($textarea) {
|
||||
try {
|
||||
const el = $textarea && $textarea[0]
|
||||
if (!el) return
|
||||
el.focus()
|
||||
const len = typeof el.value === 'string' ? el.value.length : 0
|
||||
if (typeof el.setSelectionRange === 'function') {
|
||||
el.setSelectionRange(len, len)
|
||||
}
|
||||
} catch (_) {
|
||||
// best-effort only
|
||||
}
|
||||
}
|
||||
|
||||
function buildV1ResourceStreamUrl(info, resource) {
|
||||
if (!info || !info.apiUrl || !resource) return ''
|
||||
// Use the configured apiUrl as the base (may include a reverse-proxy subpath).
|
||||
|
||||
+4
-2
@@ -2,8 +2,8 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_extName__",
|
||||
"default_locale": "en",
|
||||
"version": "2026.03.08",
|
||||
"version_name": "Supports 0.18.0 to the latest version",
|
||||
"version": "2026.03.22",
|
||||
"version_name": "Supports 0.15.0 - 0.26.x",
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": "assets/logo_24x24.png",
|
||||
@@ -21,6 +21,8 @@
|
||||
},
|
||||
"permissions": [
|
||||
"tabs",
|
||||
"scripting",
|
||||
"windows",
|
||||
"storage",
|
||||
"activeTab",
|
||||
"contextMenus"
|
||||
|
||||
+9
-1
@@ -51,6 +51,13 @@
|
||||
</div>
|
||||
|
||||
<div class="memo-editor">
|
||||
<div class="memo-editor-header">
|
||||
<button id="fullscreen" class="action-btn" type="button" aria-label="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fullscreen" viewBox="0 0 16 16">
|
||||
<path d="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5M.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5m15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
class="common-editor-inputer"
|
||||
rows="4"
|
||||
@@ -59,6 +66,7 @@
|
||||
placeholder=""
|
||||
required=""
|
||||
></textarea>
|
||||
<div id="editor-resize-handle" aria-label="Resize"></div>
|
||||
</div>
|
||||
|
||||
<div class="common-tools-wrapper">
|
||||
@@ -151,7 +159,7 @@
|
||||
<script src="../js/ko.js"></script>
|
||||
<script src="../js/relativeTime.js"></script>
|
||||
<script src="../js/view-image.js"></script>
|
||||
<script src="../js/memosApi.js"></script>
|
||||
<script src="../js/compat/memosApi.v024.js"></script>
|
||||
<script src="../js/compat/memosApi.v1.js"></script>
|
||||
<script src="../js/compat/memosApi.v023.js"></script>
|
||||
<script src="../js/oper.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user