/
githubmirror
/
marktext
Обзор
Документация
Войти
/
githubmirror
/
marktext
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
packages/muyajs/lib/contentState/imageCtrl.js
258 строк
8 KB
Ran Luo
chore: convert repo to pnpm monorepo (packages/desktop, muyajs, website) (#4302)
29 май 2026, 05:25
Не верифицирован
29 май 2026, 05:25
565bfcd
Код
Авторство
О чём код?
import { URL_REG, DATA_URL_REG } from '../config' import { correctImageSrc } from '../utils/getImageInfo' // Browser-friendly replacement for Node's `url.fileURLToPath`. Renderer is // sandboxed so we can't reach Node's `url` module. const fileURLToPath = (input) => { if (!input) return '' let url try { url = typeof input === 'string' ? new URL(input) : input } catch { return String(input) } if (url.protocol !== 'file:') return String(input) let pathname = decodeURIComponent(url.pathname || '') // On Windows, file:///C:/foo → "/C:/foo"; strip the leading slash. if (/^\/[a-zA-Z]:/.test(pathname)) pathname = pathname.slice(1) return pathname } const imageCtrl = (ContentState) => { /** * insert inline image at the cursor position. */ ContentState.prototype.insertImage = function({ alt = '', src = '', title = '' }) { const match = /(?:\/|\\)?([^./\\]+)\.[a-z]+$/.exec(src) if (!alt) { alt = match && match[1] ? match[1] : '' } const { start, end } = this.cursor const { formats } = this.selectionFormats({ start, end }) const { key, offset: startOffset } = start const { offset: endOffset } = end const block = this.getBlock(key) if ( block.type === 'span' && (block.functionType === 'codeContent' || block.functionType === 'languageInput' || block.functionType === 'thematicBreakLine') ) { // You can not insert image into code block or language input... return } const { text } = block const imageFormat = formats.filter((f) => f.type === 'image') // Only encode URLs but not local paths or data URLs let imgUrl if (URL_REG.test(src)) { imgUrl = encodeURI(src) } else if (DATA_URL_REG.test(src)) { imgUrl = src } else { imgUrl = src.replace(/ /g, encodeURI(' ')).replace(/#/g, encodeURIComponent('#')) } let srcAndTitle = imgUrl if (srcAndTitle && title) { srcAndTitle += ` "${title}"` } if ( imageFormat.length === 1 && imageFormat[0].range.start !== startOffset && imageFormat[0].range.end !== endOffset ) { // Replace already existing image let imageAlt = alt // Extract alt from image if there isn't an image source already (GH#562). E.g: ![old-alt]() if (imageFormat[0].alt && !imageFormat[0].src) { imageAlt = imageFormat[0].alt } const { start, end } = imageFormat[0].range block.text = text.substring(0, start) + `` + text.substring(end) this.cursor = { start: { key, offset: start + 2 }, end: { key, offset: start + 2 + imageAlt.length }, isEdit: true } } else if (key !== end.key) { // Replace multi-line text const endBlock = this.getBlock(end.key) const { text } = endBlock endBlock.text = text.substring(0, endOffset) + `` + text.substring(endOffset) const offset = endOffset + 2 this.cursor = { start: { key: end.key, offset }, end: { key: end.key, offset: offset + alt.length }, isEdit: true } } else { // Replace single-line text const imageAlt = startOffset !== endOffset ? text.substring(startOffset, endOffset) : alt block.text = text.substring(0, start.offset) + `` + text.substring(end.offset) this.cursor = { start: { key, offset: startOffset + 2 }, end: { key, offset: startOffset + 2 + imageAlt.length }, isEdit: true } } this.partialRender() this.muya.dispatchChange() } ContentState.prototype.updateImage = function({ imageId, key, token }, attrName, attrValue) { // inline/left/center/right const block = this.getBlock(key) const { range } = token const { start, end } = range const oldText = block.text let imageText = '' const attrs = Object.assign({}, token.attrs) attrs[attrName] = attrValue imageText = '<img ' for (const attr of Object.keys(attrs)) { let value = attrs[attr] if (value && attr === 'src') { value = correctImageSrc(value) } imageText += `${attr}="${value}" ` } imageText = imageText.trim() imageText += '>' block.text = oldText.substring(0, start) + imageText + oldText.substring(end) this.singleRender(block, false) const image = document.querySelector(`#${imageId} img`) if (image) { this.cursor = { ...this.cursor, isEdit: true } // To trigger a history record image.click() return this.muya.dispatchChange() } } ContentState.prototype.replaceImage = function( { key, token }, { alt = '', src = '', title = '' } ) { const { type } = token const block = this.getBlock(key) const { start, end } = token.range const oldText = block.text let imageText = '' if (type === 'image') { imageText = ' { imageText += src.replace(/ /g, encodeURI(' ')).replace(/#/g, encodeURIComponent('#')) } if (title) { imageText += ` "${title}"` } imageText += ')' } else if (type === 'html_tag') { const { attrs } = token Object.assign(attrs, { alt, src, title }) imageText = '<img ' for (const attr of Object.keys(attrs)) { let value = attrs[attr] if (value && attr === 'src') { value = correctImageSrc(value) } imageText += `${attr}="${value}" ` } imageText = imageText.trim() imageText += '>' } block.text = oldText.substring(0, start) + imageText + oldText.substring(end) this.singleRender(block) this.cursor = { ...this.cursor, isEdit: true } // To trigger a history record return this.muya.dispatchChange() } ContentState.prototype.deleteImage = function({ key, token }) { const block = this.getBlock(key) const { eventCenter } = this.muya // The image block may have been removed by a prior render (e.g. a figure // replacement). Clear selection so backspace doesn't keep firing on the // stale reference — see issue #3950. if (!block) { this.selectedImage = null eventCenter.dispatch('muya-transformer', { reference: null }) eventCenter.dispatch('muya-image-toolbar', { reference: null }) return } const oldText = block.text const { start, end } = token.range block.text = oldText.substring(0, start) + oldText.substring(end) this.cursor = { start: { key, offset: start }, end: { key, offset: start }, isEdit: true } this.singleRender(block) // Hide image toolbar and image transformer eventCenter.dispatch('muya-transformer', { reference: null }) eventCenter.dispatch('muya-image-toolbar', { reference: null }) return this.muya.dispatchChange() } ContentState.prototype.selectImage = function(imageInfo) { this.selectedImage = imageInfo const { key } = imageInfo const block = this.getBlock(key) const outMostBlock = this.findOutMostBlock(block) this.cursor = { start: { key, offset: imageInfo.token.range.end }, end: { key, offset: imageInfo.token.range.end }, isEdit: false } // Fix #1568 const { start } = this.prevCursor const oldBlock = this.findOutMostBlock(this.getBlock(start.key)) if (oldBlock.key !== outMostBlock.key) { this.singleRender(oldBlock, false) } return this.singleRender(outMostBlock, true) } ContentState.prototype.openImage = function({ key, absoluteImagePath }) { if (!absoluteImagePath) return const block = this.getBlock(key) const { eventCenter } = this.muya if (this.muya.options.openImageWithExternalTool) { const path = fileURLToPath(absoluteImagePath) this.muya.options.openImageWithExternalTool(path) this.singleRender(block) eventCenter.dispatch('muya-transformer', { reference: null }) eventCenter.dispatch('muya-image-toolbar', { reference: null }) this.muya.dispatchChange() } } } export default imageCtrl