完善预览功能
This commit is contained in:
@@ -5,7 +5,7 @@ This template should help get you started developing with Vue 3 and TypeScript i
|
|||||||
## Recommended IDE Setup
|
## Recommended IDE Setup
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$env:HTTPS_PROXY="http://127.0.0.1:10808"
|
$env:HTTPS_PROXY="http://127.0.0.1:10808"
|
||||||
|
|
||||||
|
hello
|
||||||
|
|||||||
25
src/App.vue
25
src/App.vue
@@ -18,6 +18,7 @@ type SyncProvider = "none" | "webdav" | "s3";
|
|||||||
const DEFAULT_DOCUMENT_MODE_KEY = "yurou:default-document-mode";
|
const DEFAULT_DOCUMENT_MODE_KEY = "yurou:default-document-mode";
|
||||||
const APP_THEME_KEY = "yurou:app-theme";
|
const APP_THEME_KEY = "yurou:app-theme";
|
||||||
const RENDER_SINGLE_LINE_BREAKS_KEY = "yurou:render-single-line-breaks";
|
const RENDER_SINGLE_LINE_BREAKS_KEY = "yurou:render-single-line-breaks";
|
||||||
|
const PARAGRAPH_INDENT_KEY = "yurou:paragraph-indent";
|
||||||
const MARKDOWN_EXTENSIONS = new Set(["md"]);
|
const MARKDOWN_EXTENSIONS = new Set(["md"]);
|
||||||
const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "avif"]);
|
const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "avif"]);
|
||||||
const VIDEO_EXTENSIONS = new Set(["mp4", "webm", "mov", "m4v", "ogv"]);
|
const VIDEO_EXTENSIONS = new Set(["mp4", "webm", "mov", "m4v", "ogv"]);
|
||||||
@@ -124,6 +125,21 @@ function saveRenderSingleLineBreaks(value: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadParagraphIndent() {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(PARAGRAPH_INDENT_KEY) === "true";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveParagraphIndent(value: boolean) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(PARAGRAPH_INDENT_KEY, String(value));
|
||||||
|
} catch { /* noop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SyncConfig {
|
interface SyncConfig {
|
||||||
provider: SyncProvider;
|
provider: SyncProvider;
|
||||||
webdav: {
|
webdav: {
|
||||||
@@ -230,6 +246,7 @@ const defaultDocumentMode = ref<DocumentMode>(loadDefaultDocumentMode());
|
|||||||
const documentMode = ref<DocumentMode>(defaultDocumentMode.value);
|
const documentMode = ref<DocumentMode>(defaultDocumentMode.value);
|
||||||
const appTheme = ref<AppTheme>(loadAppTheme());
|
const appTheme = ref<AppTheme>(loadAppTheme());
|
||||||
const renderSingleLineBreaks = ref(loadRenderSingleLineBreaks());
|
const renderSingleLineBreaks = ref(loadRenderSingleLineBreaks());
|
||||||
|
const paragraphIndent = ref(loadParagraphIndent());
|
||||||
const showMoreMenu = ref(false);
|
const showMoreMenu = ref(false);
|
||||||
const settingsRequest = ref(0);
|
const settingsRequest = ref(0);
|
||||||
const settingsInitialPage = ref<"about" | "editor" | "sync">("about");
|
const settingsInitialPage = ref<"about" | "editor" | "sync">("about");
|
||||||
@@ -667,6 +684,11 @@ function updateRenderSingleLineBreaks(value: boolean) {
|
|||||||
saveRenderSingleLineBreaks(value);
|
saveRenderSingleLineBreaks(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateParagraphIndent(value: boolean) {
|
||||||
|
paragraphIndent.value = value;
|
||||||
|
saveParagraphIndent(value);
|
||||||
|
}
|
||||||
|
|
||||||
function resetWorkspaceAndEditorState(options: { stopWatcher?: boolean } = {}) {
|
function resetWorkspaceAndEditorState(options: { stopWatcher?: boolean } = {}) {
|
||||||
if (options.stopWatcher !== false) {
|
if (options.stopWatcher !== false) {
|
||||||
void stopWorkspaceWatcher();
|
void stopWorkspaceWatcher();
|
||||||
@@ -1269,6 +1291,7 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
|||||||
:default-document-mode="defaultDocumentMode"
|
:default-document-mode="defaultDocumentMode"
|
||||||
:app-theme="appTheme"
|
:app-theme="appTheme"
|
||||||
:render-single-line-breaks="renderSingleLineBreaks"
|
:render-single-line-breaks="renderSingleLineBreaks"
|
||||||
|
:paragraph-indent="paragraphIndent"
|
||||||
:recent-workspaces="recentWorkspaces"
|
:recent-workspaces="recentWorkspaces"
|
||||||
@open-folder="onOpenFolder"
|
@open-folder="onOpenFolder"
|
||||||
@switch-workspace="(path: string) => switchWorkspace(path)"
|
@switch-workspace="(path: string) => switchWorkspace(path)"
|
||||||
@@ -1283,6 +1306,7 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
|||||||
@update-default-document-mode="updateDefaultDocumentMode"
|
@update-default-document-mode="updateDefaultDocumentMode"
|
||||||
@update-app-theme="updateAppTheme"
|
@update-app-theme="updateAppTheme"
|
||||||
@update-render-single-line-breaks="updateRenderSingleLineBreaks"
|
@update-render-single-line-breaks="updateRenderSingleLineBreaks"
|
||||||
|
@update-paragraph-indent="updateParagraphIndent"
|
||||||
@prepare-workspace-sync="prepareWorkspaceSync"
|
@prepare-workspace-sync="prepareWorkspaceSync"
|
||||||
@workspace-synced="handleWorkspaceSynced"
|
@workspace-synced="handleWorkspaceSynced"
|
||||||
/>
|
/>
|
||||||
@@ -1431,6 +1455,7 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
|||||||
:document-mode="documentMode"
|
:document-mode="documentMode"
|
||||||
:file-path="currentFilePath"
|
:file-path="currentFilePath"
|
||||||
:render-single-line-breaks="renderSingleLineBreaks"
|
:render-single-line-breaks="renderSingleLineBreaks"
|
||||||
|
:paragraph-indent="paragraphIndent"
|
||||||
placeholder="输入 Markdown 内容... 支持标题、粗体、斜体、代码块等"
|
placeholder="输入 Markdown 内容... 支持标题、粗体、斜体、代码块等"
|
||||||
@update:model-value="onUpdate"
|
@update:model-value="onUpdate"
|
||||||
@save-shortcut="doSave"
|
@save-shortcut="doSave"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const props = withDefaults(
|
|||||||
defaultDocumentMode?: DocumentMode;
|
defaultDocumentMode?: DocumentMode;
|
||||||
appTheme?: AppTheme;
|
appTheme?: AppTheme;
|
||||||
renderSingleLineBreaks?: boolean;
|
renderSingleLineBreaks?: boolean;
|
||||||
|
paragraphIndent?: boolean;
|
||||||
recentWorkspaces?: WorkspaceEntry[];
|
recentWorkspaces?: WorkspaceEntry[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -46,6 +47,7 @@ const props = withDefaults(
|
|||||||
defaultDocumentMode: "edit",
|
defaultDocumentMode: "edit",
|
||||||
appTheme: "warm",
|
appTheme: "warm",
|
||||||
renderSingleLineBreaks: false,
|
renderSingleLineBreaks: false,
|
||||||
|
paragraphIndent: false,
|
||||||
recentWorkspaces: () => [],
|
recentWorkspaces: () => [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -63,6 +65,7 @@ const emit = defineEmits<{
|
|||||||
updateDefaultDocumentMode: [mode: DocumentMode];
|
updateDefaultDocumentMode: [mode: DocumentMode];
|
||||||
updateAppTheme: [theme: AppTheme];
|
updateAppTheme: [theme: AppTheme];
|
||||||
updateRenderSingleLineBreaks: [value: boolean];
|
updateRenderSingleLineBreaks: [value: boolean];
|
||||||
|
updateParagraphIndent: [value: boolean];
|
||||||
prepareWorkspaceSync: [done: (saved: boolean) => void];
|
prepareWorkspaceSync: [done: (saved: boolean) => void];
|
||||||
workspaceSynced: [];
|
workspaceSynced: [];
|
||||||
"search-result-click": [filePath: string, lineNumber: number];
|
"search-result-click": [filePath: string, lineNumber: number];
|
||||||
@@ -882,9 +885,11 @@ function setAllFoldersExpanded(expanded: boolean) {
|
|||||||
:default-document-mode="defaultDocumentMode"
|
:default-document-mode="defaultDocumentMode"
|
||||||
:app-theme="appTheme"
|
:app-theme="appTheme"
|
||||||
:render-single-line-breaks="renderSingleLineBreaks"
|
:render-single-line-breaks="renderSingleLineBreaks"
|
||||||
|
:paragraph-indent="paragraphIndent"
|
||||||
@update-default-document-mode="(mode: DocumentMode) => emit('updateDefaultDocumentMode', mode)"
|
@update-default-document-mode="(mode: DocumentMode) => emit('updateDefaultDocumentMode', mode)"
|
||||||
@update-app-theme="(theme: AppTheme) => emit('updateAppTheme', theme)"
|
@update-app-theme="(theme: AppTheme) => emit('updateAppTheme', theme)"
|
||||||
@update-render-single-line-breaks="(value: boolean) => emit('updateRenderSingleLineBreaks', value)"
|
@update-render-single-line-breaks="(value: boolean) => emit('updateRenderSingleLineBreaks', value)"
|
||||||
|
@update-paragraph-indent="(value: boolean) => emit('updateParagraphIndent', value)"
|
||||||
@prepare-workspace-sync="forwardPrepareWorkspaceSync"
|
@prepare-workspace-sync="forwardPrepareWorkspaceSync"
|
||||||
@workspace-synced="emit('workspaceSynced')"
|
@workspace-synced="emit('workspaceSynced')"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ const emit = defineEmits<{
|
|||||||
blur: [];
|
blur: [];
|
||||||
escape: [];
|
escape: [];
|
||||||
paste: [payload: PastePayload];
|
paste: [payload: PastePayload];
|
||||||
|
"backspace-at-start": [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const hostRef = ref<HTMLDivElement>();
|
const hostRef = ref<HTMLDivElement>();
|
||||||
@@ -346,6 +347,17 @@ function createEditor() {
|
|||||||
},
|
},
|
||||||
indentWithTab,
|
indentWithTab,
|
||||||
...closeBracketsKeymap,
|
...closeBracketsKeymap,
|
||||||
|
{
|
||||||
|
key: "Backspace",
|
||||||
|
run(view) {
|
||||||
|
const selection = view.state.selection.main;
|
||||||
|
if (selection.empty && selection.head === 0) {
|
||||||
|
emit("backspace-at-start");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
...defaultKeymap,
|
...defaultKeymap,
|
||||||
...searchKeymap,
|
...searchKeymap,
|
||||||
...historyKeymap,
|
...historyKeymap,
|
||||||
@@ -449,7 +461,15 @@ function scrollToLine(lineNumber: number) {
|
|||||||
view.focus();
|
view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ focus, focusAtEnd, replaceRange, scrollToLine });
|
function setCursorPosition(pos: number) {
|
||||||
|
const view = editorView;
|
||||||
|
if (!view) return;
|
||||||
|
const safePos = Math.max(0, Math.min(pos, view.state.doc.length));
|
||||||
|
view.dispatch({ selection: EditorSelection.cursor(safePos), scrollIntoView: true });
|
||||||
|
view.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ focus, focusAtEnd, replaceRange, scrollToLine, setCursorPosition });
|
||||||
|
|
||||||
onMounted(createEditor);
|
onMounted(createEditor);
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ interface MarkdownCodeEditorApi {
|
|||||||
focusAtEnd: () => void;
|
focusAtEnd: () => void;
|
||||||
replaceRange: (text: string, from?: number, to?: number) => void;
|
replaceRange: (text: string, from?: number, to?: number) => void;
|
||||||
scrollToLine: (lineNumber: number) => void;
|
scrollToLine: (lineNumber: number) => void;
|
||||||
|
setCursorPosition: (pos: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -80,6 +81,7 @@ const props = withDefaults(
|
|||||||
previewOnly?: boolean;
|
previewOnly?: boolean;
|
||||||
filePath?: string | null;
|
filePath?: string | null;
|
||||||
renderSingleLineBreaks?: boolean;
|
renderSingleLineBreaks?: boolean;
|
||||||
|
paragraphIndent?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: "",
|
modelValue: "",
|
||||||
@@ -88,6 +90,7 @@ const props = withDefaults(
|
|||||||
previewOnly: false,
|
previewOnly: false,
|
||||||
filePath: null,
|
filePath: null,
|
||||||
renderSingleLineBreaks: false,
|
renderSingleLineBreaks: false,
|
||||||
|
paragraphIndent: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -271,6 +274,9 @@ const pendingLocalModelUpdate = ref(false);
|
|||||||
const isEditingEmptyDocument = computed(
|
const isEditingEmptyDocument = computed(
|
||||||
() => editingBlockId.value === EMPTY_BLOCK_ID,
|
() => editingBlockId.value === EMPTY_BLOCK_ID,
|
||||||
);
|
);
|
||||||
|
const previewParagraphStyle = computed(() => ({
|
||||||
|
"--paragraph-indent": props.paragraphIndent ? "2em" : "0",
|
||||||
|
}));
|
||||||
let modeScrollRestoreFrame: number | null = null;
|
let modeScrollRestoreFrame: number | null = null;
|
||||||
let renderRequestId = 0;
|
let renderRequestId = 0;
|
||||||
const previewImageSelector = ".markdown-preview:not(.markdown-mixed) img";
|
const previewImageSelector = ".markdown-preview:not(.markdown-mixed) img";
|
||||||
@@ -591,6 +597,56 @@ function flushPendingChanges(): string | undefined {
|
|||||||
return finalizeBlockEdit();
|
return finalizeBlockEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function mergeCurrentBlockWithPrevious() {
|
||||||
|
if (activeDocumentMode.value !== "mixed") return;
|
||||||
|
const blockId = editingBlockId.value;
|
||||||
|
if (!blockId) return;
|
||||||
|
|
||||||
|
const blockIndex = markdownBlocks.value.findIndex((b) => b.id === blockId);
|
||||||
|
if (blockIndex <= 0) return;
|
||||||
|
|
||||||
|
const previousBlock = markdownBlocks.value[blockIndex - 1];
|
||||||
|
const currentBlock = markdownBlocks.value[blockIndex];
|
||||||
|
|
||||||
|
// 将当前编辑中的源码与前一个块的源码合并
|
||||||
|
const mergedSource = previousBlock.source + editingSource.value;
|
||||||
|
|
||||||
|
// 构建新文档:用合并后的源码替换两个块的范围
|
||||||
|
const nextValue =
|
||||||
|
props.modelValue.slice(0, previousBlock.startOffset) +
|
||||||
|
mergedSource +
|
||||||
|
props.modelValue.slice(currentBlock.endOffset);
|
||||||
|
|
||||||
|
// 记录合并点位置(上一个块的原末尾),用于后续光标定位
|
||||||
|
const cursorOffset = previousBlock.source.length;
|
||||||
|
|
||||||
|
// 取消当前编辑状态
|
||||||
|
cancelBlockEdit();
|
||||||
|
|
||||||
|
// 更新文档
|
||||||
|
emitModelValue(nextValue);
|
||||||
|
|
||||||
|
// 等待重新解析块结构
|
||||||
|
await parseBlocks(nextValue);
|
||||||
|
|
||||||
|
// 找到合并后的新块(startOffset 仍为 previousBlock.startOffset)
|
||||||
|
const newBlock = markdownBlocks.value.find(
|
||||||
|
(b) => b.startOffset === previousBlock.startOffset,
|
||||||
|
);
|
||||||
|
if (newBlock) {
|
||||||
|
// 进入编辑模式,光标定位在合并点
|
||||||
|
editingBlockId.value = newBlock.id;
|
||||||
|
editingSource.value = newBlock.source;
|
||||||
|
editingBaseValue.value = nextValue;
|
||||||
|
editingRange.value = {
|
||||||
|
startOffset: newBlock.startOffset,
|
||||||
|
endOffset: newBlock.endOffset,
|
||||||
|
};
|
||||||
|
await nextTick();
|
||||||
|
blockCodeRef.value?.setCursorPosition(cursorOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(activeDocumentMode, (mode, oldMode) => {
|
watch(activeDocumentMode, (mode, oldMode) => {
|
||||||
const scrollSnapshot = captureScrollSnapshot();
|
const scrollSnapshot = captureScrollSnapshot();
|
||||||
let committedValue: string | undefined;
|
let committedValue: string | undefined;
|
||||||
@@ -975,11 +1031,11 @@ defineExpose({
|
|||||||
<div v-if="renderError" class="text-red-500 text-sm">
|
<div v-if="renderError" class="text-red-500 text-sm">
|
||||||
⚠ Markdown 解析错误
|
⚠ Markdown 解析错误
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="markdown-preview" v-html="renderedHtml" />
|
<div v-else class="markdown-preview" :style="previewParagraphStyle" v-html="renderedHtml" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mixed mode -->
|
<!-- Mixed mode -->
|
||||||
<div v-else class="min-h-[60vh] markdown-preview markdown-mixed">
|
<div v-else class="min-h-[60vh] markdown-preview markdown-mixed" :style="previewParagraphStyle">
|
||||||
<div v-if="renderError" class="text-red-500 text-sm">
|
<div v-if="renderError" class="text-red-500 text-sm">
|
||||||
⚠ Markdown 解析错误
|
⚠ Markdown 解析错误
|
||||||
</div>
|
</div>
|
||||||
@@ -1030,6 +1086,7 @@ defineExpose({
|
|||||||
@escape="cancelBlockEdit"
|
@escape="cancelBlockEdit"
|
||||||
@blur="finalizeBlockEdit"
|
@blur="finalizeBlockEdit"
|
||||||
@paste="onBlockPaste"
|
@paste="onBlockPaste"
|
||||||
|
@backspace-at-start="mergeCurrentBlockWithPrevious"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -1091,7 +1148,7 @@ defineExpose({
|
|||||||
.markdown-preview h3 { font-size: 1.125em; }
|
.markdown-preview h3 { font-size: 1.125em; }
|
||||||
.markdown-preview h4 { font-size: 1em; }
|
.markdown-preview h4 { font-size: 1em; }
|
||||||
|
|
||||||
.markdown-preview p { margin: 0; }
|
.markdown-preview p { margin: 0; text-indent: var(--paragraph-indent, 0); }
|
||||||
|
|
||||||
.markdown-preview:not(.markdown-mixed) > p + p,
|
.markdown-preview:not(.markdown-mixed) > p + p,
|
||||||
.markdown-mixed
|
.markdown-mixed
|
||||||
@@ -1099,7 +1156,7 @@ defineExpose({
|
|||||||
+ .markdown-block-paragraph
|
+ .markdown-block-paragraph
|
||||||
> .markdown-block-preview
|
> .markdown-block-preview
|
||||||
> p {
|
> p {
|
||||||
margin-top: 1.625em;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview strong { font-weight: 700; color: var(--app-text-strong); }
|
.markdown-preview strong { font-weight: 700; color: var(--app-text-strong); }
|
||||||
@@ -1224,11 +1281,6 @@ defineExpose({
|
|||||||
box-shadow 0.15s ease;
|
box-shadow 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-block:not(.markdown-block-editing):hover > .markdown-block-preview > * {
|
|
||||||
background-color: var(--app-surface-muted);
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown-block-editing {
|
.markdown-block-editing {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -1236,7 +1288,7 @@ defineExpose({
|
|||||||
.markdown-mixed
|
.markdown-mixed
|
||||||
> .markdown-block-paragraph
|
> .markdown-block-paragraph
|
||||||
+ .markdown-block-paragraph.markdown-block-editing {
|
+ .markdown-block-paragraph.markdown-block-editing {
|
||||||
margin-top: 1.625em;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-block-editing,
|
.markdown-block-editing,
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ const props = withDefaults(
|
|||||||
defaultDocumentMode?: DocumentMode;
|
defaultDocumentMode?: DocumentMode;
|
||||||
appTheme?: AppTheme;
|
appTheme?: AppTheme;
|
||||||
renderSingleLineBreaks?: boolean;
|
renderSingleLineBreaks?: boolean;
|
||||||
|
paragraphIndent?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
initialPage: "about",
|
initialPage: "about",
|
||||||
@@ -85,6 +86,7 @@ const props = withDefaults(
|
|||||||
defaultDocumentMode: "edit",
|
defaultDocumentMode: "edit",
|
||||||
appTheme: "warm",
|
appTheme: "warm",
|
||||||
renderSingleLineBreaks: false,
|
renderSingleLineBreaks: false,
|
||||||
|
paragraphIndent: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@ const emit = defineEmits<{
|
|||||||
updateDefaultDocumentMode: [mode: DocumentMode];
|
updateDefaultDocumentMode: [mode: DocumentMode];
|
||||||
updateAppTheme: [theme: AppTheme];
|
updateAppTheme: [theme: AppTheme];
|
||||||
updateRenderSingleLineBreaks: [value: boolean];
|
updateRenderSingleLineBreaks: [value: boolean];
|
||||||
|
updateParagraphIndent: [value: boolean];
|
||||||
prepareWorkspaceSync: [done: (saved: boolean) => void];
|
prepareWorkspaceSync: [done: (saved: boolean) => void];
|
||||||
workspaceSynced: [];
|
workspaceSynced: [];
|
||||||
}>();
|
}>();
|
||||||
@@ -604,6 +607,20 @@ onBeforeUnmount(() => {
|
|||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-row">
|
||||||
|
<div class="settings-copy">
|
||||||
|
<h3>段落首行缩进</h3>
|
||||||
|
<p>开启后,预览和混合模式下段落首行会缩进两个字符,更符合中文排版习惯。</p>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch" title="段落首行缩进">
|
||||||
|
<input
|
||||||
|
:checked="paragraphIndent"
|
||||||
|
type="checkbox"
|
||||||
|
@change="emit('updateParagraphIndent', ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user