♻️ Refactor code
This commit is contained in:
@@ -109,11 +109,6 @@ export class AppearanceConfig {
|
||||
*/
|
||||
"language": LanguageType;
|
||||
|
||||
/**
|
||||
* 编辑器主题
|
||||
*/
|
||||
"theme": ThemeType;
|
||||
|
||||
/**
|
||||
* 系统界面主题
|
||||
*/
|
||||
@@ -124,9 +119,6 @@ export class AppearanceConfig {
|
||||
if (!("language" in $$source)) {
|
||||
this["language"] = ("" as LanguageType);
|
||||
}
|
||||
if (!("theme" in $$source)) {
|
||||
this["theme"] = ("" as ThemeType);
|
||||
}
|
||||
if (!("systemTheme" in $$source)) {
|
||||
this["systemTheme"] = ("" as SystemThemeType);
|
||||
}
|
||||
@@ -562,76 +554,6 @@ export enum TabType {
|
||||
TabTypeTab = "tab",
|
||||
};
|
||||
|
||||
/**
|
||||
* ThemeType 主题类型定义
|
||||
*/
|
||||
export enum ThemeType {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* ThemeDefaultDark 默认深色主题
|
||||
*/
|
||||
ThemeDefaultDark = "default-dark",
|
||||
|
||||
/**
|
||||
* ThemeDracula Dracula主题
|
||||
*/
|
||||
ThemeDracula = "dracula",
|
||||
|
||||
/**
|
||||
* ThemeAura Aura主题
|
||||
*/
|
||||
ThemeAura = "aura",
|
||||
|
||||
/**
|
||||
* ThemeGithubDark GitHub深色主题
|
||||
*/
|
||||
ThemeGithubDark = "github-dark",
|
||||
|
||||
/**
|
||||
* ThemeGithubLight GitHub浅色主题
|
||||
*/
|
||||
ThemeGithubLight = "github-light",
|
||||
|
||||
/**
|
||||
* ThemeMaterialDark Material深色主题
|
||||
*/
|
||||
ThemeMaterialDark = "material-dark",
|
||||
|
||||
/**
|
||||
* ThemeMaterialLight Material浅色主题
|
||||
*/
|
||||
ThemeMaterialLight = "material-light",
|
||||
|
||||
/**
|
||||
* ThemeSolarizedDark Solarized深色主题
|
||||
*/
|
||||
ThemeSolarizedDark = "solarized-dark",
|
||||
|
||||
/**
|
||||
* ThemeSolarizedLight Solarized浅色主题
|
||||
*/
|
||||
ThemeSolarizedLight = "solarized-light",
|
||||
|
||||
/**
|
||||
* ThemeTokyoNight Tokyo Night主题
|
||||
*/
|
||||
ThemeTokyoNight = "tokyo-night",
|
||||
|
||||
/**
|
||||
* ThemeTokyoNightStorm Tokyo Night Storm主题
|
||||
*/
|
||||
ThemeTokyoNightStorm = "tokyo-night-storm",
|
||||
|
||||
/**
|
||||
* ThemeTokyoNightDay Tokyo Night Day主题
|
||||
*/
|
||||
ThemeTokyoNightDay = "tokyo-night-day",
|
||||
};
|
||||
|
||||
/**
|
||||
* UpdatesConfig 更新设置配置
|
||||
*/
|
||||
|
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -8,6 +8,7 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BlockLanguageSelector: typeof import('./src/components/toolbar/BlockLanguageSelector.vue')['default']
|
||||
LinuxTitleBar: typeof import('./src/components/titlebar/LinuxTitleBar.vue')['default']
|
||||
MacOSTitleBar: typeof import('./src/components/titlebar/MacOSTitleBar.vue')['default']
|
||||
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
||||
|
@@ -2,23 +2,20 @@
|
||||
import { onMounted } from 'vue';
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { useSystemStore } from '@/stores/systemStore';
|
||||
import { useSystemTheme } from '@/composables/useSystemTheme';
|
||||
import WindowTitleBar from '@/components/titlebar/WindowTitleBar.vue';
|
||||
|
||||
const configStore = useConfigStore();
|
||||
const systemStore = useSystemStore();
|
||||
const { setTheme } = useSystemTheme();
|
||||
|
||||
// 应用启动时加载配置和初始化系统信息
|
||||
onMounted(async () => {
|
||||
// 并行初始化配置和系统信息
|
||||
await Promise.all([
|
||||
configStore.initConfig(),
|
||||
systemStore.initializeSystemInfo()
|
||||
systemStore.initializeSystemInfo(),
|
||||
]);
|
||||
|
||||
await configStore.initializeLanguage();
|
||||
setTheme(configStore.config.appearance.systemTheme);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
317
frontend/src/components/toolbar/BlockLanguageSelector.vue
Normal file
317
frontend/src/components/toolbar/BlockLanguageSelector.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { SUPPORTED_LANGUAGES, type SupportedLanguage } from '@/views/editor/extensions/codeblock/types';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 组件状态
|
||||
const showLanguageMenu = ref(false);
|
||||
const searchQuery = ref('');
|
||||
const searchInputRef = ref<HTMLInputElement>();
|
||||
|
||||
// 语言别名映射
|
||||
const LANGUAGE_ALIASES: Record<SupportedLanguage, string> = {
|
||||
text: 'txt',
|
||||
javascript: 'js',
|
||||
typescript: 'ts',
|
||||
python: 'py',
|
||||
html: 'htm',
|
||||
css: '',
|
||||
json: '',
|
||||
markdown: 'md',
|
||||
shell: 'sh',
|
||||
sql: '',
|
||||
yaml: 'yml',
|
||||
xml: '',
|
||||
php: '',
|
||||
java: '',
|
||||
cpp: 'c++',
|
||||
c: '',
|
||||
go: '',
|
||||
rust: 'rs',
|
||||
ruby: 'rb'
|
||||
};
|
||||
|
||||
// 当前选中的语言
|
||||
const currentLanguage = ref<SupportedLanguage>('javascript');
|
||||
|
||||
// 过滤后的语言列表
|
||||
const filteredLanguages = computed(() => {
|
||||
if (!searchQuery.value) {
|
||||
return SUPPORTED_LANGUAGES;
|
||||
}
|
||||
|
||||
const query = searchQuery.value.toLowerCase();
|
||||
return SUPPORTED_LANGUAGES.filter(langId => {
|
||||
const alias = LANGUAGE_ALIASES[langId];
|
||||
return langId.toLowerCase().includes(query) ||
|
||||
(alias && alias.toLowerCase().includes(query));
|
||||
});
|
||||
});
|
||||
|
||||
// 切换语言选择器显示状态
|
||||
const toggleLanguageMenu = () => {
|
||||
showLanguageMenu.value = !showLanguageMenu.value;
|
||||
};
|
||||
|
||||
// 关闭语言选择器
|
||||
const closeLanguageMenu = () => {
|
||||
showLanguageMenu.value = false;
|
||||
searchQuery.value = '';
|
||||
};
|
||||
|
||||
// 选择语言
|
||||
const selectLanguage = (languageId: SupportedLanguage) => {
|
||||
currentLanguage.value = languageId;
|
||||
closeLanguageMenu();
|
||||
// TODO: 这里后续需要调用实际的语言设置功能
|
||||
console.log('Selected language:', languageId);
|
||||
};
|
||||
|
||||
// 点击外部关闭
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target.closest('.block-language-selector')) {
|
||||
closeLanguageMenu();
|
||||
}
|
||||
};
|
||||
|
||||
// 键盘事件处理
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeLanguageMenu();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
// 获取当前语言的显示名称
|
||||
const getCurrentLanguageName = computed(() => {
|
||||
return currentLanguage.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block-language-selector">
|
||||
<button
|
||||
class="language-btn"
|
||||
:title="t('toolbar.blockLanguage')"
|
||||
@click="toggleLanguageMenu"
|
||||
>
|
||||
<span class="language-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="16 18 22 12 16 6"></polyline>
|
||||
<polyline points="8 6 2 12 8 18"></polyline>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="language-name">{{ getCurrentLanguageName }}</span>
|
||||
<span class="arrow" :class="{ 'open': showLanguageMenu }">▲</span>
|
||||
</button>
|
||||
|
||||
<div class="language-menu" v-if="showLanguageMenu">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-container">
|
||||
<input
|
||||
ref="searchInputRef"
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="search-input"
|
||||
:placeholder="t('toolbar.searchLanguage')"
|
||||
@keydown.stop
|
||||
/>
|
||||
<svg class="search-icon" xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 语言列表 -->
|
||||
<div class="language-list">
|
||||
<div
|
||||
v-for="language in filteredLanguages"
|
||||
:key="language"
|
||||
class="language-option"
|
||||
:class="{ 'active': currentLanguage === language }"
|
||||
@click="selectLanguage(language)"
|
||||
>
|
||||
<span class="language-name">{{ language }}</span>
|
||||
<span class="language-alias" v-if="LANGUAGE_ALIASES[language]">{{ LANGUAGE_ALIASES[language] }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<div v-if="filteredLanguages.length === 0" class="no-results">
|
||||
{{ t('toolbar.noLanguageFound') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.block-language-selector {
|
||||
position: relative;
|
||||
|
||||
.language-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.language-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.language-name {
|
||||
max-width: 60px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 8px;
|
||||
margin-left: 2px;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.language-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 4px;
|
||||
width: 220px;
|
||||
max-height: 280px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
|
||||
.search-container {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 2px;
|
||||
padding: 5px 8px 5px 26px;
|
||||
font-size: 11px;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
line-height: 1.2;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.language-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.language-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgba(181, 206, 168, 0.1);
|
||||
color: #b5cea8;
|
||||
|
||||
.language-alias {
|
||||
color: rgba(181, 206, 168, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.language-name {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.language-alias {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
.language-list::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.language-list::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.language-list::-webkit-scrollbar-thumb {
|
||||
background-color: var(--border-color);
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -6,33 +6,15 @@ import { useEditorStore } from '@/stores/editorStore';
|
||||
import { useErrorHandler } from '@/utils/errorHandler';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import { useRouter } from 'vue-router';
|
||||
import BlockLanguageSelector from './BlockLanguageSelector.vue';
|
||||
|
||||
const editorStore = useEditorStore();
|
||||
const configStore = useConfigStore();
|
||||
const { safeCall } = useErrorHandler();
|
||||
const { t, locale } = useI18n();
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
// 语言下拉菜单
|
||||
const showLanguageMenu = ref(false);
|
||||
|
||||
const supportedLanguages = [
|
||||
{ code: 'zh-CN', name: '简体中文' },
|
||||
{ code: 'en-US', name: 'English' }
|
||||
];
|
||||
|
||||
const toggleLanguageMenu = () => {
|
||||
showLanguageMenu.value = !showLanguageMenu.value;
|
||||
};
|
||||
|
||||
const closeLanguageMenu = () => {
|
||||
showLanguageMenu.value = false;
|
||||
};
|
||||
|
||||
const changeLanguage = async (langCode: string) => {
|
||||
await safeCall(() => configStore.setLanguage(langCode as any), 'language.changeFailed');
|
||||
closeLanguageMenu();
|
||||
};
|
||||
|
||||
// 设置窗口置顶
|
||||
const setWindowAlwaysOnTop = async (isTop: boolean) => {
|
||||
@@ -95,20 +77,9 @@ watch(isLoaded, async (newLoaded) => {
|
||||
<span class="font-size" :title="t('toolbar.fontSizeTooltip')" @click="() => configStore.resetFontSize()">
|
||||
{{ configStore.config.editing.fontSize }}px
|
||||
</span>
|
||||
<span class="tab-settings">
|
||||
<label :title="t('toolbar.tabLabel')" class="tab-toggle">
|
||||
<input type="checkbox" :checked="configStore.config.editing.enableTabIndent" @change="() => configStore.toggleTabIndent()"/>
|
||||
<span>{{ t('toolbar.tabLabel') }}</span>
|
||||
</label>
|
||||
<span class="tab-type" :title="t('toolbar.tabType.' + (configStore.config.editing.tabType === 'spaces' ? 'spaces' : 'tab'))" @click="() => configStore.toggleTabType()">
|
||||
{{ t('toolbar.tabType.' + (configStore.config.editing.tabType === 'spaces' ? 'spaces' : 'tab')) }}
|
||||
</span>
|
||||
<span class="tab-size" title="Tab大小" v-if="configStore.config.editing.tabType === 'spaces'">
|
||||
<button class="tab-btn" @click="() => configStore.decreaseTabSize()" :disabled="configStore.config.editing.tabSize <= configStore.tabSize.min">-</button>
|
||||
<span>{{ configStore.config.editing.tabSize }}</span>
|
||||
<button class="tab-btn" @click="() => configStore.increaseTabSize()" :disabled="configStore.config.editing.tabSize >= configStore.tabSize.max">+</button>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- 块语言选择器 -->
|
||||
<BlockLanguageSelector />
|
||||
|
||||
<!-- 窗口置顶图标按钮 -->
|
||||
<div
|
||||
@@ -122,24 +93,7 @@ watch(isLoaded, async (newLoaded) => {
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 语言切换按钮 -->
|
||||
<div class="selector-dropdown">
|
||||
<button class="selector-btn" @click="toggleLanguageMenu">
|
||||
{{ locale }}
|
||||
<span class="arrow">▲</span>
|
||||
</button>
|
||||
<div class="selector-menu" v-if="showLanguageMenu">
|
||||
<div
|
||||
v-for="lang in supportedLanguages"
|
||||
:key="lang.code"
|
||||
class="selector-option"
|
||||
:class="{ active: locale === lang.code }"
|
||||
@click="changeLanguage(lang.code)"
|
||||
>
|
||||
{{ t(`languages.${lang.code}`) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="settings-btn" :title="t('toolbar.settings')" @click="goToSettings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||
@@ -191,59 +145,7 @@ watch(isLoaded, async (newLoaded) => {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.tab-settings {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
|
||||
.tab-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-type {
|
||||
cursor: pointer;
|
||||
padding: 0 3px;
|
||||
border-radius: 3px;
|
||||
background-color: var(--border-color);
|
||||
opacity: 0.6;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
|
||||
.tab-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
padding: 0 3px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
|
||||
&:disabled {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 窗口置顶图标按钮样式 */
|
||||
.pin-button {
|
||||
@@ -278,64 +180,7 @@ watch(isLoaded, async (newLoaded) => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 通用下拉选择器样式 */
|
||||
.selector-dropdown {
|
||||
position: relative;
|
||||
|
||||
.selector-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 8px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 4px;
|
||||
min-width: 120px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
|
||||
.selector-option {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--border-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #b5cea8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.settings-btn {
|
||||
background: none;
|
||||
|
@@ -1,189 +0,0 @@
|
||||
import { ref, computed, shallowRef } from 'vue';
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import type { ThemeType } from '@/types';
|
||||
|
||||
// 主题加载器类型
|
||||
type ThemeLoader = () => Promise<Extension>;
|
||||
|
||||
// 默认主题常量
|
||||
const DEFAULT_THEME = 'default-dark' as ThemeType;
|
||||
|
||||
// 主题加载映射
|
||||
const themeLoaderMap = new Map<string, ThemeLoader>();
|
||||
|
||||
// 初始化主题加载器
|
||||
const initThemeLoaders = () => {
|
||||
themeLoaderMap.set('default-dark', () => import('@/views/editor/theme/default-dark').then(m => m.defaultDark));
|
||||
themeLoaderMap.set('dracula', () => import('@/views/editor/theme/dracula').then(m => m.dracula));
|
||||
themeLoaderMap.set('aura', () => import('@/views/editor/theme/aura').then(m => m.aura));
|
||||
themeLoaderMap.set('github-dark', () => import('@/views/editor/theme/github-dark').then(m => m.githubDark));
|
||||
themeLoaderMap.set('github-light', () => import('@/views/editor/theme/github-light').then(m => m.githubLight));
|
||||
themeLoaderMap.set('material-dark', () => import('@/views/editor/theme/material-dark').then(m => m.materialDark));
|
||||
themeLoaderMap.set('material-light', () => import('@/views/editor/theme/material-light').then(m => m.materialLight));
|
||||
themeLoaderMap.set('solarized-dark', () => import('@/views/editor/theme/solarized-dark').then(m => m.solarizedDark));
|
||||
themeLoaderMap.set('solarized-light', () => import('@/views/editor/theme/solarized-light').then(m => m.solarizedLight));
|
||||
themeLoaderMap.set('tokyo-night', () => import('@/views/editor/theme/tokyo-night').then(m => m.tokyoNight));
|
||||
themeLoaderMap.set('tokyo-night-storm', () => import('@/views/editor/theme/tokyo-night-storm').then(m => m.tokyoNightStorm));
|
||||
themeLoaderMap.set('tokyo-night-day', () => import('@/views/editor/theme/tokyo-night-day').then(m => m.tokyoNightDay));
|
||||
};
|
||||
|
||||
// 延迟初始化
|
||||
initThemeLoaders();
|
||||
|
||||
// 全局状态
|
||||
const currentTheme = ref<ThemeType>(DEFAULT_THEME);
|
||||
const themeCompartment = new Compartment();
|
||||
const themeCache = new Map<ThemeType, Extension>();
|
||||
const failedThemes = new Set<ThemeType>(); // 记录加载失败的主题
|
||||
|
||||
/**
|
||||
* 编辑器主题管理
|
||||
*/
|
||||
export function useEditorTheme() {
|
||||
|
||||
/**
|
||||
* 安全加载主题扩展
|
||||
*/
|
||||
const loadTheme = async (targetTheme: ThemeType): Promise<Extension> => {
|
||||
// 1. 从缓存快速返回
|
||||
const cached = themeCache.get(targetTheme);
|
||||
if (cached) return cached;
|
||||
|
||||
// 2. 检查是否已知失败的主题,避免重复尝试
|
||||
if (failedThemes.has(targetTheme) && targetTheme !== DEFAULT_THEME) {
|
||||
console.info(`Theme ${targetTheme} is known to fail, attempting default theme directly`);
|
||||
return attemptLoadTheme(DEFAULT_THEME).catch(() => [] as Extension);
|
||||
}
|
||||
|
||||
// 3. 使用 try-catch 链和 nullish coalescing,替代递归
|
||||
const result = await attemptLoadTheme(targetTheme)
|
||||
.catch(async (error) => {
|
||||
// 仅当目标主题不是默认主题时,才尝试默认主题
|
||||
if (targetTheme !== DEFAULT_THEME) {
|
||||
console.warn(`Theme ${targetTheme} failed, fallback to ${DEFAULT_THEME}:`, error);
|
||||
return attemptLoadTheme(DEFAULT_THEME).catch((fallbackError) => {
|
||||
console.error(`Fallback theme ${DEFAULT_THEME} also failed:`, fallbackError);
|
||||
return [] as Extension; // 最终回退到空扩展
|
||||
});
|
||||
}
|
||||
// 如果默认主题也失败了,返回空扩展
|
||||
console.error(`Default theme ${DEFAULT_THEME} failed:`, error);
|
||||
return [] as Extension;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 单纯的主题加载尝试 - 不处理回退逻辑
|
||||
*/
|
||||
const attemptLoadTheme = async (themeType: ThemeType): Promise<Extension> => {
|
||||
// 获取加载器,使用 optional chaining 和 nullish coalescing
|
||||
const loader = themeLoaderMap.get(themeType);
|
||||
|
||||
if (!loader) {
|
||||
const error = new Error(`Theme loader not found: ${themeType}`);
|
||||
failedThemes.add(themeType);
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
const extension = await loader();
|
||||
|
||||
// 缓存成功加载的主题
|
||||
themeCache.set(themeType, extension);
|
||||
// 从失败列表中移除(如果存在)
|
||||
failedThemes.delete(themeType);
|
||||
|
||||
return extension;
|
||||
} catch (error) {
|
||||
// 记录失败的主题
|
||||
failedThemes.add(themeType);
|
||||
console.error(`Failed to load theme: ${themeType}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建可配置的主题扩展
|
||||
*/
|
||||
const createThemeExtension = async (themeType: ThemeType): Promise<Extension> => {
|
||||
const extension = await loadTheme(themeType);
|
||||
currentTheme.value = themeType;
|
||||
return themeCompartment.of(extension);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新编辑器主题 - 使用防抖和错误处理
|
||||
*/
|
||||
const updateTheme = async (view: EditorView, themeType: ThemeType): Promise<void> => {
|
||||
// 使用可选链操作符检查 view
|
||||
if (!view?.dispatch || themeType === currentTheme.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = await loadTheme(themeType);
|
||||
|
||||
// 使用 try-catch 包装 dispatch,避免编辑器状态异常
|
||||
try {
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(extension)
|
||||
});
|
||||
currentTheme.value = themeType;
|
||||
} catch (error) {
|
||||
console.error('Failed to dispatch theme update:', error);
|
||||
throw error; // 重新抛出,让调用者处理
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量预加载主题 - 使用 Promise.allSettled 确保部分失败不影响其他
|
||||
*/
|
||||
const preloadThemes = async (themes: ThemeType[]): Promise<PromiseSettledResult<Extension>[]> => {
|
||||
const uniqueThemes = [...new Set(themes)]; // 去重
|
||||
return Promise.allSettled(
|
||||
uniqueThemes.map(theme => loadTheme(theme))
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置主题系统状态
|
||||
*/
|
||||
const resetThemeSystem = (): void => {
|
||||
themeCache.clear();
|
||||
failedThemes.clear();
|
||||
currentTheme.value = DEFAULT_THEME;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取主题系统状态信息
|
||||
*/
|
||||
const getThemeSystemInfo = () => ({
|
||||
currentTheme: currentTheme.value,
|
||||
cachedThemes: Array.from(themeCache.keys()),
|
||||
failedThemes: Array.from(failedThemes),
|
||||
availableThemes: Array.from(themeLoaderMap.keys()),
|
||||
});
|
||||
|
||||
return {
|
||||
// 状态
|
||||
currentTheme: computed(() => currentTheme.value),
|
||||
|
||||
// 核心方法
|
||||
createThemeExtension,
|
||||
updateTheme,
|
||||
loadTheme,
|
||||
|
||||
// 批量操作
|
||||
preloadThemes,
|
||||
|
||||
// 工具方法
|
||||
resetThemeSystem,
|
||||
getThemeSystemInfo,
|
||||
|
||||
// 缓存管理
|
||||
clearCache: () => themeCache.clear(),
|
||||
clearFailedThemes: () => failedThemes.clear(),
|
||||
};
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue';
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
|
||||
export function useSystemTheme() {
|
||||
const configStore = useConfigStore();
|
||||
const currentSystemTheme = ref<'dark' | 'light'>('dark');
|
||||
|
||||
// 设置主题 - 简化版本
|
||||
const setTheme = (theme: string) => {
|
||||
const root = document.documentElement;
|
||||
root.setAttribute('data-theme', theme);
|
||||
};
|
||||
|
||||
// 监听配置变化
|
||||
watch(
|
||||
() => configStore.config.appearance.systemTheme,
|
||||
(newTheme) => {
|
||||
// 直接根据配置设置主题,不需要检测系统主题
|
||||
const root = document.documentElement;
|
||||
root.setAttribute('data-theme', newTheme);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
const root = document.documentElement;
|
||||
const systemTheme = configStore.config.appearance.systemTheme;
|
||||
root.setAttribute('data-theme', systemTheme);
|
||||
});
|
||||
|
||||
return {
|
||||
currentSystemTheme,
|
||||
setTheme
|
||||
};
|
||||
}
|
@@ -1,59 +0,0 @@
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { useEditorTheme } from './useEditorTheme';
|
||||
import type { ThemeType } from '@/types';
|
||||
|
||||
/**
|
||||
* 主题管理 - 用于设置页面
|
||||
*/
|
||||
export function useTheme() {
|
||||
const configStore = useConfigStore();
|
||||
const { preloadThemes, getThemeSystemInfo } = useEditorTheme();
|
||||
|
||||
/**
|
||||
* 设置主题 - 同时更新配置和预览
|
||||
*/
|
||||
const setTheme = async (themeType: ThemeType): Promise<void> => {
|
||||
try {
|
||||
// 更新配置存储(这会自动触发编辑器主题更新)
|
||||
await configStore.setTheme(themeType);
|
||||
|
||||
console.info(`Theme switched to: ${themeType}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to set theme:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 预加载常用主题
|
||||
*/
|
||||
const preloadCommonThemes = async (): Promise<void> => {
|
||||
const commonThemes: ThemeType[] = [
|
||||
'default-dark' as ThemeType,
|
||||
'dracula' as ThemeType,
|
||||
'github-dark' as ThemeType,
|
||||
'material-dark' as ThemeType
|
||||
];
|
||||
|
||||
try {
|
||||
await preloadThemes(commonThemes);
|
||||
console.info('Common themes preloaded successfully');
|
||||
} catch (error) {
|
||||
console.warn('Some themes failed to preload:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取主题状态
|
||||
*/
|
||||
const getThemeStatus = () => ({
|
||||
current: configStore.config.appearance.theme,
|
||||
...getThemeSystemInfo()
|
||||
});
|
||||
|
||||
return {
|
||||
setTheme,
|
||||
preloadCommonThemes,
|
||||
getThemeStatus,
|
||||
};
|
||||
}
|
@@ -20,7 +20,10 @@ export default {
|
||||
},
|
||||
encoding: 'UTF-8',
|
||||
settings: 'Settings',
|
||||
alwaysOnTop: 'Always on Top'
|
||||
alwaysOnTop: 'Always on Top',
|
||||
blockLanguage: 'Block Language',
|
||||
searchLanguage: 'Search language...',
|
||||
noLanguageFound: 'No language found'
|
||||
},
|
||||
config: {
|
||||
loadSuccess: 'Configuration loaded successfully',
|
||||
|
@@ -20,7 +20,10 @@ export default {
|
||||
},
|
||||
encoding: 'UTF-8',
|
||||
settings: '设置',
|
||||
alwaysOnTop: '窗口置顶'
|
||||
alwaysOnTop: '窗口置顶',
|
||||
blockLanguage: '块语言',
|
||||
searchLanguage: '搜索语言...',
|
||||
noLanguageFound: '未找到匹配的语言'
|
||||
},
|
||||
config: {
|
||||
loadSuccess: '配置加载成功',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {computed, reactive} from 'vue';
|
||||
import {ConfigService} from '@/../bindings/voidraft/internal/services';
|
||||
import {ConfigService} from '../../bindings/voidraft/internal/services';
|
||||
import {
|
||||
AppConfig,
|
||||
AppearanceConfig,
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
LanguageType,
|
||||
SystemThemeType,
|
||||
TabType,
|
||||
ThemeType
|
||||
} from '@/../bindings/voidraft/internal/models';
|
||||
} from '../../bindings/voidraft/internal/models/models';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useErrorHandler} from '@/utils/errorHandler';
|
||||
import {ConfigUtils} from '@/utils/configUtils';
|
||||
@@ -68,7 +67,6 @@ const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
||||
|
||||
const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
|
||||
language: 'appearance.language',
|
||||
theme: 'appearance.theme',
|
||||
systemTheme: 'appearance.system_theme'
|
||||
} as const;
|
||||
|
||||
@@ -141,8 +139,7 @@ const DEFAULT_CONFIG: AppConfig = {
|
||||
},
|
||||
appearance: {
|
||||
language: LanguageType.LangZhCN,
|
||||
theme: 'default-dark' as ThemeType,
|
||||
systemTheme: 'dark' as SystemThemeType
|
||||
systemTheme: SystemThemeType.SystemThemeDark
|
||||
},
|
||||
keyBindings: {},
|
||||
updates: {},
|
||||
@@ -304,13 +301,6 @@ export const useConfigStore = defineStore('config', () => {
|
||||
}, 'config.languageChangeFailed', 'config.languageChanged');
|
||||
};
|
||||
|
||||
// 主题设置方法
|
||||
const setTheme = async (theme: ThemeType): Promise<void> => {
|
||||
await safeCall(async () => {
|
||||
await updateAppearanceConfig('theme', theme);
|
||||
}, 'config.themeChangeFailed', 'config.themeChanged');
|
||||
};
|
||||
|
||||
// 系统主题设置方法
|
||||
const setSystemTheme = async (systemTheme: SystemThemeType): Promise<void> => {
|
||||
await safeCall(async () => {
|
||||
@@ -381,7 +371,6 @@ export const useConfigStore = defineStore('config', () => {
|
||||
initializeLanguage,
|
||||
|
||||
// 主题相关方法
|
||||
setTheme,
|
||||
setSystemTheme,
|
||||
|
||||
// 字体大小操作
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {ref, watch} from 'vue';
|
||||
import {DocumentStats} from '@/types/editor';
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {EditorState, Extension} from '@codemirror/state';
|
||||
import {useConfigStore} from './configStore';
|
||||
@@ -17,18 +16,24 @@ import {
|
||||
createFontExtensionFromBackend,
|
||||
updateFontConfig,
|
||||
} from '@/views/editor/extensions';
|
||||
import { useEditorTheme } from '@/composables/useEditorTheme';
|
||||
import { createThemeExtension, updateEditorTheme } from '@/views/editor/extensions/themeExtension';
|
||||
import { useThemeStore } from './themeStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { ThemeType } from '@/types';
|
||||
import { DocumentService } from '../../bindings/voidraft/internal/services';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
||||
|
||||
export interface DocumentStats {
|
||||
lines: number;
|
||||
characters: number;
|
||||
selectedCharacters: number;
|
||||
}
|
||||
export const useEditorStore = defineStore('editor', () => {
|
||||
// 引用配置store
|
||||
const configStore = useConfigStore();
|
||||
const documentStore = useDocumentStore();
|
||||
const logStore = useLogStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { t } = useI18n();
|
||||
const { createThemeExtension, updateTheme } = useEditorTheme();
|
||||
|
||||
// 状态
|
||||
const documentStats = ref<DocumentStats>({
|
||||
@@ -123,8 +128,8 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
const basicExtensions = createBasicSetup();
|
||||
|
||||
// 获取主题扩展
|
||||
const themeExtension = await createThemeExtension(
|
||||
configStore.config.appearance.theme || 'default-dark' as ThemeType
|
||||
const themeExtension = createThemeExtension(
|
||||
configStore.config.appearance.systemTheme || SystemThemeType.SystemThemeDark
|
||||
);
|
||||
|
||||
// 获取Tab相关扩展
|
||||
@@ -222,12 +227,6 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 更新编辑器主题
|
||||
const updateEditorTheme = async (newTheme: ThemeType) => {
|
||||
if (newTheme && editorView.value) {
|
||||
await updateTheme(editorView.value as EditorView, newTheme);
|
||||
}
|
||||
};
|
||||
|
||||
// 销毁编辑器
|
||||
const destroyEditor = () => {
|
||||
@@ -260,9 +259,9 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
});
|
||||
|
||||
// 监听主题变化
|
||||
watch(() => configStore.config.appearance.theme, async (newTheme) => {
|
||||
if (newTheme) {
|
||||
await updateEditorTheme(newTheme);
|
||||
watch(() => themeStore.currentTheme, (newTheme) => {
|
||||
if (editorView.value && newTheme) {
|
||||
updateEditorTheme(editorView.value as EditorView, newTheme);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -281,7 +280,6 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
createEditor,
|
||||
reconfigureTabSettings,
|
||||
reconfigureFontSettings,
|
||||
updateEditorTheme,
|
||||
handleManualSave,
|
||||
destroyEditor,
|
||||
scrollEditorToBottom,
|
||||
|
36
frontend/src/stores/themeStore.ts
Normal file
36
frontend/src/stores/themeStore.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, watch } from 'vue';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { useConfigStore } from './configStore';
|
||||
|
||||
/**
|
||||
* 主题管理 Store
|
||||
* 职责:
|
||||
*/
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
const configStore = useConfigStore();
|
||||
|
||||
const currentTheme = computed(() =>
|
||||
configStore.config?.appearance?.systemTheme || SystemThemeType.SystemThemeAuto
|
||||
);
|
||||
|
||||
|
||||
// 应用主题到 DOM
|
||||
const applyThemeToDOM = (theme: SystemThemeType) => {
|
||||
document.documentElement.setAttribute('data-theme',
|
||||
theme === SystemThemeType.SystemThemeAuto ? 'auto' :
|
||||
theme === SystemThemeType.SystemThemeDark ? 'dark' : 'light'
|
||||
);
|
||||
};
|
||||
|
||||
// 设置主题
|
||||
const setTheme = async (theme: SystemThemeType) => {
|
||||
await configStore.setSystemTheme(theme);
|
||||
applyThemeToDOM(theme);
|
||||
};
|
||||
|
||||
return {
|
||||
currentTheme,
|
||||
setTheme,
|
||||
};
|
||||
});
|
5
frontend/src/types/editor.d.ts
vendored
5
frontend/src/types/editor.d.ts
vendored
@@ -1,5 +0,0 @@
|
||||
export interface DocumentStats {
|
||||
lines: number;
|
||||
characters: number;
|
||||
selectedCharacters: number;
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
// 统一类型导出
|
||||
export type { ThemeType, LanguageType, SystemThemeType } from '@/../bindings/voidraft/internal/models';
|
||||
export * from './theme';
|
||||
export * from './editor';
|
@@ -1,204 +0,0 @@
|
||||
import type { ThemeType } from '@/../bindings/voidraft/internal/models';
|
||||
|
||||
// 主题配置信息
|
||||
export interface ThemeInfo {
|
||||
id: ThemeType;
|
||||
name: string;
|
||||
displayName: string;
|
||||
isDark: boolean;
|
||||
previewColors: {
|
||||
background: string;
|
||||
foreground: string;
|
||||
keyword: string;
|
||||
string: string;
|
||||
function: string;
|
||||
comment: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 可用主题列表
|
||||
export const AVAILABLE_THEMES: ThemeInfo[] = [
|
||||
{
|
||||
id: 'default-dark' as ThemeType,
|
||||
name: 'default-dark',
|
||||
displayName: '深色默认',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#252B37',
|
||||
foreground: '#9BB586',
|
||||
keyword: '#FF79C6',
|
||||
string: '#F1FA8C',
|
||||
function: '#50FA7B',
|
||||
comment: '#6272A4'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'dracula' as ThemeType,
|
||||
name: 'dracula',
|
||||
displayName: 'Dracula',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#282A36',
|
||||
foreground: '#F8F8F2',
|
||||
keyword: '#FF79C6',
|
||||
string: '#F1FA8C',
|
||||
function: '#50FA7B',
|
||||
comment: '#6272A4'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'aura' as ThemeType,
|
||||
name: 'aura',
|
||||
displayName: 'Aura',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#21202e',
|
||||
foreground: '#edecee',
|
||||
keyword: '#a277ff',
|
||||
string: '#61ffca',
|
||||
function: '#ffca85',
|
||||
comment: '#6d6d6d'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'github-dark' as ThemeType,
|
||||
name: 'github-dark',
|
||||
displayName: 'GitHub 深色',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#24292e',
|
||||
foreground: '#d1d5da',
|
||||
keyword: '#f97583',
|
||||
string: '#9ecbff',
|
||||
function: '#79b8ff',
|
||||
comment: '#6a737d'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'github-light' as ThemeType,
|
||||
name: 'github-light',
|
||||
displayName: 'GitHub 浅色',
|
||||
isDark: false,
|
||||
previewColors: {
|
||||
background: '#fff',
|
||||
foreground: '#444d56',
|
||||
keyword: '#d73a49',
|
||||
string: '#032f62',
|
||||
function: '#005cc5',
|
||||
comment: '#6a737d'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'material-dark' as ThemeType,
|
||||
name: 'material-dark',
|
||||
displayName: 'Material 深色',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#263238',
|
||||
foreground: '#EEFFFF',
|
||||
keyword: '#C792EA',
|
||||
string: '#C3E88D',
|
||||
function: '#82AAFF',
|
||||
comment: '#546E7A'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'material-light' as ThemeType,
|
||||
name: 'material-light',
|
||||
displayName: 'Material 浅色',
|
||||
isDark: false,
|
||||
previewColors: {
|
||||
background: '#FAFAFA',
|
||||
foreground: '#90A4AE',
|
||||
keyword: '#7C4DFF',
|
||||
string: '#91B859',
|
||||
function: '#6182B8',
|
||||
comment: '#90A4AE'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'solarized-dark' as ThemeType,
|
||||
name: 'solarized-dark',
|
||||
displayName: 'Solarized 深色',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#002B36',
|
||||
foreground: '#93A1A1',
|
||||
keyword: '#859900',
|
||||
string: '#2AA198',
|
||||
function: '#268BD2',
|
||||
comment: '#586E75'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'solarized-light' as ThemeType,
|
||||
name: 'solarized-light',
|
||||
displayName: 'Solarized 浅色',
|
||||
isDark: false,
|
||||
previewColors: {
|
||||
background: '#FDF6E3',
|
||||
foreground: '#586E75',
|
||||
keyword: '#859900',
|
||||
string: '#2AA198',
|
||||
function: '#268BD2',
|
||||
comment: '#93A1A1'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'tokyo-night' as ThemeType,
|
||||
name: 'tokyo-night',
|
||||
displayName: 'Tokyo Night',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#1a1b26',
|
||||
foreground: '#787c99',
|
||||
keyword: '#bb9af7',
|
||||
string: '#9ece6a',
|
||||
function: '#7aa2f7',
|
||||
comment: '#444b6a'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'tokyo-night-storm' as ThemeType,
|
||||
name: 'tokyo-night-storm',
|
||||
displayName: 'Tokyo Night Storm',
|
||||
isDark: true,
|
||||
previewColors: {
|
||||
background: '#24283b',
|
||||
foreground: '#7982a9',
|
||||
keyword: '#bb9af7',
|
||||
string: '#9ece6a',
|
||||
function: '#7aa2f7',
|
||||
comment: '#565f89'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'tokyo-night-day' as ThemeType,
|
||||
name: 'tokyo-night-day',
|
||||
displayName: 'Tokyo Night Day',
|
||||
isDark: false,
|
||||
previewColors: {
|
||||
background: '#e1e2e7',
|
||||
foreground: '#6a6f8e',
|
||||
keyword: '#9854f1',
|
||||
string: '#587539',
|
||||
function: '#2e7de9',
|
||||
comment: '#9da3c2'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// 根据主题ID获取主题信息
|
||||
export function getThemeInfo(themeId: ThemeType): ThemeInfo | undefined {
|
||||
return AVAILABLE_THEMES.find(theme => theme.id === themeId);
|
||||
}
|
||||
|
||||
// 获取所有深色主题
|
||||
export function getDarkThemes(): ThemeInfo[] {
|
||||
return AVAILABLE_THEMES.filter(theme => theme.isDark);
|
||||
}
|
||||
|
||||
// 获取所有浅色主题
|
||||
export function getLightThemes(): ThemeInfo[] {
|
||||
return AVAILABLE_THEMES.filter(theme => !theme.isDark);
|
||||
}
|
@@ -72,9 +72,7 @@ export function createAutoSavePlugin(options: AutoSaveOptions = {}) {
|
||||
|
||||
// 静默发送最终内容,忽略错误
|
||||
const content = this.view.state.doc.toString();
|
||||
DocumentService.UpdateActiveDocumentContent(content).catch(() => {
|
||||
// 静默忽略销毁时的错误
|
||||
});
|
||||
DocumentService.UpdateActiveDocumentContent(content).then();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -143,16 +143,14 @@ const blockLayer = layer({
|
||||
return;
|
||||
}
|
||||
|
||||
// 只对最后一个块进行特殊处理
|
||||
// 对最后一个块进行特殊处理,让它直接延伸到底部
|
||||
if (idx === blocks.length - 1) {
|
||||
// 计算需要为最后一个块添加多少额外高度,但要更保守
|
||||
const editorHeight = view.dom.clientHeight;
|
||||
const contentBottom = toCoordsBottom - view.documentTop + view.documentPadding.top;
|
||||
|
||||
// 只有当内容不足以填满视口时,才添加额外高度
|
||||
// 让最后一个块直接延伸到编辑器底部
|
||||
if (contentBottom < editorHeight) {
|
||||
let extraHeight = editorHeight - contentBottom - view.defaultLineHeight - 16; // 保留合理的底部边距
|
||||
extraHeight = Math.max(0, extraHeight); // 确保不为负数
|
||||
const extraHeight = editorHeight - contentBottom-10;
|
||||
toCoordsBottom += extraHeight;
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,6 @@ import {getCodeBlockLanguageExtension} from './lang-parser';
|
||||
import {createLanguageDetection} from './language-detection';
|
||||
import {EditorOptions, SupportedLanguage} from './types';
|
||||
import {lineNumbers} from '@codemirror/view';
|
||||
import './styles.css'
|
||||
|
||||
/**
|
||||
* 代码块扩展配置选项
|
||||
@@ -47,14 +46,6 @@ export interface CodeBlockOptions {
|
||||
defaultAutoDetect?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认编辑器选项
|
||||
*/
|
||||
const defaultEditorOptions: EditorOptions = {
|
||||
defaultBlockToken: 'text',
|
||||
defaultBlockAutoDetect: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取块内行号信息
|
||||
*/
|
||||
|
@@ -48,14 +48,12 @@ export function getBlocksFromSyntaxTree(state: EditorState): Block[] | null {
|
||||
|
||||
// 解析整个分隔符文本来获取语言和自动检测标记
|
||||
const delimiterText = doc.sliceString(child.from, child.to);
|
||||
console.log('🔍 [解析器] 分隔符文本:', delimiterText);
|
||||
|
||||
|
||||
// 使用正则表达式解析分隔符
|
||||
const match = delimiterText.match(/∞∞∞([a-zA-Z0-9_-]+)(-a)?\n/);
|
||||
if (match) {
|
||||
language = match[1] || 'text';
|
||||
auto = match[2] === '-a';
|
||||
console.log(`🔍 [解析器] 解析结果: 语言=${language}, 自动=${auto}`);
|
||||
} else {
|
||||
// 回退到逐个解析子节点
|
||||
child.node.firstChild?.cursor().iterate(langChild => {
|
||||
|
@@ -1,224 +0,0 @@
|
||||
/* 块层样式 */
|
||||
.code-blocks-layer {
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.code-blocks-layer .block-even,
|
||||
.code-blocks-layer .block-odd {
|
||||
width: 100%;
|
||||
box-sizing: content-box;
|
||||
left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.code-blocks-layer .block-even:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* 块开始装饰 */
|
||||
.code-block-start {
|
||||
height: 12px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.code-block-start.first {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/* 默认块样式*/
|
||||
.code-blocks-layer .block-even {
|
||||
background: #252B37 !important;
|
||||
border-top: 1px solid #1e222a;
|
||||
}
|
||||
|
||||
.code-blocks-layer .block-odd {
|
||||
background: #213644 !important;
|
||||
border-top: 1px solid #1e222a;
|
||||
}
|
||||
|
||||
/* 浅色主题块样式 */
|
||||
:root[data-theme="light"] .code-blocks-layer .block-even {
|
||||
background: #ffffff !important;
|
||||
border-top: 1px solid #dfdfdf;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .code-blocks-layer .block-odd {
|
||||
background: #f4f8f4 !important;
|
||||
border-top: 1px solid #dfdfdf;
|
||||
}
|
||||
|
||||
/* 确保深色主题样式 */
|
||||
:root[data-theme="dark"] .code-blocks-layer .block-even {
|
||||
background: #252B37 !important;
|
||||
border-top: 1px solid #1e222a;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] .code-blocks-layer .block-odd {
|
||||
background: #213644 !important;
|
||||
border-top: 1px solid #1e222a;
|
||||
}
|
||||
|
||||
/* 空块选择样式 */
|
||||
.code-block-empty-selected {
|
||||
background-color: #0865a9aa !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .code-block-empty-selected {
|
||||
background-color: #77baff8c !important;
|
||||
}
|
||||
|
||||
/* 选择样式 */
|
||||
.cm-activeLine.code-empty-block-selected {
|
||||
background-color: #0865a9aa;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .cm-activeLine.code-empty-block-selected {
|
||||
background-color: #77baff8c;
|
||||
}
|
||||
|
||||
/* 光标样式 */
|
||||
.cm-cursor, .cm-dropCursor {
|
||||
border-left-width: 2px;
|
||||
padding-top: 4px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
.cm-content {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
/* 装订线样式 */
|
||||
.cm-gutters {
|
||||
padding: 0 2px 0 4px;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 0.15);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cm-activeLineGutter {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 浅色主题装订线 */
|
||||
:root[data-theme="light"] .cm-gutters {
|
||||
background-color: transparent;
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
border: none;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .cm-activeLineGutter {
|
||||
background-color: transparent;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
/* 活动行样式 */
|
||||
.cm-activeLine {
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .cm-activeLine {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 选择背景 */
|
||||
.cm-selectionBackground {
|
||||
background-color: #225377aa;
|
||||
}
|
||||
|
||||
.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
|
||||
background-color: #0865a9aa;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .cm-selectionBackground {
|
||||
background: #b2c2ca85;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
|
||||
background: #77baff8c;
|
||||
}
|
||||
|
||||
/* 编辑器焦点样式 */
|
||||
.cm-editor.cm-focused {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 折叠装订线样式 */
|
||||
.cm-foldGutter {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.cm-foldGutter .cm-gutterElement {
|
||||
opacity: 0;
|
||||
transition: opacity 400ms;
|
||||
}
|
||||
|
||||
.cm-gutters:hover .cm-gutterElement {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 匹配括号样式 */
|
||||
.cm-focused .cm-matchingBracket,
|
||||
.cm-focused .cm-nonmatchingBracket {
|
||||
outline: 0.5px solid #8fbcbb;
|
||||
}
|
||||
|
||||
.cm-focused .cm-matchingBracket {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cm-focused .cm-nonmatchingBracket {
|
||||
outline: 0.5px solid #bc8f8f;
|
||||
}
|
||||
|
||||
/* 搜索匹配样式 */
|
||||
.cm-searchMatch {
|
||||
background-color: transparent;
|
||||
outline: 1px solid #8fbcbb;
|
||||
}
|
||||
|
||||
.cm-searchMatch.cm-searchMatch-selected {
|
||||
background-color: #d8dee9;
|
||||
color: #2e3440;
|
||||
}
|
||||
|
||||
/* 选择匹配样式 */
|
||||
.cm-selectionMatch {
|
||||
background-color: #50606D;
|
||||
}
|
||||
|
||||
/* 折叠占位符样式 */
|
||||
.cm-foldPlaceholder {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* 工具提示样式 */
|
||||
.cm-tooltip {
|
||||
border: none;
|
||||
background-color: #3b4252;
|
||||
}
|
||||
|
||||
.cm-tooltip .cm-tooltip-arrow:before {
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.cm-tooltip .cm-tooltip-arrow:after {
|
||||
border-top-color: #3b4252;
|
||||
border-bottom-color: #3b4252;
|
||||
}
|
||||
|
||||
/* 自动完成工具提示 */
|
||||
.cm-tooltip-autocomplete > ul > li[aria-selected] {
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
color: #4c566a;
|
||||
}
|
@@ -4,5 +4,6 @@ export * from './wheelZoomExtension';
|
||||
export * from './statsExtension';
|
||||
export * from './autoSaveExtension';
|
||||
export * from './fontExtension';
|
||||
export * from './themeExtension';
|
||||
export * from './codeblast';
|
||||
export * from './codeblock';
|
@@ -1,7 +1,6 @@
|
||||
import {Extension} from '@codemirror/state';
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {DocumentStats} from '@/types/editor';
|
||||
|
||||
import {DocumentStats} from '@/stores/editorStore';
|
||||
// 更新编辑器文档统计信息
|
||||
export const updateStats = (
|
||||
view: EditorView,
|
||||
|
53
frontend/src/views/editor/extensions/themeExtension.ts
Normal file
53
frontend/src/views/editor/extensions/themeExtension.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { SystemThemeType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { dark } from '@/views/editor/theme/dark';
|
||||
import { light } from '@/views/editor/theme/light';
|
||||
|
||||
// 主题区间 - 用于动态切换主题
|
||||
export const themeCompartment = new Compartment();
|
||||
|
||||
/**
|
||||
* 根据主题类型获取主题扩展
|
||||
*/
|
||||
const getThemeExtension = (themeType: SystemThemeType): Extension => {
|
||||
// 处理 auto 主题类型
|
||||
let actualTheme: SystemThemeType = themeType;
|
||||
if (themeType === SystemThemeType.SystemThemeAuto) {
|
||||
actualTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? SystemThemeType.SystemThemeDark
|
||||
: SystemThemeType.SystemThemeLight;
|
||||
}
|
||||
|
||||
// 直接返回对应的主题扩展
|
||||
switch (actualTheme) {
|
||||
case SystemThemeType.SystemThemeLight:
|
||||
return light;
|
||||
case SystemThemeType.SystemThemeDark:
|
||||
default:
|
||||
return dark;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建主题扩展(用于编辑器初始化)
|
||||
*/
|
||||
export const createThemeExtension = (themeType: SystemThemeType = SystemThemeType.SystemThemeDark): Extension => {
|
||||
const extension = getThemeExtension(themeType);
|
||||
return themeCompartment.of(extension);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新编辑器主题
|
||||
*/
|
||||
export const updateEditorTheme = (view: EditorView, themeType: SystemThemeType): void => {
|
||||
if (!view?.dispatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extension = getThemeExtension(themeType);
|
||||
view.dispatch({
|
||||
effects: themeCompartment.reconfigure(extension)
|
||||
});
|
||||
};
|
||||
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'aura',
|
||||
dark: true,
|
||||
background: '#21202e',
|
||||
foreground: '#edecee',
|
||||
selection: '#A198EC7F',
|
||||
cursor: '#a277ff',
|
||||
dropdownBackground: '#21202e',
|
||||
dropdownBorder: '#3b334b',
|
||||
activeLine: '#4d4b6622',
|
||||
lineNumber: '#a394f033',
|
||||
lineNumberActive: '#cdccce',
|
||||
matchingBracket: '#a394f033',
|
||||
keyword: '#a277ff',
|
||||
storage: '#a277ff',
|
||||
variable: '#edecee',
|
||||
parameter: '#edecee',
|
||||
function: '#ffca85',
|
||||
string: '#61ffca',
|
||||
constant: '#61ffca',
|
||||
type: '#82e2ff',
|
||||
class: '#82e2ff',
|
||||
number: '#61ffca',
|
||||
comment: '#6d6d6d',
|
||||
heading: '#a277ff',
|
||||
invalid: '#ff6767',
|
||||
regexp: '#61ffca',
|
||||
}
|
||||
|
||||
export const auraTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const auraHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const aura: Extension = [
|
||||
auraTheme,
|
||||
syntaxHighlighting(auraHighlightStyle),
|
||||
]
|
237
frontend/src/views/editor/theme/dark.ts
Normal file
237
frontend/src/views/editor/theme/dark.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language';
|
||||
import {tags} from '@lezer/highlight';
|
||||
|
||||
const colors = {
|
||||
// 基础色调
|
||||
background: '#252B37', // 主背景色
|
||||
// backgroundAlt: '#252B37', // 交替背景色
|
||||
backgroundSecondary: '#213644', // 次要背景色
|
||||
surface: '#474747', // 面板背景
|
||||
|
||||
// 文本颜色
|
||||
foreground: '#9BB586', // 主文本色
|
||||
foregroundSecondary: '#9c9c9c', // 次要文本色
|
||||
comment: '#6272a4', // 注释色
|
||||
|
||||
// 语法高亮色
|
||||
keyword: '#ff79c6', // 关键字
|
||||
string: '#f1fa8c', // 字符串
|
||||
function: '#50fa7b', // 函数名
|
||||
number: '#bd93f9', // 数字
|
||||
operator: '#ff79c6', // 操作符
|
||||
variable: '#8fbcbb', // 变量
|
||||
type: '#8be9fd', // 类型
|
||||
|
||||
// 界面元素
|
||||
cursor: '#fff', // 光标
|
||||
selection: '#0865a9aa', // 选中背景
|
||||
selectionBlur: '#225377aa', // 失焦选中背景
|
||||
activeLine: 'rgba(255,255,255,0.04)', // 当前行高亮
|
||||
lineNumber: 'rgba(255,255,255, 0.15)', // 行号
|
||||
activeLineNumber: 'rgba(255,255,255, 0.6)', // 活动行号
|
||||
|
||||
// 边框和分割线
|
||||
border: '#1e222a', // 边框色
|
||||
borderLight: 'rgba(255,255,255, 0.1)', // 浅色边框
|
||||
|
||||
// 搜索和匹配
|
||||
searchMatch: '#8fbcbb', // 搜索匹配
|
||||
matchingBracket: 'rgba(255,255,255,0.1)', // 匹配括号
|
||||
};
|
||||
|
||||
const darkTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: colors.foreground,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保编辑器容器背景一致
|
||||
'.cm-editor': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保滚动区域背景一致
|
||||
'.cm-scroller': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 编辑器内容
|
||||
'.cm-content': {
|
||||
caretColor: colors.cursor,
|
||||
paddingTop: '4px',
|
||||
},
|
||||
|
||||
// 光标
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: colors.cursor,
|
||||
borderLeftWidth: '2px',
|
||||
paddingTop: '4px',
|
||||
marginTop: '-2px',
|
||||
},
|
||||
|
||||
// 选择
|
||||
'.cm-selectionBackground': {
|
||||
backgroundColor: colors.selectionBlur,
|
||||
},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
'.cm-activeLine.code-empty-block-selected': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
|
||||
// 当前行高亮
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: colors.activeLine
|
||||
},
|
||||
|
||||
// 行号区域
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'rgba(0,0,0, 0.1)',
|
||||
color: colors.lineNumber,
|
||||
border: 'none',
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'transparent',
|
||||
color: colors.activeLineNumber,
|
||||
},
|
||||
|
||||
// 折叠功能
|
||||
'.cm-foldGutter': {
|
||||
marginLeft: '0px',
|
||||
},
|
||||
'.cm-foldGutter .cm-gutterElement': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 400ms',
|
||||
},
|
||||
'.cm-gutters:hover .cm-gutterElement': {
|
||||
opacity: 1,
|
||||
},
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: '#ddd',
|
||||
},
|
||||
|
||||
|
||||
// 搜索匹配
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: 'transparent',
|
||||
outline: `1px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: colors.foreground,
|
||||
color: colors.background,
|
||||
},
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: '#50606D',
|
||||
},
|
||||
|
||||
// 括号匹配
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: `0.5px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'&.cm-focused .cm-matchingBracket': {
|
||||
backgroundColor: colors.matchingBracket,
|
||||
color: 'inherit',
|
||||
},
|
||||
'&.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: '0.5px solid #bc8f8f',
|
||||
},
|
||||
|
||||
// 编辑器焦点
|
||||
'&.cm-editor.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
|
||||
// 工具提示
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: colors.surface,
|
||||
borderBottomColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: colors.activeLine,
|
||||
color: colors.foreground,
|
||||
},
|
||||
},
|
||||
|
||||
// 代码块层
|
||||
'.code-blocks-layer': {
|
||||
width: '100%',
|
||||
},
|
||||
'.code-blocks-layer .block-even, .code-blocks-layer .block-odd': {
|
||||
width: '100%',
|
||||
boxSizing: 'content-box',
|
||||
},
|
||||
'.code-blocks-layer .block-even': {
|
||||
background: colors.background,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
},
|
||||
'.code-blocks-layer .block-even:first-child': {
|
||||
borderTop: 'none',
|
||||
},
|
||||
'.code-blocks-layer .block-odd': {
|
||||
background: colors.backgroundSecondary,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
},
|
||||
|
||||
// 代码块开始标记
|
||||
'.code-block-start': {
|
||||
height: '12px',
|
||||
position: 'relative',
|
||||
},
|
||||
'.code-block-start.first': {
|
||||
height: '0px',
|
||||
},
|
||||
}, {dark: true});
|
||||
|
||||
// 语法高亮样式
|
||||
const darkHighlightStyle = HighlightStyle.define([
|
||||
{tag: tags.keyword, color: colors.keyword},
|
||||
{tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable},
|
||||
{tag: [tags.variableName], color: colors.variable},
|
||||
{tag: [tags.function(tags.variableName)], color: colors.function},
|
||||
{tag: [tags.labelName], color: colors.operator},
|
||||
{tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.keyword},
|
||||
{tag: [tags.definition(tags.name), tags.separator], color: colors.function},
|
||||
{tag: [tags.brace], color: colors.variable},
|
||||
{tag: [tags.annotation], color: '#d30102'},
|
||||
{tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: colors.number},
|
||||
{tag: [tags.typeName, tags.className], color: colors.type},
|
||||
{tag: [tags.operator, tags.operatorKeyword], color: colors.operator},
|
||||
{tag: [tags.tagName], color: colors.number},
|
||||
{tag: [tags.squareBracket], color: '#bf616a'},
|
||||
{tag: [tags.angleBracket], color: '#d08770'},
|
||||
{tag: [tags.attributeName], color: colors.variable},
|
||||
{tag: [tags.regexp], color: colors.string},
|
||||
{tag: [tags.quote], color: colors.comment},
|
||||
{tag: [tags.string], color: colors.string},
|
||||
{tag: tags.link, color: colors.variable, textDecoration: 'underline'},
|
||||
{tag: [tags.url, tags.escape, tags.special(tags.string)], color: colors.string},
|
||||
{tag: [tags.meta], color: colors.comment},
|
||||
{tag: [tags.comment], color: colors.comment, fontStyle: 'italic'},
|
||||
{tag: tags.strong, fontWeight: 'bold'},
|
||||
{tag: tags.emphasis, fontStyle: 'italic'},
|
||||
{tag: tags.strikethrough, textDecoration: 'line-through'},
|
||||
{tag: tags.heading, fontWeight: 'bold', color: colors.keyword},
|
||||
{tag: [tags.heading1, tags.heading2], fontSize: '1.4em'},
|
||||
{tag: [tags.heading3, tags.heading4], fontSize: '1.2em'},
|
||||
{tag: [tags.heading5, tags.heading6], fontSize: '1.1em'},
|
||||
]);
|
||||
|
||||
export const dark = [
|
||||
darkTheme,
|
||||
syntaxHighlighting(darkHighlightStyle),
|
||||
];
|
@@ -1,149 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'base-dark',
|
||||
dark: true,
|
||||
background: '#252B37',
|
||||
foreground: '#9BB586',
|
||||
selection: '#3381c1',
|
||||
selectionMatch: '#1A58887F',
|
||||
cursor: '#F8F8F2',
|
||||
dropdownBackground: '#282A36',
|
||||
dropdownBorder: '#191A21',
|
||||
activeLine: '#2E333F99',
|
||||
lineNumber: '#676d7c',
|
||||
lineNumberActive: '#F8F8F2',
|
||||
lineNumberBackground: '#212731',
|
||||
matchingBracket: '#44475A',
|
||||
keyword: '#FF79C6',
|
||||
storage: '#FF79C6',
|
||||
variable: '#F8F8F2',
|
||||
parameter: '#F8F8F2',
|
||||
function: '#50FA7B',
|
||||
string: '#F1FA8C',
|
||||
constant: '#BD93F9',
|
||||
type: '#8BE9FD',
|
||||
class: '#8BE9FD',
|
||||
number: '#BD93F9',
|
||||
comment: '#6272A4',
|
||||
heading: '#BD93F9',
|
||||
invalid: '#FF5555',
|
||||
regexp: '#F1FA8C',
|
||||
}
|
||||
|
||||
export const draculaTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
|
||||
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
|
||||
backgroundColor: `${config.selection} !important`
|
||||
},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: `${config.selectionMatch} !important`,
|
||||
borderRadius: '2px'
|
||||
},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'rgba(0,0,0, 0.1)',
|
||||
color: config.foreground,
|
||||
border: 'none',
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
// backgroundColor: config.background
|
||||
backgroundColor: "transparent",
|
||||
color: 'rgba(255,255,255, 0.6)'
|
||||
},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const draculaHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const customHighlightActiveLine = EditorView.theme({
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: config.activeLine,
|
||||
}
|
||||
})
|
||||
|
||||
export const defaultDark: Extension = [
|
||||
draculaTheme,
|
||||
syntaxHighlighting(draculaHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'dracula',
|
||||
dark: true,
|
||||
background: '#282A36',
|
||||
foreground: '#F8F8F2',
|
||||
selection: '#44475A',
|
||||
cursor: '#F8F8F2',
|
||||
dropdownBackground: '#282A36',
|
||||
dropdownBorder: '#191A21',
|
||||
activeLine: '#53576c22',
|
||||
lineNumber: '#6272A4',
|
||||
lineNumberActive: '#F8F8F2',
|
||||
matchingBracket: '#44475A',
|
||||
keyword: '#FF79C6',
|
||||
storage: '#FF79C6',
|
||||
variable: '#F8F8F2',
|
||||
parameter: '#F8F8F2',
|
||||
function: '#50FA7B',
|
||||
string: '#F1FA8C',
|
||||
constant: '#BD93F9',
|
||||
type: '#8BE9FD',
|
||||
class: '#8BE9FD',
|
||||
number: '#BD93F9',
|
||||
comment: '#6272A4',
|
||||
heading: '#BD93F9',
|
||||
invalid: '#FF5555',
|
||||
regexp: '#F1FA8C',
|
||||
}
|
||||
|
||||
export const draculaTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const draculaHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const dracula: Extension = [
|
||||
draculaTheme,
|
||||
syntaxHighlighting(draculaHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'githubDark',
|
||||
dark: true,
|
||||
background: '#24292e',
|
||||
foreground: '#d1d5da',
|
||||
selection: '#3392FF44',
|
||||
cursor: '#c8e1ff',
|
||||
dropdownBackground: '#24292e',
|
||||
dropdownBorder: '#1b1f23',
|
||||
activeLine: '#4d566022',
|
||||
lineNumber: '#444d56',
|
||||
lineNumberActive: '#e1e4e8',
|
||||
matchingBracket: '#17E5E650',
|
||||
keyword: '#f97583',
|
||||
storage: '#f97583',
|
||||
variable: '#ffab70',
|
||||
parameter: '#e1e4e8',
|
||||
function: '#79b8ff',
|
||||
string: '#9ecbff',
|
||||
constant: '#79b8ff',
|
||||
type: '#79b8ff',
|
||||
class: '#b392f0',
|
||||
number: '#79b8ff',
|
||||
comment: '#6a737d',
|
||||
heading: '#79b8ff',
|
||||
invalid: '#f97583',
|
||||
regexp: '#9ecbff',
|
||||
}
|
||||
|
||||
export const githubDarkTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const githubDarkHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const githubDark: Extension = [
|
||||
githubDarkTheme,
|
||||
syntaxHighlighting(githubDarkHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'githubLight',
|
||||
dark: false,
|
||||
background: '#fff',
|
||||
foreground: '#444d56',
|
||||
selection: '#0366d625',
|
||||
cursor: '#044289',
|
||||
dropdownBackground: '#fff',
|
||||
dropdownBorder: '#e1e4e8',
|
||||
activeLine: '#c6c6c622',
|
||||
lineNumber: '#1b1f234d',
|
||||
lineNumberActive: '#24292e',
|
||||
matchingBracket: '#34d05840',
|
||||
keyword: '#d73a49',
|
||||
storage: '#d73a49',
|
||||
variable: '#e36209',
|
||||
parameter: '#24292e',
|
||||
function: '#005cc5',
|
||||
string: '#032f62',
|
||||
constant: '#005cc5',
|
||||
type: '#005cc5',
|
||||
class: '#6f42c1',
|
||||
number: '#005cc5',
|
||||
comment: '#6a737d',
|
||||
heading: '#005cc5',
|
||||
invalid: '#cb2431',
|
||||
regexp: '#032f62',
|
||||
}
|
||||
|
||||
export const githubLightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const githubLightHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const githubLight: Extension = [
|
||||
githubLightTheme,
|
||||
syntaxHighlighting(githubLightHighlightStyle),
|
||||
]
|
239
frontend/src/views/editor/theme/light.ts
Normal file
239
frontend/src/views/editor/theme/light.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { tags } from '@lezer/highlight';
|
||||
|
||||
const colors = {
|
||||
// 基础色调
|
||||
background: '#ffffff', // 主背景色
|
||||
// backgroundAlt: '#f4f8f4', // 交替背景色
|
||||
backgroundSecondary: '#f1faf1', // 次要背景色
|
||||
surface: '#f5f5f5', // 面板背景
|
||||
|
||||
// 文本颜色
|
||||
foreground: '#444d56', // 主文本色
|
||||
foregroundSecondary: '#6a737d', // 次要文本色
|
||||
comment: '#6a737d', // 注释色
|
||||
|
||||
// 语法高亮色
|
||||
keyword: '#d73a49', // 关键字
|
||||
string: '#032f62', // 字符串
|
||||
function: '#005cc5', // 函数名
|
||||
number: '#005cc5', // 数字
|
||||
operator: '#d73a49', // 操作符
|
||||
variable: '#24292e', // 变量
|
||||
type: '#6f42c1', // 类型
|
||||
|
||||
// 界面元素
|
||||
cursor: '#000', // 光标
|
||||
selection: '#77baff8c', // 选中背景
|
||||
selectionBlur: '#b2c2ca85', // 失焦选中背景
|
||||
activeLine: 'rgba(0,0,0, 0.04)', // 当前行高亮
|
||||
lineNumber: 'rgba(0,0,0, 0.25)', // 行号
|
||||
activeLineNumber: 'rgba(0,0,0, 0.6)', // 活动行号
|
||||
|
||||
// 边框和分割线
|
||||
border: '#dfdfdf', // 边框色
|
||||
borderLight: 'rgba(0,0,0, 0.05)', // 浅色边框
|
||||
|
||||
// 搜索和匹配
|
||||
searchMatch: '#005cc5', // 搜索匹配
|
||||
matchingBracket: 'rgba(0,0,0,0.1)', // 匹配括号
|
||||
|
||||
};
|
||||
|
||||
const lightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: colors.foreground,
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保编辑器容器背景一致
|
||||
'.cm-editor': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 确保滚动区域背景一致
|
||||
'.cm-scroller': {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
|
||||
// 编辑器内容
|
||||
'.cm-content': {
|
||||
caretColor: colors.cursor,
|
||||
paddingTop: '4px',
|
||||
},
|
||||
|
||||
// 光标
|
||||
'.cm-cursor, .cm-dropCursor': {
|
||||
borderLeftColor: colors.cursor,
|
||||
borderLeftWidth: '2px',
|
||||
paddingTop: '4px',
|
||||
marginTop: '-2px',
|
||||
},
|
||||
|
||||
// 选择
|
||||
'.cm-selectionBackground': {
|
||||
backgroundColor: colors.selectionBlur,
|
||||
},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
'.cm-activeLine.code-empty-block-selected': {
|
||||
backgroundColor: colors.selection,
|
||||
},
|
||||
|
||||
// 当前行高亮
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: colors.activeLine
|
||||
},
|
||||
|
||||
// 行号区域
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'rgba(0,0,0, 0.04)',
|
||||
color: colors.lineNumber,
|
||||
border: 'none',
|
||||
borderRight: `1px solid ${colors.borderLight}`,
|
||||
padding: '0 2px 0 4px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'transparent',
|
||||
color: colors.activeLineNumber,
|
||||
},
|
||||
|
||||
// 折叠功能
|
||||
'.cm-foldGutter': {
|
||||
marginLeft: '0px',
|
||||
},
|
||||
'.cm-foldGutter .cm-gutterElement': {
|
||||
opacity: 0,
|
||||
transition: 'opacity 400ms',
|
||||
},
|
||||
'.cm-gutters:hover .cm-gutterElement': {
|
||||
opacity: 1,
|
||||
},
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: colors.comment,
|
||||
},
|
||||
|
||||
|
||||
// 搜索匹配
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: 'transparent',
|
||||
outline: `1px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: colors.searchMatch,
|
||||
color: colors.background,
|
||||
},
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: '#e6f3ff',
|
||||
},
|
||||
|
||||
// 括号匹配
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: `0.5px solid ${colors.searchMatch}`,
|
||||
},
|
||||
'&.cm-focused .cm-matchingBracket': {
|
||||
backgroundColor: colors.matchingBracket,
|
||||
color: 'inherit',
|
||||
},
|
||||
'&.cm-focused .cm-nonmatchingBracket': {
|
||||
outline: '0.5px solid #d73a49',
|
||||
},
|
||||
|
||||
// 编辑器焦点
|
||||
'&.cm-editor.cm-focused': {
|
||||
outline: 'none',
|
||||
},
|
||||
|
||||
// 工具提示
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: colors.surface,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: colors.surface,
|
||||
borderBottomColor: colors.surface,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: colors.activeLine,
|
||||
color: colors.foreground,
|
||||
},
|
||||
},
|
||||
|
||||
// 代码块层
|
||||
'.code-blocks-layer': {
|
||||
width: '100%',
|
||||
},
|
||||
'.code-blocks-layer .block-even, .code-blocks-layer .block-odd': {
|
||||
width: '100%',
|
||||
boxSizing: 'content-box',
|
||||
},
|
||||
'.code-blocks-layer .block-even': {
|
||||
background: colors.background,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
},
|
||||
'.code-blocks-layer .block-even:first-child': {
|
||||
borderTop: 'none',
|
||||
},
|
||||
'.code-blocks-layer .block-odd': {
|
||||
background: colors.backgroundSecondary,
|
||||
borderTop: `1px solid ${colors.border}`,
|
||||
},
|
||||
|
||||
// 代码块开始标记
|
||||
'.code-block-start': {
|
||||
height: '12px',
|
||||
},
|
||||
'.code-block-start.first': {
|
||||
height: '0px',
|
||||
},
|
||||
}, { dark: false });
|
||||
|
||||
// 语法高亮样式
|
||||
const lightHighlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: colors.keyword },
|
||||
{ tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName], color: colors.variable },
|
||||
{ tag: [tags.variableName], color: colors.variable },
|
||||
{ tag: [tags.function(tags.variableName)], color: colors.function },
|
||||
{ tag: [tags.labelName], color: colors.operator },
|
||||
{ tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)], color: colors.keyword },
|
||||
{ tag: [tags.definition(tags.name), tags.separator], color: colors.function },
|
||||
{ tag: [tags.brace], color: colors.variable },
|
||||
{ tag: [tags.annotation], color: '#d73a49' },
|
||||
{ tag: [tags.number, tags.changed, tags.annotation, tags.modifier, tags.self, tags.namespace], color: colors.number },
|
||||
{ tag: [tags.typeName, tags.className], color: colors.type },
|
||||
{ tag: [tags.operator, tags.operatorKeyword], color: colors.operator },
|
||||
{ tag: [tags.tagName], color: colors.type },
|
||||
{ tag: [tags.squareBracket], color: colors.keyword },
|
||||
{ tag: [tags.angleBracket], color: colors.operator },
|
||||
{ tag: [tags.attributeName], color: colors.variable },
|
||||
{ tag: [tags.regexp], color: colors.string },
|
||||
{ tag: [tags.quote], color: colors.comment },
|
||||
{ tag: [tags.string], color: colors.string },
|
||||
{ tag: tags.link, color: colors.function, textDecoration: 'underline' },
|
||||
{ tag: [tags.url, tags.escape, tags.special(tags.string)], color: colors.string },
|
||||
{ tag: [tags.meta], color: colors.comment },
|
||||
{ tag: [tags.comment], color: colors.comment, fontStyle: 'italic' },
|
||||
{ tag: tags.strong, fontWeight: 'bold' },
|
||||
{ tag: tags.emphasis, fontStyle: 'italic' },
|
||||
{ tag: tags.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: tags.heading, fontWeight: 'bold', color: colors.keyword },
|
||||
{ tag: [tags.heading1, tags.heading2], fontSize: '1.4em' },
|
||||
{ tag: [tags.heading3, tags.heading4], fontSize: '1.2em' },
|
||||
{ tag: [tags.heading5, tags.heading6], fontSize: '1.1em' },
|
||||
]);
|
||||
|
||||
export const light = [
|
||||
lightTheme,
|
||||
syntaxHighlighting(lightHighlightStyle),
|
||||
];
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'materialDark',
|
||||
dark: true,
|
||||
background: '#263238',
|
||||
foreground: '#EEFFFF',
|
||||
selection: '#80CBC420',
|
||||
cursor: '#FFCC00',
|
||||
dropdownBackground: '#263238',
|
||||
dropdownBorder: '#FFFFFF10',
|
||||
activeLine: '#4c616c22',
|
||||
lineNumber: '#37474F',
|
||||
lineNumberActive: '#607a86',
|
||||
matchingBracket: '#263238',
|
||||
keyword: '#C792EA',
|
||||
storage: '#C792EA',
|
||||
variable: '#EEFFFF',
|
||||
parameter: '#EEFFFF',
|
||||
function: '#82AAFF',
|
||||
string: '#C3E88D',
|
||||
constant: '#F78C6C',
|
||||
type: '#B2CCD6',
|
||||
class: '#FFCB6B',
|
||||
number: '#F78C6C',
|
||||
comment: '#546E7A',
|
||||
heading: '#C3E88D',
|
||||
invalid: '#FF5370',
|
||||
regexp: '#89DDFF',
|
||||
}
|
||||
|
||||
export const materialDarkTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const materialDarkHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const materialDark: Extension = [
|
||||
materialDarkTheme,
|
||||
syntaxHighlighting(materialDarkHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'materialLight',
|
||||
dark: false,
|
||||
background: '#FAFAFA',
|
||||
foreground: '#90A4AE',
|
||||
selection: '#80CBC440',
|
||||
cursor: '#272727',
|
||||
dropdownBackground: '#FAFAFA',
|
||||
dropdownBorder: '#00000010',
|
||||
activeLine: '#c2c2c222',
|
||||
lineNumber: '#CFD8DC',
|
||||
lineNumberActive: '#7E939E',
|
||||
matchingBracket: '#FAFAFA',
|
||||
keyword: '#7C4DFF',
|
||||
storage: '#7C4DFF',
|
||||
variable: '#90A4AE',
|
||||
parameter: '#90A4AE',
|
||||
function: '#6182B8',
|
||||
string: '#91B859',
|
||||
constant: '#F76D47',
|
||||
type: '#8796B0',
|
||||
class: '#FFB62C',
|
||||
number: '#F76D47',
|
||||
comment: '#90A4AE',
|
||||
heading: '#91B859',
|
||||
invalid: '#E53935',
|
||||
regexp: '#39ADB5',
|
||||
}
|
||||
|
||||
export const materialLightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const materialLightHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const materialLight: Extension = [
|
||||
materialLightTheme,
|
||||
syntaxHighlighting(materialLightHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'solarizedDark',
|
||||
dark: true,
|
||||
background: '#002B36',
|
||||
foreground: '#93A1A1',
|
||||
selection: '#274642',
|
||||
cursor: '#D30102',
|
||||
dropdownBackground: '#002B36',
|
||||
dropdownBorder: '#2AA19899',
|
||||
activeLine: '#005b7022',
|
||||
lineNumber: '#93A1A1',
|
||||
lineNumberActive: '#949494',
|
||||
matchingBracket: '#073642',
|
||||
keyword: '#859900',
|
||||
storage: '#93A1A1',
|
||||
variable: '#268BD2',
|
||||
parameter: '#268BD2',
|
||||
function: '#268BD2',
|
||||
string: '#2AA198',
|
||||
constant: '#CB4B16',
|
||||
type: '#CB4B16',
|
||||
class: '#CB4B16',
|
||||
number: '#D33682',
|
||||
comment: '#586E75',
|
||||
heading: '#268BD2',
|
||||
invalid: '#DC322F',
|
||||
regexp: '#DC322F',
|
||||
}
|
||||
|
||||
export const solarizedDarkTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const solarizedDarkHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const solarizedDark: Extension = [
|
||||
solarizedDarkTheme,
|
||||
syntaxHighlighting(solarizedDarkHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'solarizedLight',
|
||||
dark: false,
|
||||
background: '#FDF6E3',
|
||||
foreground: '#586E75',
|
||||
selection: '#EEE8D5',
|
||||
cursor: '#657B83',
|
||||
dropdownBackground: '#FDF6E3',
|
||||
dropdownBorder: '#D3AF86',
|
||||
activeLine: '#d5bd5c22',
|
||||
lineNumber: '#586E75',
|
||||
lineNumberActive: '#567983',
|
||||
matchingBracket: '#EEE8D5',
|
||||
keyword: '#859900',
|
||||
storage: '#586E75',
|
||||
variable: '#268BD2',
|
||||
parameter: '#268BD2',
|
||||
function: '#268BD2',
|
||||
string: '#2AA198',
|
||||
constant: '#CB4B16',
|
||||
type: '#CB4B16',
|
||||
class: '#CB4B16',
|
||||
number: '#D33682',
|
||||
comment: '#93A1A1',
|
||||
heading: '#268BD2',
|
||||
invalid: '#DC322F',
|
||||
regexp: '#DC322F',
|
||||
}
|
||||
|
||||
export const solarizedLightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const solarizedLightHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const solarizedLight: Extension = [
|
||||
solarizedLightTheme,
|
||||
syntaxHighlighting(solarizedLightHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'tokyoNightDay',
|
||||
dark: false,
|
||||
background: '#e1e2e7',
|
||||
foreground: '#6a6f8e',
|
||||
selection: '#8591b840',
|
||||
cursor: '#3760bf',
|
||||
dropdownBackground: '#e1e2e7',
|
||||
dropdownBorder: '#6a6f8e',
|
||||
activeLine: '#a7aaba22',
|
||||
lineNumber: '#b3b6cd',
|
||||
lineNumberActive: '#68709a',
|
||||
matchingBracket: '#e9e9ec',
|
||||
keyword: '#9854f1',
|
||||
storage: '#9854f1',
|
||||
variable: '#3760bf',
|
||||
parameter: '#3760bf',
|
||||
function: '#2e7de9',
|
||||
string: '#587539',
|
||||
constant: '#9854f1',
|
||||
type: '#07879d',
|
||||
class: '#3760bf',
|
||||
number: '#b15c00',
|
||||
comment: '#9da3c2',
|
||||
heading: '#006a83',
|
||||
invalid: '#ff3e64',
|
||||
regexp: '#2e5857',
|
||||
}
|
||||
|
||||
export const tokyoNightDayTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const tokyoNightDayHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const tokyoNightDay: Extension = [
|
||||
tokyoNightDayTheme,
|
||||
syntaxHighlighting(tokyoNightDayHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'tokyoNightStorm',
|
||||
dark: true,
|
||||
background: '#24283b',
|
||||
foreground: '#7982a9',
|
||||
selection: '#6f7bb630',
|
||||
cursor: '#c0caf5',
|
||||
dropdownBackground: '#24283b',
|
||||
dropdownBorder: '#7982a9',
|
||||
activeLine: '#4d547722',
|
||||
lineNumber: '#3b4261',
|
||||
lineNumberActive: '#737aa2',
|
||||
matchingBracket: '#1f2335',
|
||||
keyword: '#bb9af7',
|
||||
storage: '#bb9af7',
|
||||
variable: '#c0caf5',
|
||||
parameter: '#c0caf5',
|
||||
function: '#7aa2f7',
|
||||
string: '#9ece6a',
|
||||
constant: '#bb9af7',
|
||||
type: '#2ac3de',
|
||||
class: '#c0caf5',
|
||||
number: '#ff9e64',
|
||||
comment: '#565f89',
|
||||
heading: '#89ddff',
|
||||
invalid: '#ff5370',
|
||||
regexp: '#b4f9f8',
|
||||
}
|
||||
|
||||
export const tokyoNightStormTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const tokyoNightStormHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const tokyoNightStorm: Extension = [
|
||||
tokyoNightStormTheme,
|
||||
syntaxHighlighting(tokyoNightStormHighlightStyle),
|
||||
]
|
@@ -1,128 +0,0 @@
|
||||
import {EditorView} from '@codemirror/view'
|
||||
import {Extension} from '@codemirror/state'
|
||||
import {HighlightStyle, syntaxHighlighting} from '@codemirror/language'
|
||||
import {tags as t} from '@lezer/highlight'
|
||||
|
||||
export const config = {
|
||||
name: 'tokyoNight',
|
||||
dark: true,
|
||||
background: '#1a1b26',
|
||||
foreground: '#787c99',
|
||||
selection: '#515c7e40',
|
||||
cursor: '#c0caf5',
|
||||
dropdownBackground: '#1a1b26',
|
||||
dropdownBorder: '#787c99',
|
||||
activeLine: '#43455c22',
|
||||
lineNumber: '#363b54',
|
||||
lineNumberActive: '#737aa2',
|
||||
matchingBracket: '#16161e',
|
||||
keyword: '#bb9af7',
|
||||
storage: '#bb9af7',
|
||||
variable: '#c0caf5',
|
||||
parameter: '#c0caf5',
|
||||
function: '#7aa2f7',
|
||||
string: '#9ece6a',
|
||||
constant: '#bb9af7',
|
||||
type: '#0db9d7',
|
||||
class: '#c0caf5',
|
||||
number: '#ff9e64',
|
||||
comment: '#444b6a',
|
||||
heading: '#89ddff',
|
||||
invalid: '#ff5370',
|
||||
regexp: '#b4f9f8',
|
||||
}
|
||||
|
||||
export const tokyoNightTheme = EditorView.theme({
|
||||
'&': {
|
||||
color: config.foreground,
|
||||
backgroundColor: config.background,
|
||||
},
|
||||
|
||||
'.cm-content': {caretColor: config.cursor},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': {borderLeftColor: config.cursor},
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {backgroundColor: config.selection},
|
||||
|
||||
'.cm-panels': {backgroundColor: config.dropdownBackground, color: config.foreground},
|
||||
'.cm-panels.cm-panels-top': {borderBottom: '2px solid black'},
|
||||
'.cm-panels.cm-panels-bottom': {borderTop: '2px solid black'},
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: config.dropdownBackground,
|
||||
outline: `1px solid ${config.dropdownBorder}`
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: config.selection
|
||||
},
|
||||
|
||||
'.cm-activeLine': {backgroundColor: config.activeLine},
|
||||
'.cm-selectionMatch': {backgroundColor: config.selection},
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: config.matchingBracket,
|
||||
outline: 'none'
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: config.background,
|
||||
color: config.foreground,
|
||||
border: 'none'
|
||||
},
|
||||
'.cm-activeLineGutter': {backgroundColor: config.background},
|
||||
|
||||
'.cm-lineNumbers .cm-gutterElement': {color: config.lineNumber},
|
||||
'.cm-lineNumbers .cm-activeLineGutter': {color: config.lineNumberActive},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: config.foreground
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: `1px solid ${config.dropdownBorder}`,
|
||||
backgroundColor: config.dropdownBackground,
|
||||
color: config.foreground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent'
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: config.foreground,
|
||||
borderBottomColor: config.foreground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
background: config.selection,
|
||||
color: config.foreground,
|
||||
}
|
||||
}
|
||||
}, {dark: config.dark})
|
||||
|
||||
export const tokyoNightHighlightStyle = HighlightStyle.define([
|
||||
{tag: t.keyword, color: config.keyword},
|
||||
{tag: [t.name, t.deleted, t.character, t.macroName], color: config.variable},
|
||||
{tag: [t.propertyName], color: config.function},
|
||||
{tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)], color: config.string},
|
||||
{tag: [t.function(t.variableName), t.labelName], color: config.function},
|
||||
{tag: [t.color, t.constant(t.name), t.standard(t.name)], color: config.constant},
|
||||
{tag: [t.definition(t.name), t.separator], color: config.variable},
|
||||
{tag: [t.className], color: config.class},
|
||||
{tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: config.number},
|
||||
{tag: [t.typeName], color: config.type, fontStyle: config.type},
|
||||
{tag: [t.operator, t.operatorKeyword], color: config.keyword},
|
||||
{tag: [t.url, t.escape, t.regexp, t.link], color: config.regexp},
|
||||
{tag: [t.meta, t.comment], color: config.comment},
|
||||
{tag: t.strong, fontWeight: 'bold'},
|
||||
{tag: t.emphasis, fontStyle: 'italic'},
|
||||
{tag: t.link, textDecoration: 'underline'},
|
||||
{tag: t.heading, fontWeight: 'bold', color: config.heading},
|
||||
{tag: [t.atom, t.bool, t.special(t.variableName)], color: config.variable},
|
||||
{tag: t.invalid, color: config.invalid},
|
||||
{tag: t.strikethrough, textDecoration: 'line-through'},
|
||||
])
|
||||
|
||||
export const tokyoNight: Extension = [
|
||||
tokyoNightTheme,
|
||||
syntaxHighlighting(tokyoNightHighlightStyle),
|
||||
]
|
@@ -1,19 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { useThemeStore } from '@/stores/themeStore';
|
||||
import { useErrorHandler } from '@/utils/errorHandler';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import type { ThemeType, SystemThemeType } from '@/types';
|
||||
import { LanguageType } from '@/../bindings/voidraft/internal/models';
|
||||
import { AVAILABLE_THEMES } from '@/types/theme';
|
||||
import { useTheme } from '@/composables/useTheme';
|
||||
import { SystemThemeType, LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||
|
||||
const { t } = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { safeCall } = useErrorHandler();
|
||||
const { setTheme: setThemeComposable } = useTheme();
|
||||
|
||||
// 语言选项
|
||||
const languageOptions = [
|
||||
@@ -23,9 +20,9 @@ const languageOptions = [
|
||||
|
||||
// 系统主题选项
|
||||
const systemThemeOptions = [
|
||||
{ value: 'dark' as SystemThemeType, label: t('systemTheme.dark') },
|
||||
{ value: 'light' as SystemThemeType, label: t('systemTheme.light') },
|
||||
{ value: 'auto' as SystemThemeType, label: t('systemTheme.auto') },
|
||||
{ value: SystemThemeType.SystemThemeDark, label: t('systemTheme.dark') },
|
||||
{ value: SystemThemeType.SystemThemeLight, label: t('systemTheme.light') },
|
||||
{ value: SystemThemeType.SystemThemeAuto, label: t('systemTheme.auto') },
|
||||
];
|
||||
|
||||
// 更新语言设置
|
||||
@@ -45,42 +42,10 @@ const updateSystemTheme = async (event: Event) => {
|
||||
const selectedSystemTheme = select.value as SystemThemeType;
|
||||
|
||||
await safeCall(
|
||||
() => configStore.setSystemTheme(selectedSystemTheme),
|
||||
() => themeStore.setTheme(selectedSystemTheme),
|
||||
'config.systemThemeChangeFailed'
|
||||
);
|
||||
};
|
||||
|
||||
// 主题选择
|
||||
const themeOptions = computed(() => AVAILABLE_THEMES);
|
||||
const selectedTheme = ref<ThemeType>(configStore.config.appearance.theme || 'default-dark' as ThemeType);
|
||||
|
||||
// 当前主题预览信息
|
||||
const currentPreviewTheme = computed(() => {
|
||||
const theme = themeOptions.value.find(t => t.id === selectedTheme.value);
|
||||
return theme || themeOptions.value[0];
|
||||
});
|
||||
|
||||
// 选择主题
|
||||
const selectTheme = async (themeId: ThemeType) => {
|
||||
selectedTheme.value = themeId;
|
||||
|
||||
// 更新配置(这会自动触发编辑器主题更新)
|
||||
await safeCall(
|
||||
() => configStore.setTheme(themeId),
|
||||
'config.themeChangeFailed'
|
||||
);
|
||||
|
||||
// 同步更新预览(用于设置页面的预览区域)
|
||||
await setThemeComposable(themeId);
|
||||
};
|
||||
|
||||
// 监听配置变化,同步主题选择
|
||||
watch(() => configStore.config.appearance.theme, (newTheme) => {
|
||||
if (newTheme && newTheme !== selectedTheme.value) {
|
||||
selectedTheme.value = newTheme;
|
||||
setThemeComposable(newTheme);
|
||||
}
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -104,64 +69,6 @@ watch(() => configStore.config.appearance.theme, (newTheme) => {
|
||||
</select>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection :title="t('settings.appearance')">
|
||||
<div class="appearance-content">
|
||||
<div class="theme-selection-area">
|
||||
<div class="theme-selector">
|
||||
<div class="selector-label">{{ t('settings.theme') }}</div>
|
||||
<div class="theme-options">
|
||||
<div
|
||||
v-for="theme in themeOptions"
|
||||
:key="theme.id"
|
||||
class="theme-option"
|
||||
:class="{ active: selectedTheme === theme.id }"
|
||||
@click="selectTheme(theme.id)"
|
||||
>
|
||||
<div class="color-preview" :style="{ backgroundColor: theme.previewColors.background }"></div>
|
||||
<div class="theme-name">{{ theme.displayName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-area">
|
||||
<div class="editor-preview" :style="{ backgroundColor: currentPreviewTheme.previewColors.background }">
|
||||
<div class="preview-header" :style="{ backgroundColor: currentPreviewTheme.previewColors.background, borderBottomColor: currentPreviewTheme.previewColors.foreground + '33' }">
|
||||
<div class="preview-title" :style="{ color: currentPreviewTheme.previewColors.foreground }">{{ currentPreviewTheme.displayName }} 预览</div>
|
||||
</div>
|
||||
<div class="preview-content" :style="{ color: currentPreviewTheme.previewColors.foreground }">
|
||||
<div class="preview-line">
|
||||
<span class="line-number" :style="{ color: currentPreviewTheme.previewColors.comment }">1</span>
|
||||
<span class="keyword" :style="{ color: currentPreviewTheme.previewColors.keyword }">function</span>
|
||||
<span> </span>
|
||||
<span class="function" :style="{ color: currentPreviewTheme.previewColors.function }">exampleFunc</span>() {
|
||||
</div>
|
||||
<div class="preview-line">
|
||||
<span class="line-number" :style="{ color: currentPreviewTheme.previewColors.comment }">2</span>
|
||||
<span> </span>
|
||||
<span class="keyword" :style="{ color: currentPreviewTheme.previewColors.keyword }">const</span>
|
||||
<span> hello = </span>
|
||||
<span class="string" :style="{ color: currentPreviewTheme.previewColors.string }">"你好,世界!"</span>;
|
||||
</div>
|
||||
<div class="preview-line">
|
||||
<span class="line-number" :style="{ color: currentPreviewTheme.previewColors.comment }">3</span>
|
||||
<span> </span>
|
||||
<span class="function" :style="{ color: currentPreviewTheme.previewColors.function }">console.log</span>(hello);
|
||||
</div>
|
||||
<div class="preview-line">
|
||||
<span class="line-number" :style="{ color: currentPreviewTheme.previewColors.comment }">4</span>
|
||||
<span> </span>
|
||||
<span class="comment" :style="{ color: currentPreviewTheme.previewColors.comment }">// 这是中文注释</span>
|
||||
</div>
|
||||
<div class="preview-line">
|
||||
<span class="line-number" :style="{ color: currentPreviewTheme.previewColors.comment }">5</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -171,30 +78,6 @@ watch(() => configStore.config.appearance.theme, (newTheme) => {
|
||||
padding-bottom: 48px;
|
||||
}
|
||||
|
||||
.appearance-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
gap: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-selection-area {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.preview-area {
|
||||
flex: 0 0 400px;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
.select-input {
|
||||
min-width: 150px;
|
||||
padding: 8px 12px;
|
||||
@@ -221,158 +104,4 @@ watch(() => configStore.config.appearance.theme, (newTheme) => {
|
||||
color: var(--settings-text);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-selector {
|
||||
padding: 0;
|
||||
|
||||
.selector-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.theme-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 16px;
|
||||
justify-content: start;
|
||||
|
||||
.theme-option {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.color-preview {
|
||||
height: 70px;
|
||||
border-radius: 6px;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #c0c0c0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
|
||||
.color-preview {
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.color-preview {
|
||||
border-color: #4a9eff;
|
||||
box-shadow: 0 4px 20px rgba(74, 158, 255, 0.3);
|
||||
|
||||
&::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #4a9eff;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 4px rgba(74, 158, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-name {
|
||||
color: #4a9eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-preview {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
|
||||
.preview-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
.preview-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&::before {
|
||||
content: '🎨';
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-content {
|
||||
padding: 16px 0;
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
|
||||
.preview-line {
|
||||
padding: 2px 16px;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.line-number {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
margin-right: 12px;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coming-soon-placeholder {
|
||||
padding: 20px;
|
||||
background-color: #333333;
|
||||
border-radius: 6px;
|
||||
color: #a0a0a0;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user