mirror of
https://github.com/wanglin2/obsidian-simplemindmap.git
synced 2026-07-22 18:20:28 +00:00
882 lines
24 KiB
JavaScript
882 lines
24 KiB
JavaScript
import { Notice, TextFileView, TFile, setIcon, Platform } from 'obsidian'
|
|
import {
|
|
SMM_VIEW_TYPE,
|
|
SAVE_ICON,
|
|
IMPORT_ICON,
|
|
EXPORT_ICON,
|
|
SMM_TAG,
|
|
OUTLINE_ICON,
|
|
MINDMAP_ICON,
|
|
PRINT_ICON,
|
|
getSmmEditViewCount
|
|
} from './ob/constant.js'
|
|
import { initApp } from './src/main.js'
|
|
import {
|
|
assembleMarkdownText,
|
|
parseMarkdownText,
|
|
createDefaultText
|
|
} from './ob/metadataAndMarkdown.js'
|
|
import {
|
|
hideTargetMenu,
|
|
smmFilePathToFileName,
|
|
formatFileName
|
|
} from './ob/utils.js'
|
|
import LZString from 'lz-string'
|
|
import { base64ToFile } from './src/utils'
|
|
import ModifyFileName from './ob/ModifyFileName.js'
|
|
|
|
class SmmEditView extends TextFileView {
|
|
constructor(leaf, plugin) {
|
|
super(leaf)
|
|
this.plugin = plugin
|
|
this.contentEl.style.padding = 0
|
|
this.warpEl = this.contentEl.createDiv('smmMindmapEditContainer')
|
|
this.warpEl.style.cssText = `
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
`
|
|
this.mindMapData = ''
|
|
this.parsedMindMapData = null
|
|
this.mindMapAPP = null
|
|
|
|
this.isActive = true
|
|
this.resizeObserver = null
|
|
this.isUnSave = false
|
|
this.isOuterChange = false
|
|
this.isNotTriggerDataChange = false
|
|
this.isHidden = false
|
|
this.needResize = false
|
|
this.isOutlineEditMode = false
|
|
this.saveButton = null
|
|
this.exportButton = null
|
|
this.importButton = null
|
|
this.printOutlineButton = null
|
|
this.changeToOutlineButton = null
|
|
this.changeToMindmapButton = null
|
|
this.savingTipEl = null
|
|
this.isReadonlyMode = false
|
|
this.toggleReadonlyButton = null
|
|
this.modifyFileName = new ModifyFileName(this)
|
|
this.popScope = null
|
|
this.id = getSmmEditViewCount()
|
|
}
|
|
|
|
getViewType() {
|
|
return SMM_VIEW_TYPE
|
|
}
|
|
|
|
getIcon() {
|
|
return 'smm-icon'
|
|
}
|
|
|
|
onPaneMenu(menu, source) {
|
|
super.onPaneMenu(menu, source)
|
|
hideTargetMenu(menu, '在新窗口中打开')
|
|
hideTargetMenu(menu, '移动至新窗口')
|
|
}
|
|
|
|
async switchToMarkdownView() {
|
|
if (!this.file || !this.leaf) return
|
|
const mdViewType = this.app.viewRegistry.getTypeByExtension('md')
|
|
await this.leaf.setViewState({
|
|
type: mdViewType,
|
|
state: {
|
|
...this.leaf.getViewState().state,
|
|
isSwitchToMarkdownViewFromSmmView: true
|
|
},
|
|
popstate: true
|
|
})
|
|
}
|
|
|
|
async onOpen() {
|
|
this.registerEvent(
|
|
this.app.vault.on('modify', this._handleExternalChange.bind(this))
|
|
)
|
|
// 注册激活状态监听
|
|
this.registerEvent(
|
|
this.app.workspace.on(
|
|
'active-leaf-change',
|
|
this._handleActiveChange.bind(this)
|
|
)
|
|
)
|
|
this.registerEvent(
|
|
this.app.workspace.on('resize', this._handleResize.bind(this))
|
|
)
|
|
|
|
this._addActionBtns()
|
|
|
|
this._initThemeMode()
|
|
|
|
this.modifyFileName.setupTitleEditing()
|
|
this._registerHotkeyOverrides()
|
|
}
|
|
|
|
getViewData() {
|
|
return this.mindMapData
|
|
}
|
|
|
|
async setViewData(data, isClear) {
|
|
if (this.isHidden) {
|
|
return
|
|
}
|
|
if (isClear) {
|
|
this.clear()
|
|
}
|
|
let rawData = data.trim()
|
|
try {
|
|
if (!rawData) {
|
|
throw new Error(this.plugin._t('tip.fileIsEmpty'))
|
|
} else {
|
|
this.parsedMindMapData = parseMarkdownText(rawData)
|
|
const content = this.parsedMindMapData.metadata.content
|
|
if (content) {
|
|
let passedContent = ''
|
|
try {
|
|
// 兼容未编码的老版数据
|
|
const json = JSON.parse(content)
|
|
if (json && json.root) {
|
|
passedContent = content
|
|
}
|
|
} catch (error) {}
|
|
if (!passedContent) {
|
|
this.parsedMindMapData.metadata.content = LZString.decompressFromBase64(
|
|
content
|
|
)
|
|
}
|
|
} else {
|
|
throw new Error('文件格式错误')
|
|
}
|
|
}
|
|
} catch (error) {
|
|
rawData = createDefaultText(
|
|
'',
|
|
this.plugin._getCreateDefaultMindMapOptions()
|
|
)
|
|
}
|
|
this.mindMapData = rawData
|
|
if (!this.parsedMindMapData) {
|
|
this.parsedMindMapData = parseMarkdownText(rawData)
|
|
this.parsedMindMapData.metadata.content = LZString.decompressFromBase64(
|
|
this.parsedMindMapData.metadata.content
|
|
)
|
|
}
|
|
if (isClear) {
|
|
this._renderMindMap()
|
|
} else if (this.mindMapAPP) {
|
|
this.isNotTriggerDataChange = true
|
|
this.mindMapAPP.$bus.$emit(
|
|
'updateMindMapDataFromOb',
|
|
this.parsedMindMapData.metadata.content
|
|
)
|
|
}
|
|
}
|
|
|
|
_clearObserver() {
|
|
if (this.resizeObserver) {
|
|
this.resizeObserver.disconnect()
|
|
this.resizeObserver = null
|
|
}
|
|
}
|
|
|
|
_renderMindMap() {
|
|
this.warpEl.empty()
|
|
const el = this.warpEl.createDiv('smmMindMapEdit')
|
|
el.style.cssText = `
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
`
|
|
this._clearObserver()
|
|
this.resizeObserver = new ResizeObserver(entries => {
|
|
for (const entry of entries) {
|
|
const { width, height } = entry.contentRect
|
|
if (width > 10 && height > 10) {
|
|
this._initializeMindMap(el)
|
|
this._clearObserver()
|
|
break
|
|
}
|
|
}
|
|
})
|
|
this.resizeObserver.observe(el)
|
|
}
|
|
|
|
_initializeMindMap(el) {
|
|
if (this.mindMapAPP) return
|
|
const filePath = this.file?.path
|
|
let initLocationNodeId = ''
|
|
const { fileToSubpathMap } = this.plugin
|
|
if (fileToSubpathMap && fileToSubpathMap[filePath]) {
|
|
initLocationNodeId = fileToSubpathMap[filePath]
|
|
delete fileToSubpathMap[filePath]
|
|
}
|
|
this.mindMapAPP = initApp(el, {
|
|
getInitMindMapData: () => {
|
|
return this.parsedMindMapData.metadata.content
|
|
},
|
|
getInitLocationNodeId: () => {
|
|
return initLocationNodeId
|
|
},
|
|
getMindMapCurrentData: (content, linkData, textData) => {
|
|
this.parsedMindMapData.metadata.content = content
|
|
const {
|
|
metadata,
|
|
svgdata,
|
|
svgdataUpdateAt,
|
|
linkdata
|
|
} = this.parsedMindMapData
|
|
this.mindMapData = assembleMarkdownText({
|
|
metadata: {
|
|
path: `${this.file?.path}`,
|
|
tags: Array.from(new Set([SMM_TAG, ...metadata.tags])),
|
|
content: LZString.compressToBase64(content),
|
|
yaml: metadata.yaml
|
|
},
|
|
svgdata,
|
|
svgdataUpdateAt,
|
|
linkdata: linkData || linkdata || [],
|
|
textdata: textData || []
|
|
})
|
|
},
|
|
saveMindMapData: async (_, svgData) => {
|
|
try {
|
|
this._showSavingTip()
|
|
const { embedImageIsSeparateFile } = this.plugin.settings
|
|
if (svgData) {
|
|
if (embedImageIsSeparateFile) {
|
|
const res = await this._getFileSvgData(svgData)
|
|
if (res) {
|
|
this.parsedMindMapData.svgdata = `![[${res}]]`
|
|
this.parsedMindMapData.svgdataUpdateAt = moment().format(
|
|
'YYYY-MM-DD HH:mm:ss'
|
|
)
|
|
} else {
|
|
new Notice(this.plugin._t('tip.imgCreateFail'))
|
|
}
|
|
} else {
|
|
this.parsedMindMapData.svgdata = LZString.compressToBase64(
|
|
svgData
|
|
)
|
|
}
|
|
}
|
|
this.forceSave()
|
|
} catch (error) {}
|
|
},
|
|
getMindMapConfig: () => {
|
|
return this.plugin.settings.mindMapConfig || {}
|
|
},
|
|
saveMindMapConfig: newConfig => {
|
|
this.plugin.settings.mindMapConfig = newConfig
|
|
this.plugin._saveSettings()
|
|
},
|
|
getMindMapLocalConfig: () => {
|
|
const { mindMapLocalConfig, themeMode } = this.plugin.settings
|
|
const config = mindMapLocalConfig || {}
|
|
config.isDark =
|
|
themeMode === 'follow'
|
|
? this.plugin._getObIsDark()
|
|
: themeMode === 'dark'
|
|
return config
|
|
},
|
|
saveMindMapLocalConfig: newConfig => {
|
|
this.plugin.settings.mindMapLocalConfig = newConfig
|
|
this.plugin._saveSettings()
|
|
},
|
|
getSettings: () => {
|
|
return this.plugin.settings || {}
|
|
},
|
|
updateSettings: data => {
|
|
this.plugin.settings = {
|
|
...this.plugin.settings,
|
|
...data
|
|
}
|
|
this.plugin._saveSettings()
|
|
},
|
|
getObAllFiles: (extraSelf = true) => {
|
|
const fileList = this.app.vault.getFiles()
|
|
if (extraSelf && this.file) {
|
|
return fileList.filter(item => {
|
|
return item.path !== this.file.path
|
|
})
|
|
}
|
|
return fileList
|
|
},
|
|
createLinkInfoFromFilePath: (filePath, subpath = '', alias = '') => {
|
|
const file = this.tryGetFileByPath(filePath)
|
|
if (!file) {
|
|
return null
|
|
}
|
|
const linkText = this.app.fileManager.generateMarkdownLink(
|
|
file,
|
|
this.file?.path || '',
|
|
subpath,
|
|
alias
|
|
)
|
|
return {
|
|
link: file.path,
|
|
linkText
|
|
}
|
|
},
|
|
openFile: (filePath, isNewTab = false, createOnNoExist = false) => {
|
|
if (!filePath) return
|
|
const arr = filePath.split(/(#|^|\|)/)
|
|
filePath = arr[0]
|
|
let postfix = ''
|
|
if (arr.length > 1 && arr[1] !== '|') {
|
|
postfix = arr.slice(1).join('')
|
|
}
|
|
const file = this.tryGetFileByPath(filePath)
|
|
if (file && file instanceof TFile) {
|
|
this.app.workspace.openLinkText(
|
|
file.path + postfix,
|
|
this.file?.path || '',
|
|
isNewTab
|
|
)
|
|
} else if (createOnNoExist) {
|
|
this.app.workspace.openLinkText(
|
|
/\.md$/.test(filePath) ? filePath : filePath + '.md',
|
|
this.file?.path || '',
|
|
isNewTab
|
|
)
|
|
}
|
|
},
|
|
openWebLink: url => {
|
|
if (this.app.openExternal) {
|
|
this.app.openExternal(url)
|
|
return
|
|
}
|
|
try {
|
|
if (window.require) {
|
|
const { shell } = window.require('electron')
|
|
shell.openExternal(url)
|
|
return
|
|
}
|
|
} catch (e) {}
|
|
window.open(url, '_blank', 'noopener,noreferrer')
|
|
},
|
|
saveFileToVault: async (...args) => {
|
|
const res = await this.saveFileToVault(...args)
|
|
return res
|
|
},
|
|
getResourcePath: vaultPath => {
|
|
return this.app.vault.adapter.getResourcePath(vaultPath)
|
|
},
|
|
getObInternalLink: (...args) => {
|
|
return this.app.fileManager.generateMarkdownLink(this.file, '', ...args)
|
|
},
|
|
showTip: tip => {
|
|
new Notice(tip)
|
|
},
|
|
getFileFromUri: uri => {
|
|
const params = new URLSearchParams(uri.split('?')[1])
|
|
const vault = decodeURIComponent(params.get('vault') || '')
|
|
const filePath = decodeURIComponent(params.get('file') || '')
|
|
if (!vault || !filePath) return null
|
|
if (this.app.vault.getName() !== vault) {
|
|
new Notice(this.plugin._t('tip.onlyEnableSelectCurrentVaultFile'))
|
|
return null
|
|
}
|
|
return this.tryGetFileByPath(filePath)
|
|
},
|
|
isMobile: () => {
|
|
return Platform.isMobile
|
|
},
|
|
getObsidianApp: () => {
|
|
return this.app
|
|
},
|
|
getFormatFileName: (str, ignores = []) => {
|
|
return formatFileName(str, {
|
|
notename: this.file?.basename.split('.')[0],
|
|
ignores
|
|
})
|
|
}
|
|
})
|
|
setTimeout(() => {
|
|
this._listenMindMapEvent()
|
|
}, 100)
|
|
}
|
|
|
|
tryGetFileByPath(filePath) {
|
|
let res = this.app.vault.getFileByPath(filePath)
|
|
if (!res) {
|
|
res = this.app.vault.getFileByPath(filePath + '.md')
|
|
}
|
|
if (!res) {
|
|
res = this.app.metadataCache.getFirstLinkpathDest(
|
|
filePath,
|
|
this.file?.path
|
|
)
|
|
}
|
|
return res
|
|
}
|
|
|
|
async _getFileSvgData(svgData) {
|
|
const targetFileName = smmFilePathToFileName(this.file?.path, '.svg')
|
|
const { embedImageIsSeparateFileFolder } = this.plugin.settings
|
|
const originSvgData = this.parsedMindMapData.svgdata
|
|
if (originSvgData && /^!\[\[/.test(originSvgData)) {
|
|
const match = originSvgData.match(/!\[\[([^\]]+)\]\]/)
|
|
if (match && match[1]) {
|
|
const filePath = match[1]
|
|
const fileName = filePath.split('/').pop()
|
|
if (fileName !== targetFileName) {
|
|
const exist = await this.app.vault.exists(filePath)
|
|
if (exist) {
|
|
await this.app.vault.adapter.remove(filePath)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
svgData = base64ToFile(svgData, targetFileName)
|
|
const res = await this.saveFileToVault(
|
|
svgData,
|
|
true,
|
|
embedImageIsSeparateFileFolder,
|
|
true
|
|
)
|
|
return res
|
|
}
|
|
|
|
async saveFileToVault(file, isImg = true, targetFolder = '', cover = false) {
|
|
try {
|
|
let folder = targetFolder || ''
|
|
if (!folder) {
|
|
folder = isImg
|
|
? this._getFileSavePath('imagePathType', 'imagePath', 'imageSubPath')
|
|
: this._getFileSavePath(
|
|
'attachmentPathType',
|
|
'attachmentPath',
|
|
'attachmentSubPath'
|
|
)
|
|
}
|
|
await this.plugin._ensureDirectoryExists(folder)
|
|
let fileName = file.name
|
|
let fileExist = false
|
|
if (!cover) {
|
|
let counter = 1
|
|
while (
|
|
await this.app.vault.adapter.exists(
|
|
folder ? `${folder}/${fileName}` : fileName
|
|
)
|
|
) {
|
|
const parts = file.name.split('.')
|
|
const ext = parts.pop()
|
|
fileName = `${parts.join('.')}_${counter}.${ext}`
|
|
counter++
|
|
}
|
|
} else {
|
|
fileExist = await this.app.vault.adapter.exists(
|
|
folder ? `${folder}/${fileName}` : fileName
|
|
)
|
|
}
|
|
// 读取文件内容
|
|
const arrayBuffer = await file.arrayBuffer()
|
|
// 在vault中创建路径
|
|
const vaultPath = folder ? `${folder}/${fileName}` : fileName
|
|
// 写入文件
|
|
if (fileExist) {
|
|
await app.vault.adapter.writeBinary(vaultPath, arrayBuffer)
|
|
} else {
|
|
await this.app.vault.createBinary(vaultPath, arrayBuffer)
|
|
}
|
|
return vaultPath
|
|
} catch (error) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
jumpToNodeByUid(uid) {
|
|
if (!this.mindMapAPP) {
|
|
return
|
|
}
|
|
this.mindMapAPP.$bus.$emit('jumpToNodeByUid', uid)
|
|
}
|
|
|
|
async save(ignoreActiveCheck = false) {
|
|
if (!ignoreActiveCheck && !this.isActive) {
|
|
return
|
|
}
|
|
if (this.app.noSaveOnClose) return
|
|
const t = Date.now()
|
|
if (this.mindMapAPP) {
|
|
this.mindMapAPP.$bus.$emit('getMindMapCurrentData')
|
|
this.mindMapAPP.$bus.$emit('clearAutoSave')
|
|
}
|
|
if (!this.mindMapData) {
|
|
return
|
|
}
|
|
await super.save()
|
|
this._setIsUnSave(false)
|
|
this._hideSavingTip()
|
|
}
|
|
|
|
forceSave(ignoreActiveCheck = false) {
|
|
this._showSavingTip()
|
|
this.save(ignoreActiveCheck)
|
|
}
|
|
|
|
forceSaveAndUpdateImage() {
|
|
this._showSavingTip()
|
|
this.mindMapAPP.$bus.$emit('saveToLocal', true, true)
|
|
}
|
|
|
|
_listenMindMapEvent() {
|
|
this._onDataChange = this._onDataChange.bind(this)
|
|
this.mindMapAPP.$bus.$on('data_change', this._onDataChange)
|
|
}
|
|
|
|
_onDataChange() {
|
|
if (this.isNotTriggerDataChange) {
|
|
this.isNotTriggerDataChange = false
|
|
return
|
|
}
|
|
this._setIsUnSave(true)
|
|
}
|
|
|
|
_handleResize() {
|
|
if (!this.mindMapAPP || this.needResize) {
|
|
return
|
|
}
|
|
if (this.isHidden) {
|
|
this.needResize = true
|
|
return
|
|
}
|
|
try {
|
|
if (this.mindMapAPP.$updateMindMapSize) {
|
|
const { width, height } = this.warpEl.getBoundingClientRect()
|
|
if (width > 0 && height > 0) {
|
|
this.mindMapAPP.$updateMindMapSize()
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
|
|
async _handleActiveChange(leaf) {
|
|
const nowActive = leaf?.view === this
|
|
if (nowActive && !this.isActive) {
|
|
this._registerHotkeyOverrides()
|
|
this.isActive = true
|
|
if (this.mindMapAPP) {
|
|
if (this.isHidden) {
|
|
this.isHidden = false
|
|
this._checkForExternalChanges()
|
|
}
|
|
// 更新尺寸
|
|
if (this.needResize) {
|
|
this.needResize = false
|
|
this._handleResize()
|
|
}
|
|
if (this.isOuterChange) {
|
|
this.isOuterChange = false
|
|
}
|
|
}
|
|
} else if (!nowActive && this.isActive) {
|
|
this._clearPopScope()
|
|
const rect = this.warpEl.getBoundingClientRect()
|
|
this.isHidden = rect.width === 0 || rect.height === 0
|
|
if (this.mindMapAPP) {
|
|
this.mindMapAPP.$bus.$emit('obTabDeactivate')
|
|
this.save()
|
|
this.mindMapAPP.$clearUpdateMindMapSize()
|
|
}
|
|
this.isActive = false
|
|
}
|
|
}
|
|
|
|
async _handleExternalChange(file) {
|
|
if (this.isActive) return
|
|
if (file.path === this.file?.path) {
|
|
this.isOuterChange = true
|
|
}
|
|
}
|
|
|
|
async _checkForExternalChanges() {
|
|
if (!this.file) return
|
|
try {
|
|
const data = await this.app.vault.read(this.file)
|
|
await this.setViewData(data, false)
|
|
} catch (e) {}
|
|
}
|
|
|
|
clear() {
|
|
this._clearObserver()
|
|
if (this.mindMapAPP) {
|
|
try {
|
|
this.mindMapAPP.$destroy()
|
|
} catch (e) {}
|
|
this.mindMapAPP = null
|
|
}
|
|
this.warpEl.empty()
|
|
this.mindMapData = ''
|
|
this.parsedMindMapData = null
|
|
this.isUnSave = false
|
|
this.isOuterChange = false
|
|
this.isNotTriggerDataChange = false
|
|
this.isHidden = false
|
|
this.needResize = false
|
|
this._resetOnOutlineEditMode()
|
|
}
|
|
|
|
async onClose() {
|
|
this._clearPopScope()
|
|
this.modifyFileName.unload()
|
|
await this.save()
|
|
this.clear()
|
|
this.saveButton = null
|
|
this.exportButton = null
|
|
this.importButton = null
|
|
this.printOutlineButton = null
|
|
this.changeToOutlineButton = null
|
|
this.changeToMindmapButton = null
|
|
this.toggleReadonlyButton = null
|
|
}
|
|
|
|
_debounce(func, wait) {
|
|
let timeout
|
|
return () => {
|
|
clearTimeout(timeout)
|
|
timeout = setTimeout(() => {
|
|
func.call(this)
|
|
}, wait)
|
|
}
|
|
}
|
|
|
|
_addActionBtns() {
|
|
const viewActions = this.headerEl.querySelector('.view-actions')
|
|
|
|
this.toggleReadonlyButton = this._createActionBtn(
|
|
this.plugin._t('action.changeToReadonly'),
|
|
'book-open',
|
|
viewActions,
|
|
async () => {
|
|
await this.save()
|
|
if (this.isReadonlyMode) {
|
|
this.isReadonlyMode = false
|
|
this.mindMapAPP.$bus.$emit('toggleReadonly', false)
|
|
setIcon(this.toggleReadonlyButton, 'book-open')
|
|
this.toggleReadonlyButton.setAttribute(
|
|
'aria-label',
|
|
this.plugin._t('action.changeToReadonly')
|
|
)
|
|
this._showButtons([
|
|
this.saveButton,
|
|
this.importButton,
|
|
this.exportButton
|
|
])
|
|
} else {
|
|
this.isReadonlyMode = true
|
|
this.mindMapAPP.$bus.$emit('toggleReadonly', true)
|
|
setIcon(this.toggleReadonlyButton, 'edit-3')
|
|
this.toggleReadonlyButton.setAttribute(
|
|
'aria-label',
|
|
this.plugin._t('action.changeToEdit')
|
|
)
|
|
this._hideButtons([
|
|
this.saveButton,
|
|
this.importButton,
|
|
this.exportButton
|
|
])
|
|
}
|
|
},
|
|
true
|
|
)
|
|
|
|
this.changeToOutlineButton = this._createActionBtn(
|
|
this.plugin._t('action.changeToOutline'),
|
|
OUTLINE_ICON,
|
|
viewActions,
|
|
async () => {
|
|
await this.save()
|
|
this._hideButtons([this.exportButton, this.changeToOutlineButton])
|
|
this._showButtons([this.printOutlineButton, this.changeToMindmapButton])
|
|
// 保存按钮文案修改
|
|
this.saveButton.setAttribute('aria-label', '保存')
|
|
this.mindMapAPP.$bus.$emit('showOutlineEdit')
|
|
this.isOutlineEditMode = true
|
|
}
|
|
)
|
|
|
|
this.changeToMindmapButton = this._createActionBtn(
|
|
this.plugin._t('action.changeToMindMap'),
|
|
MINDMAP_ICON,
|
|
viewActions,
|
|
async () => {
|
|
await this.save()
|
|
this.mindMapAPP.$bus.$emit('hideOutlineEdit')
|
|
this._resetOnOutlineEditMode()
|
|
}
|
|
)
|
|
this._hideButtons([this.changeToMindmapButton])
|
|
|
|
this.printOutlineButton = this._createActionBtn(
|
|
this.plugin._t('action.printOutline'),
|
|
PRINT_ICON,
|
|
viewActions,
|
|
async () => {
|
|
this.mindMapAPP.$bus.$emit('printOutline')
|
|
}
|
|
)
|
|
this._hideButtons([this.printOutlineButton])
|
|
|
|
this.importButton = this._createActionBtn(
|
|
this.plugin._t('action.import'),
|
|
IMPORT_ICON,
|
|
viewActions,
|
|
() => {
|
|
this.mindMapAPP.$bus.$emit('showImport')
|
|
}
|
|
)
|
|
|
|
if (!Platform.isMobile) {
|
|
this.exportButton = this._createActionBtn(
|
|
this.plugin._t('action.export'),
|
|
EXPORT_ICON,
|
|
viewActions,
|
|
() => {
|
|
this.mindMapAPP.$bus.$emit('showExport')
|
|
}
|
|
)
|
|
}
|
|
|
|
// 保存并更新图像数据
|
|
this.saveButton = this._createActionBtn(
|
|
this.plugin._t('action.saveAndUpdateImage'),
|
|
SAVE_ICON,
|
|
viewActions,
|
|
() => {
|
|
this.forceSaveAndUpdateImage()
|
|
}
|
|
)
|
|
|
|
this.savingTipEl = this.headerEl.createEl('div', {
|
|
cls: 'smm-save-tip'
|
|
})
|
|
viewActions.insertBefore(this.savingTipEl, viewActions.firstChild)
|
|
}
|
|
|
|
_showSavingTip() {
|
|
if (!this.savingTipEl) return
|
|
this.savingTipEl.innerText = this.plugin._t('tip.saving')
|
|
}
|
|
|
|
_hideSavingTip() {
|
|
if (!this.savingTipEl) return
|
|
this.savingTipEl.innerText = ''
|
|
}
|
|
|
|
_createActionBtn(label, icon, parentEl, onClick, isObIcon = false) {
|
|
const el = this.headerEl.createEl('button', {
|
|
cls: 'clickable-icon'
|
|
})
|
|
el.setAttribute('aria-label', label)
|
|
if (isObIcon) {
|
|
setIcon(el, icon, 24)
|
|
} else {
|
|
el.innerHTML = icon
|
|
}
|
|
parentEl.insertBefore(el, parentEl.firstChild)
|
|
el.addEventListener('click', onClick)
|
|
return el
|
|
}
|
|
|
|
_resetOnOutlineEditMode() {
|
|
if (this.isOutlineEditMode) {
|
|
this.saveButton.setAttribute(
|
|
'aria-label',
|
|
this.plugin._t('action.saveAndUpdateImage')
|
|
)
|
|
this._showButtons([this.exportButton, this.changeToOutlineButton])
|
|
this._hideButtons([this.changeToMindmapButton, this.printOutlineButton])
|
|
this.isOutlineEditMode = false
|
|
}
|
|
}
|
|
|
|
_showButtons(list) {
|
|
list.forEach(el => {
|
|
if (!el) return
|
|
el.style.display = 'flex'
|
|
})
|
|
}
|
|
|
|
_hideButtons(list) {
|
|
list.forEach(el => {
|
|
if (!el) return
|
|
el.style.display = 'none'
|
|
})
|
|
}
|
|
|
|
_setIsUnSave(isUnSave) {
|
|
this.isUnSave = isUnSave
|
|
this.saveButton.style.color = isUnSave ? '#ff6600' : 'var(--icon-color)'
|
|
}
|
|
|
|
_initThemeMode() {
|
|
this._updateThemeMode()
|
|
|
|
this.registerEvent(
|
|
this.app.workspace.on('css-change', () => {
|
|
this._updateThemeMode()
|
|
})
|
|
)
|
|
}
|
|
|
|
_updateThemeMode() {
|
|
if (this.plugin.settings.themeMode === 'follow') {
|
|
const isDarkMode = this.plugin._getObIsDark()
|
|
if (this.mindMapAPP) {
|
|
this.mindMapAPP.$bus.$emit('setIsDark', isDarkMode)
|
|
}
|
|
}
|
|
}
|
|
|
|
_getFileSavePath(typeKey, pathKey, subPathKey) {
|
|
const type = this.plugin.settings[typeKey]
|
|
const folder = this.plugin.settings[pathKey] || ''
|
|
const subFolder = this.plugin.settings[subPathKey] || ''
|
|
switch (type) {
|
|
case 'root':
|
|
return ''
|
|
case 'custom':
|
|
return folder
|
|
case 'currentFileFolder':
|
|
return this.plugin._getActiveFileDirectory()
|
|
case 'currentFileFolderSubFolder':
|
|
const activeFileDirectory = this.plugin._getActiveFileDirectory()
|
|
return [activeFileDirectory, subFolder].filter(Boolean).join('/')
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
_clearPopScope() {
|
|
if (this.popScope) {
|
|
this.popScope()
|
|
this.popScope = null
|
|
}
|
|
}
|
|
|
|
_registerHotkeyOverrides() {
|
|
this._clearPopScope()
|
|
const scope = this.app.keymap.getRootScope()
|
|
const ctrlFHandler = scope.register(['Mod'], 'f', evt => {
|
|
evt.preventDefault()
|
|
return true
|
|
})
|
|
const ctrlSHandler = scope.register(['Mod'], 's', evt => {
|
|
evt.preventDefault()
|
|
const view = this.plugin._getActiveSmmView()
|
|
if (view) {
|
|
view.forceSave()
|
|
}
|
|
return true
|
|
})
|
|
scope.keys.unshift(scope.keys.pop())
|
|
scope.keys.unshift(scope.keys.pop())
|
|
this.popScope = () => {
|
|
scope.unregister(ctrlFHandler)
|
|
scope.unregister(ctrlSHandler)
|
|
}
|
|
}
|
|
}
|
|
|
|
export default SmmEditView
|