Refactor RsCodeMirror Component for Enhanced Language and Theme Handling
- Introduced helper functions to manage language and theme extensions, improving code organization and readability. - Implemented dynamic initialization of extensions based on selected language and theme, ensuring accurate syntax highlighting. - Added fullscreen functionality with keyboard shortcuts for a better user experience, including visual feedback for fullscreen mode. - Enhanced template structure and styles for improved responsiveness and usability, particularly in fullscreen mode. - Updated event handling for keyboard shortcuts to streamline code formatting and fullscreen toggling. - Ensured proper cleanup of body overflow styles on component unmount to maintain layout integrity.
This commit is contained in:
parent
07539e2344
commit
4b23da5239
@ -89,29 +89,62 @@ const dropdownThemes = ref([
|
|||||||
|
|
||||||
const value = ref(props.modelValue);
|
const value = ref(props.modelValue);
|
||||||
const extensions = ref([]);
|
const extensions = ref([]);
|
||||||
if (props.language == "vue") {
|
|
||||||
|
// Helper function to get theme extension
|
||||||
|
const getThemeExtension = (themeVal) => {
|
||||||
|
switch (themeVal) {
|
||||||
|
case "oneDark":
|
||||||
|
return oneDark;
|
||||||
|
case "amy":
|
||||||
|
return amy;
|
||||||
|
case "ayuLight":
|
||||||
|
return ayuLight;
|
||||||
|
case "barf":
|
||||||
|
return barf;
|
||||||
|
case "cobalt":
|
||||||
|
return cobalt;
|
||||||
|
case "dracula":
|
||||||
|
return dracula;
|
||||||
|
case "clouds":
|
||||||
|
default:
|
||||||
|
return clouds;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get language extension
|
||||||
|
const getLanguageExtension = () => {
|
||||||
|
switch (props.language) {
|
||||||
|
case "vue":
|
||||||
|
return vue();
|
||||||
|
case "css":
|
||||||
|
case "scss":
|
||||||
|
return css();
|
||||||
|
case "html":
|
||||||
|
return css(); // Use CSS for HTML highlighting
|
||||||
|
case "javascript":
|
||||||
|
case "js":
|
||||||
|
default:
|
||||||
|
return javascript();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize extensions with proper theme
|
||||||
|
const initializeExtensions = () => {
|
||||||
|
const currentTheme = props.theme || editorTheme.value || "oneDark";
|
||||||
|
const themeExtension = getThemeExtension(currentTheme);
|
||||||
|
const languageExtension = getLanguageExtension();
|
||||||
|
|
||||||
extensions.value = [
|
extensions.value = [
|
||||||
vue(),
|
languageExtension,
|
||||||
oneDark,
|
themeExtension,
|
||||||
autocompletion(),
|
autocompletion(),
|
||||||
indentUnit.of(" "),
|
indentUnit.of(" "),
|
||||||
indentOnInput(),
|
indentOnInput(),
|
||||||
];
|
];
|
||||||
} else if (props.language == "javascript") {
|
};
|
||||||
extensions.value = [
|
|
||||||
javascript(),
|
// Initialize extensions on component creation
|
||||||
oneDark,
|
initializeExtensions();
|
||||||
autocompletion(),
|
|
||||||
indentUnit.of(" "),
|
|
||||||
indentOnInput(),
|
|
||||||
];
|
|
||||||
} else if (props.language == "css") {
|
|
||||||
extensions.value = [
|
|
||||||
css(),
|
|
||||||
oneDark,
|
|
||||||
autocompletion(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalLines = ref(0);
|
const totalLines = ref(0);
|
||||||
const totalLength = ref(0);
|
const totalLength = ref(0);
|
||||||
@ -124,35 +157,45 @@ const handleReady = (payload) => {
|
|||||||
totalLength.value = view.value.state.doc.length;
|
totalLength.value = view.value.state.doc.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Watch for theme changes
|
||||||
watch(
|
watch(
|
||||||
() => editorTheme.value,
|
() => props.theme || editorTheme.value,
|
||||||
(themeVal) => {
|
(newTheme) => {
|
||||||
const themeExtension =
|
if (newTheme) {
|
||||||
themeVal === "oneDark"
|
const themeExtension = getThemeExtension(newTheme);
|
||||||
? oneDark
|
const languageExtension = getLanguageExtension();
|
||||||
: themeVal === "amy"
|
|
||||||
? amy
|
|
||||||
: themeVal === "ayuLight"
|
|
||||||
? ayuLight
|
|
||||||
: themeVal === "barf"
|
|
||||||
? barf
|
|
||||||
: themeVal === "cobalt"
|
|
||||||
? cobalt
|
|
||||||
: themeVal === "dracula"
|
|
||||||
? dracula
|
|
||||||
: clouds;
|
|
||||||
|
|
||||||
if (props.language == "vue") {
|
|
||||||
extensions.value = [
|
extensions.value = [
|
||||||
vue(),
|
languageExtension,
|
||||||
themeExtension,
|
themeExtension,
|
||||||
autocompletion(),
|
autocompletion(),
|
||||||
indentUnit.of(" "),
|
indentUnit.of(" "),
|
||||||
indentOnInput(),
|
indentOnInput(),
|
||||||
];
|
];
|
||||||
} else {
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Watch for language changes
|
||||||
|
watch(
|
||||||
|
() => props.language,
|
||||||
|
() => {
|
||||||
|
initializeExtensions();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Watch for editorTheme store changes
|
||||||
|
watch(
|
||||||
|
() => editorTheme.value,
|
||||||
|
(newTheme) => {
|
||||||
|
if (!props.theme && newTheme) {
|
||||||
|
// Only update if no explicit theme prop is provided
|
||||||
|
const themeExtension = getThemeExtension(newTheme);
|
||||||
|
const languageExtension = getLanguageExtension();
|
||||||
|
|
||||||
extensions.value = [
|
extensions.value = [
|
||||||
javascript(),
|
languageExtension,
|
||||||
themeExtension,
|
themeExtension,
|
||||||
autocompletion(),
|
autocompletion(),
|
||||||
indentUnit.of(" "),
|
indentUnit.of(" "),
|
||||||
@ -244,12 +287,49 @@ const formatCurrentCode = async () => {
|
|||||||
|
|
||||||
const debouncedFormatCode = useDebounceFn(formatCurrentCode, 300);
|
const debouncedFormatCode = useDebounceFn(formatCurrentCode, 300);
|
||||||
|
|
||||||
|
// Fullscreen functionality
|
||||||
|
const isFullscreen = ref(false);
|
||||||
|
const fullscreenContainer = ref(null);
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
isFullscreen.value = !isFullscreen.value;
|
||||||
|
|
||||||
|
if (isFullscreen.value) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
// Focus the editor after entering fullscreen
|
||||||
|
nextTick(() => {
|
||||||
|
if (view.value) {
|
||||||
|
view.value.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const exitFullscreen = () => {
|
||||||
|
if (isFullscreen.value) {
|
||||||
|
isFullscreen.value = false;
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e) => {
|
||||||
// Press Shift + Alt + F to format code
|
// Press Shift + Alt + F to format code
|
||||||
if (e.shiftKey && e.altKey && e.key === "F") {
|
if (e.shiftKey && e.altKey && e.key === "F") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
debouncedFormatCode();
|
debouncedFormatCode();
|
||||||
}
|
}
|
||||||
|
// Press F11 or Ctrl+Shift+F to toggle fullscreen
|
||||||
|
if (e.key === "F11" || (e.ctrlKey && e.shiftKey && e.key === "F")) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleFullscreen();
|
||||||
|
}
|
||||||
|
// Press Escape to exit fullscreen
|
||||||
|
if (e.key === "Escape" && isFullscreen.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
exitFullscreen();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -259,6 +339,10 @@ onMounted(() => {
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("keydown", handleKeyDown);
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
// Ensure body overflow is reset
|
||||||
|
if (isFullscreen.value) {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add this watch effect after the value ref declaration
|
// Add this watch effect after the value ref declaration
|
||||||
@ -274,38 +358,70 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="props.class">
|
<div
|
||||||
|
ref="fullscreenContainer"
|
||||||
|
:class="[
|
||||||
|
props.class,
|
||||||
|
{
|
||||||
|
'fullscreen-editor': isFullscreen,
|
||||||
|
'fixed inset-0 z-[9999] bg-[#282C34]': isFullscreen
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf]"
|
class="flex justify-between items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf] border-b border-gray-600"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
Theme:
|
<div class="flex items-center gap-2">
|
||||||
<FormKit
|
Theme:
|
||||||
v-model="editorTheme"
|
<FormKit
|
||||||
type="select"
|
v-model="editorTheme"
|
||||||
placeholder="Select Themes"
|
type="select"
|
||||||
:options="dropdownThemes"
|
placeholder="Select Themes"
|
||||||
:classes="{
|
:options="dropdownThemes"
|
||||||
input:
|
:classes="{
|
||||||
'!bg-[#282C34] !text-[#abb2bf] !border-[#abb2bf] hover:cursor-pointer h-6 w-[100px]',
|
input:
|
||||||
inner: ' !rounded-none !mb-0',
|
'!bg-[#282C34] !text-[#abb2bf] !border-[#abb2bf] hover:cursor-pointer h-6 w-[100px]',
|
||||||
outer: '!mb-0',
|
inner: ' !rounded-none !mb-0',
|
||||||
}"
|
outer: '!mb-0',
|
||||||
/>
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="isFullscreen" class="text-sm text-gray-300 ml-4">
|
||||||
|
<Icon name="material-symbols:info-outline" class="w-4 h-4 inline mr-1" />
|
||||||
|
Press <kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded">Esc</kbd> or
|
||||||
|
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded">F11</kbd> to exit fullscreen
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
@click="toggleFullscreen"
|
||||||
|
class="px-3 py-1 bg-gray-600 hover:bg-gray-500 text-sm rounded transition-colors flex items-center"
|
||||||
|
:title="isFullscreen ? 'Exit Fullscreen (F11)' : 'Enter Fullscreen (F11)'"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:name="isFullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"
|
||||||
|
class="!w-4 !h-4 mr-2"
|
||||||
|
/>
|
||||||
|
{{ isFullscreen ? 'Exit Fullscreen' : 'Fullscreen' }}
|
||||||
|
</button>
|
||||||
|
<rs-button
|
||||||
|
@click="formatCurrentCode"
|
||||||
|
class="px-3 py-1 bg-blue-600 hover:bg-blue-500 text-sm rounded transition-colors"
|
||||||
|
>
|
||||||
|
<Icon name="vscode-icons:file-type-prettier" class="!w-5 !h-5 mr-2" />
|
||||||
|
Format Code (Shift + Alt + F)
|
||||||
|
</rs-button>
|
||||||
</div>
|
</div>
|
||||||
<rs-button
|
|
||||||
@click="formatCurrentCode"
|
|
||||||
class="px-3 py-1 bg-blue-600 text-sm rounded"
|
|
||||||
>
|
|
||||||
<Icon name="vscode-icons:file-type-prettier" class="!w-5 !h-5 mr-2" />
|
|
||||||
Format Code (Shift + Alt + F)
|
|
||||||
</rs-button>
|
|
||||||
</div>
|
</div>
|
||||||
<client-only>
|
<client-only>
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
v-model="value"
|
v-model="value"
|
||||||
placeholder="Code goes here..."
|
placeholder="Code goes here..."
|
||||||
:style="{ height: height }"
|
:style="{
|
||||||
|
height: isFullscreen ? 'calc(100vh - 120px)' : height,
|
||||||
|
minHeight: isFullscreen ? 'calc(100vh - 120px)' : 'auto'
|
||||||
|
}"
|
||||||
:autofocus="true"
|
:autofocus="true"
|
||||||
:indent-with-tab="true"
|
:indent-with-tab="true"
|
||||||
:tab-size="2"
|
:tab-size="2"
|
||||||
@ -319,12 +435,107 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</client-only>
|
</client-only>
|
||||||
<div
|
<div
|
||||||
class="footer flex justify-end items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf]"
|
class="footer flex justify-between items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf] border-t border-gray-600"
|
||||||
>
|
>
|
||||||
<span class="">Lines: {{ numberComma(totalLines) }}</span>
|
<div class="flex items-center gap-4 text-sm">
|
||||||
<span class="">Length: {{ numberComma(totalLength) }}</span>
|
<span v-if="isFullscreen" class="text-gray-300">
|
||||||
|
<Icon name="material-symbols:keyboard" class="w-4 h-4 inline mr-1" />
|
||||||
|
Shortcuts:
|
||||||
|
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-1">Ctrl+Shift+F</kbd> Format,
|
||||||
|
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-1">F11</kbd> Toggle Fullscreen
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<span class="">Lines: {{ numberComma(totalLines) }}</span>
|
||||||
|
<span class="">Length: {{ numberComma(totalLength) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Fullscreen editor styles */
|
||||||
|
.fullscreen-editor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: #282C34;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-editor .footer,
|
||||||
|
.fullscreen-editor .header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-editor :deep(.cm-editor) {
|
||||||
|
flex: 1;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-editor :deep(.cm-scroller) {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth transitions */
|
||||||
|
.fullscreen-editor {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard shortcut styling */
|
||||||
|
kbd {
|
||||||
|
@apply inline-flex items-center px-2 py-1 bg-gray-700 text-gray-200 text-xs font-mono rounded shadow;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Better button styling */
|
||||||
|
button:focus {
|
||||||
|
@apply outline-none ring-2 ring-blue-500 ring-offset-2 ring-offset-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for fullscreen */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.fullscreen-editor .footer,
|
||||||
|
.fullscreen-editor .header {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-editor .footer span,
|
||||||
|
.fullscreen-editor .header span {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-editor button {
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation for entering fullscreen */
|
||||||
|
@keyframes fadeInScale {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-editor {
|
||||||
|
animation: fadeInScale 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles for better accessibility */
|
||||||
|
.fullscreen-editor :deep(.cm-focused) {
|
||||||
|
outline: 2px solid #3b82f6;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user