Add source editing toggle beside the outline button
This commit is contained in:
1 parent
73ff88c3c3
commit
15e036b9cd
7 files changed
+35
-9
No files matched your search
@@ -22,6 +22,7 @@ const paths = {
|
||||
palette: 'M12 22a10 10 0 1 1 10-10c0 2.8-2.2 4-4 4h-2a2 2 0 0 0-1.5 3.3A1.7 1.7 0 0 1 12 22zM7.5 10.5h.01M12 7.5h.01M16.5 10.5h.01',
|
||||
user: 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',
|
||||
outline: 'M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01',
|
||||
source: 'm8 6-6 6 6 6m8-12 6 6-6 6',
|
||||
command: 'M18 3a3 3 0 0 0-3 3v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 0 0 0-6z',
|
||||
info: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 11v6M12 7h.01',
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { nextTick, ref } from 'vue'
|
||||
import { commandTitle } from './commands'
|
||||
import Icon from './Icon.vue'
|
||||
import { noteTitle } from './paths'
|
||||
import { closeTab, ws } from './workspace'
|
||||
import { closeTab, toggleSourceMode, ws } from './workspace'
|
||||
|
||||
// 拖动排序:拖到某个标签的左半边插到它前面,右半边插到它后面
|
||||
const TAB_TYPE = 'application/x-rawmark-tab'
|
||||
@@ -103,9 +103,14 @@ function closeRight(path: string) {
|
||||
<Icon name="close" :size="12" />
|
||||
</button>
|
||||
</div>
|
||||
<button v-if="ws.active" class="rm-icon-btn rm-tabs-outline" :class="{ on: ws.outlineOpen }" :title="commandTitle('view.outline')" @click="ws.outlineOpen = !ws.outlineOpen">
|
||||
<div v-if="ws.active" class="rm-tabs-actions">
|
||||
<button class="rm-icon-btn" :title="ws.sourceMode ? '关闭源码编辑' : '开启源码编辑'" :aria-pressed="ws.sourceMode" @mousedown.prevent @click="toggleSourceMode">
|
||||
<Icon name="source" />
|
||||
</button>
|
||||
<button class="rm-icon-btn rm-tabs-outline" :class="{ on: ws.outlineOpen }" :title="commandTitle('view.outline')" @click="ws.outlineOpen = !ws.outlineOpen">
|
||||
<Icon name="outline" />
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="menu">
|
||||
<div class="rm-menu-backdrop" @click="menu = null" @contextmenu.prevent="menu = null" />
|
||||
<div ref="menuElement" class="rm-menu rm-tab-menu" :style="{ left: menu.x + 'px', top: menu.y + 'px' }">
|
||||
|
||||
@@ -18,7 +18,7 @@ import { noteTitle } from './paths'
|
||||
import { SIDEBAR_WIDTH, settings, updateSettings } from './settings'
|
||||
import Sidebar from './Sidebar.vue'
|
||||
import TabBar from './TabBar.vue'
|
||||
import { activeTab, flushAll, readyToReload, startWorkspace, stopWorkspace, toast, ws } from './workspace'
|
||||
import { activeTab, flushAll, readyToReload, startWorkspace, stopWorkspace, toast, toggleSourceMode, ws } from './workspace'
|
||||
|
||||
const emit = defineEmits<{ logout: [] }>()
|
||||
const title = computed(() => (ws.active ? noteTitle(ws.active) : 'RawMark'))
|
||||
@@ -110,6 +110,7 @@ onBeforeUnmount(() => {
|
||||
<button class="rm-icon-btn" title="目录" @click="ws.sidebarOpen = true"><Icon name="menu" :size="20" /></button>
|
||||
<button class="rm-mobile-title" title="切换笔记" @click="ws.switcher = ''">{{ title }}</button>
|
||||
<span class="rm-save-state" :class="{ on: saving }" />
|
||||
<button v-if="ws.active" class="rm-icon-btn" :title="ws.sourceMode ? '关闭源码编辑' : '开启源码编辑'" :aria-pressed="ws.sourceMode" @mousedown.prevent @click="toggleSourceMode"><Icon name="source" :size="20" /></button>
|
||||
<button v-if="ws.active" class="rm-icon-btn" title="大纲" @click="ws.outlineOpen = !ws.outlineOpen"><Icon name="outline" :size="20" /></button>
|
||||
<button class="rm-icon-btn" title="快速打开" @click="ws.switcher = ''"><Icon name="search" :size="20" /></button>
|
||||
<button class="rm-icon-btn" title="命令" @click="ws.switcher = '>'"><Icon name="command" :size="20" /></button>
|
||||
|
||||
+3
-2
@@ -55,6 +55,7 @@ button {
|
||||
flex: none;
|
||||
}
|
||||
.rm-icon-btn:hover { background: var(--rm-hover); color: var(--rm-text); }
|
||||
.rm-icon-btn[aria-pressed="true"] { background: var(--rm-active); color: var(--rm-accent-strong); }
|
||||
|
||||
.rm-splash,
|
||||
.rm-empty {
|
||||
@@ -284,8 +285,8 @@ button {
|
||||
}
|
||||
.rm-tabs.empty { display: none; }
|
||||
.rm-tabs-side,
|
||||
.rm-tabs-outline { flex: none; align-self: center; margin-bottom: 4px; }
|
||||
.rm-tabs-outline { margin-left: auto; position: sticky; right: 0; background: var(--rm-bg); }
|
||||
.rm-tabs-actions { flex: none; align-self: center; margin-bottom: 4px; }
|
||||
.rm-tabs-actions { display: flex; margin-left: auto; position: sticky; right: 0; background: var(--rm-bg); }
|
||||
.rm-tabs-outline.on { color: var(--rm-accent-strong); }
|
||||
.rm-tab.dragging { opacity: 0.5; }
|
||||
.rm-tab.drop-before { box-shadow: inset 2px 0 0 var(--rm-accent); }
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EditorView } from '@codemirror/view'
|
||||
import { nextTick, reactive, watch } from 'vue'
|
||||
import { editorCommands } from '../editor/shortcuts'
|
||||
import { refreshImages } from '../editor/livepreview'
|
||||
import { createEditorState, insertImageFiles, pasteImages, languageCompartment, pluginCompartment, pluginEditorExtensions, pluginLanguage, preferencesCompartment } from '../editor'
|
||||
import { createEditorState, insertImageFiles, pasteImages, languageCompartment, pluginCompartment, pluginEditorExtensions, pluginLanguage, preferencesCompartment, previewCompartment, previewExtensions } from '../editor'
|
||||
import { handleShortcut, registerCommand } from './commands'
|
||||
import { api, ApiError, encodePath, type Entry } from './api'
|
||||
import { drafts } from './drafts'
|
||||
@@ -73,6 +73,7 @@ export const ws = reactive({
|
||||
sidebarCollapsed: layout.sidebarCollapsed ?? false,
|
||||
// 大纲面板(电脑上在右侧常驻,手机上为浮层)
|
||||
outlineOpen: layout.outlineOpen ?? false,
|
||||
sourceMode: false,
|
||||
// 最近打开的笔记,最新的在前
|
||||
recent: JSON.parse(localStorage.getItem(RECENT_KEY) ?? '[]') as string[],
|
||||
toasts: [] as Toast[],
|
||||
@@ -149,6 +150,17 @@ export function editorView(): EditorView | null {
|
||||
return view
|
||||
}
|
||||
|
||||
export function toggleSourceMode() {
|
||||
ws.sourceMode = !ws.sourceMode
|
||||
const effects = previewCompartment.reconfigure(previewExtensions(ws.sourceMode))
|
||||
for (const [path, state] of editorStates) {
|
||||
if (path === ws.active && view) {
|
||||
view.dispatch({ effects })
|
||||
editorStates.set(path, view.state)
|
||||
} else editorStates.set(path, state.update({ effects }).state)
|
||||
}
|
||||
}
|
||||
|
||||
/** 从相册/文件中选择图片插入到光标处 */
|
||||
export function insertImagesFromPicker() {
|
||||
const v = view
|
||||
@@ -186,6 +198,7 @@ function buildState(path: string, doc: string): EditorState {
|
||||
shortcuts: [],
|
||||
spellcheck: settings.spellcheck,
|
||||
tabSize: settings.tabSize,
|
||||
sourceMode: ws.sourceMode,
|
||||
resolveImage: (src) => {
|
||||
const url = resolveAssetUrl(ref.path, src)
|
||||
const entry = ws.entries.find((e) => url === '/api/v1/raw/' + encodePath(e.path))
|
||||
@@ -1068,6 +1081,7 @@ function registerBuiltinCommands() {
|
||||
{ id: 'app.settings', title: '设置', keys: ['Mod+,'], run: () => (ws.settings = 'index') },
|
||||
{ id: 'view.sidebar', title: '显示/隐藏侧栏', keys: ['Mod+\\'], run: toggleSidebar },
|
||||
{ id: 'view.outline', title: '显示/隐藏大纲', keys: ['Mod+Shift+O'], when: hasNote, run: () => (ws.outlineOpen = !ws.outlineOpen) },
|
||||
{ id: 'view.source', title: '开启/关闭源码编辑', when: hasNote, run: toggleSourceMode },
|
||||
{ id: 'app.trash', title: '打开回收站', run: () => (ws.settings = 'trash') },
|
||||
{ id: 'note.share', title: '分享当前笔记', when: hasNote, run: () => (ws.sharing = ws.active) },
|
||||
{ id: 'app.images', title: '管理根目录图片', run: () => (ws.images = '') },
|
||||
|
||||
@@ -21,6 +21,10 @@ export { insertImageFiles, pasteImages } from './paste'
|
||||
export const pluginCompartment = new Compartment()
|
||||
export const languageCompartment = new Compartment()
|
||||
export const preferencesCompartment = new Compartment()
|
||||
export const previewCompartment = new Compartment()
|
||||
export function previewExtensions(sourceMode: boolean): Extension {
|
||||
return sourceMode ? EditorView.contentAttributes.of({ class: 'cm-rm-source' }) : [livePreview, tableEditing, renderedBlocks]
|
||||
}
|
||||
export function pluginLanguage() { return new Language(
|
||||
markdownLanguage.data,
|
||||
(markdownLanguage.parser as MarkdownParser).configure([typoraExtensions, ...[...markdownSyntax.values()].map((entry) => entry.value)]),
|
||||
@@ -33,6 +37,7 @@ export interface EditorStateOptions {
|
||||
doc: string
|
||||
spellcheck?: boolean
|
||||
tabSize?: number
|
||||
sourceMode?: boolean
|
||||
shortcuts?: KeyBinding[]
|
||||
resolveImage?: (src: string) => string
|
||||
// 文档变化回调,仅对挂载在视图上的状态触发;未激活标签页的状态由调用方直接更新
|
||||
@@ -54,11 +59,9 @@ export function createEditorState(opts: EditorStateOptions): EditorState {
|
||||
Prec.high(keymap.of(markdownKeymap)),
|
||||
Prec.high(keymap.of(opts.shortcuts ?? defaultEditorKeys)),
|
||||
markdownTheme,
|
||||
livePreview,
|
||||
codeBlockHighlight,
|
||||
editorFocus,
|
||||
tableEditing,
|
||||
renderedBlocks,
|
||||
previewCompartment.of(previewExtensions(opts.sourceMode ?? false)),
|
||||
// 可配置操作不再通过 CM 的默认绑定重复注册。
|
||||
keymap.of(defaultKeymap.filter((binding) => binding.run !== selectAll)),
|
||||
preferencesCompartment.of([
|
||||
|
||||
@@ -107,6 +107,7 @@ body {
|
||||
padding: 32px 24px 40vh;
|
||||
caret-color: #ff0000;
|
||||
}
|
||||
.cm-editor .cm-content.cm-rm-source { font-family: var(--rm-font-mono); }
|
||||
.cm-editor .cm-cursor {
|
||||
border-left: 2px solid #ff0000;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user