Commit af99f91a authored by Administrator's avatar Administrator
Browse files

added basic function for markdown previewer

parent 95199a50
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"jspdf": "^3.0.1", "jspdf": "^3.0.1",
"marked": "^16.1.1",
"mermaid": "^11.9.0", "mermaid": "^11.9.0",
"svg2pdf.js": "^2.5.0", "svg2pdf.js": "^2.5.0",
"vite-plugin-vuetify": "^2.1.1", "vite-plugin-vuetify": "^2.1.1",
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"jspdf": "^3.0.1", "jspdf": "^3.0.1",
"marked": "^16.1.1",
"mermaid": "^11.9.0", "mermaid": "^11.9.0",
"svg2pdf.js": "^2.5.0", "svg2pdf.js": "^2.5.0",
"vite-plugin-vuetify": "^2.1.1", "vite-plugin-vuetify": "^2.1.1",
......
<template> <template>
<div> <div class="text-to-markdown">
<h2>Text to Markdown Converter</h2> <h1 class="page-title">Text to Markdown Converter</h1>
<!-- Add your converter UI here -->
<textarea v-model="input" placeholder="Enter text here"></textarea> <div class="converter-container">
<div> <!-- Input Section -->
<h3>Markdown Output:</h3> <div class="input-section">
<pre>{{ output }}</pre> <h3>Input Text</h3>
<textarea
v-model="inputText"
@input="handleInput"
placeholder="Enter your text here to convert to Markdown preview..."
class="text-input"
rows="10"
></textarea>
<div class="button-group">
<button @click="convertNow" class="convert-btn" :disabled="isProcessing">
{{ isProcessing ? 'Converting...' : 'Convert Now' }}
</button>
<button @click="clearAll" class="clear-btn">Clear All</button>
</div>
<!-- Status Indicator -->
<div class="status" :class="currentState">
<span v-if="currentState === 'typing'">Typing...</span>
<span v-else-if="currentState === 'processing'">Converting...</span>
<span v-else-if="currentState === 'ready'">Ready</span>
<span v-else-if="currentState === 'error'">Error occurred</span>
</div>
</div>
<!-- Output Section -->
<div class="output-section">
<h3>Markdown Preview</h3>
<!-- Preview Section -->
<div class="diagram-section">
<div class="diagram-header">
<h4>Visual Preview:</h4>
<!-- Preview Controls -->
<div class="diagram-controls">
<div class="zoom-controls">
<label for="zoom-slider" class="zoom-label">Zoom:</label>
<input
id="zoom-slider"
type="range"
v-model="zoomLevel"
@input="updateZoom"
min="0.5"
max="3"
step="0.1"
class="zoom-slider"
/>
<span class="zoom-level">{{ Math.round(zoomLevel * 100) }}%</span>
<button @click="resetZoom" class="zoom-btn reset-btn">Reset</button>
</div>
<button @click="toggleFullscreen" class="fullscreen-btn" v-if="markdownCode">
Fullscreen
</button>
</div>
</div>
<div class="markdown-container" ref="markdownContainerWrapper">
<div
v-if="markdownCode"
class="markdown-preview"
ref="markdownContainer"
:style="{ transform: `scale(${zoomLevel})` }"
v-html="renderedMarkdown"
></div>
<div v-else class="placeholder">
<p>Your Markdown preview will appear here</p>
</div>
</div>
</div>
<!-- Fullscreen Modal -->
<div v-if="isFullscreen" class="fullscreen-modal" @click="closeFullscreen">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>Markdown Preview - Fullscreen View</h3>
<div class="modal-controls">
<div class="zoom-controls">
<label for="fullscreen-zoom-slider" class="zoom-label">Zoom:</label>
<input
id="fullscreen-zoom-slider"
type="range"
v-model="fullscreenZoomLevel"
@input="updateFullscreenZoom"
min="0.5"
max="3"
step="0.1"
class="zoom-slider"
/>
<span class="zoom-level">{{ Math.round(fullscreenZoomLevel * 100) }}%</span>
<button @click="resetFullscreenZoom" class="zoom-btn reset-btn">Reset</button>
</div>
<!-- Single Download Button -->
<button @click="downloadPreview" class="download-btn" v-if="markdownCode">
Download
</button>
<button @click="closeFullscreen" class="close-btn">&times;</button>
</div>
</div>
<div
class="modal-body"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="stopDrag"
@mouseleave="stopDrag"
@wheel="onWheel"
@contextmenu.prevent
>
<div
class="fullscreen-preview"
ref="fullscreenPreview"
:style="{
cursor: isDragging ? 'grabbing' : 'grab'
}"
>
<div
class="fullscreen-preview-inner"
:style="{
transform: `scale(${fullscreenZoomLevel}) translate(${dragOffset.x}px, ${dragOffset.y}px)`,
'transform-origin': 'center center',
width: '100%',
height: '100%'
}"
v-html="renderedMarkdown"
></div>
</div>
</div>
</div>
</div>
<!-- Generated Markdown Section -->
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { marked } from 'marked'
// State variables
const inputText = ref('')
const markdownCode = ref('')
const markdownContainer = ref(null)
const isProcessing = ref(false)
const currentState = ref('ready') // 'typing', 'processing', 'ready', 'error'
const zoomLevel = ref(1)
const markdownContainerWrapper = ref(null)
// Fullscreen state
const isFullscreen = ref(false)
const fullscreenPreview = ref(null)
const fullscreenZoomLevel = ref(1)
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
const dragOffset = ref({ x: 0, y: 0 })
let debounceTimeout = null
// Debounced conversion logic
const handleInput = () => {
currentState.value = 'typing'
if (debounceTimeout) clearTimeout(debounceTimeout)
debounceTimeout = setTimeout(() => {
convertToMarkdown()
}, 500)
}
// Update zoom functions
const updateZoom = () => {}
const resetZoom = () => { zoomLevel.value = 1 }
const input = ref('') // Fullscreen functions
const output = computed(() => { const toggleFullscreen = () => {
// For now, just echo input. Replace with real conversion logic later. if (isFullscreen.value) {
return input.value closeFullscreen()
} else {
openFullscreen()
}
}
const openFullscreen = async () => {
isFullscreen.value = true
fullscreenZoomLevel.value = 1
dragOffset.value = { x: 0, y: 0 }
// No need to copy innerHTML!
// await nextTick()
// if (fullscreenPreview.value && markdownContainer.value) {
// fullscreenPreview.value.innerHTML = markdownContainer.value.innerHTML
// }
}
const closeFullscreen = () => {
isDragging.value = false
isFullscreen.value = false
fullscreenZoomLevel.value = 1
dragOffset.value = { x: 0, y: 0 }
}
// Markdown conversion
const convertToMarkdown = async () => {
if (!inputText.value.trim()) {
markdownCode.value = ''
if (markdownContainer.value) markdownContainer.value.innerHTML = ''
currentState.value = 'ready'
closeFullscreen()
return
}
isProcessing.value = true
currentState.value = 'processing'
try {
// For now, just use the input as Markdown
markdownCode.value = inputText.value
await nextTick()
if (markdownContainer.value) {
markdownContainer.value.innerHTML = marked.parse(markdownCode.value)
resetZoom()
closeFullscreen()
}
currentState.value = 'ready'
} catch (error) {
currentState.value = 'error'
markdownCode.value = ''
if (markdownContainer.value) markdownContainer.value.innerHTML = ''
closeFullscreen()
} finally {
isProcessing.value = false
}
}
const convertNow = () => {
if (debounceTimeout) clearTimeout(debounceTimeout)
convertToMarkdown()
}
const clearAll = () => {
inputText.value = ''
markdownCode.value = ''
if (markdownContainer.value) markdownContainer.value.innerHTML = ''
currentState.value = 'ready'
}
// Rendered Markdown (computed for v-html)
const renderedMarkdown = computed(() => {
return markdownCode.value ? marked.parse(markdownCode.value) : ''
})
// Keyboard handler
const handleKeydown = (event) => {
if (event.key === 'Escape' && isFullscreen.value) {
closeFullscreen()
}
}
// Fullscreen zoom functions
const updateFullscreenZoom = () => {}
const resetFullscreenZoom = () => {
fullscreenZoomLevel.value = 1
dragOffset.value = { x: 0, y: 0 }
}
// Drag functions
const startDrag = (event) => {
if (event.target.closest('.modal-header')) return
isDragging.value = true
dragStart.value = {
x: event.clientX - dragOffset.value.x,
y: event.clientY - dragOffset.value.y
}
event.preventDefault()
}
const onDrag = (event) => {
if (!isDragging.value) return
dragOffset.value = {
x: event.clientX - dragStart.value.x,
y: event.clientY - dragStart.value.y
}
event.preventDefault()
}
const stopDrag = () => {
isDragging.value = false
}
// Zoom-to-mouse for fullscreen
const onWheel = (event) => {
event.preventDefault()
const modalBody = event.currentTarget
const rect = modalBody.getBoundingClientRect()
const mouseX = event.clientX - rect.left
const mouseY = event.clientY - rect.top
const oldZoom = fullscreenZoomLevel.value
const zoomSpeed = 0.1
const delta = event.deltaY > 0 ? -zoomSpeed : zoomSpeed
const newZoom = Math.max(0.5, Math.min(3, oldZoom + delta))
if (Math.abs(newZoom - oldZoom) > 0.01) {
const centerX = rect.width / 2
const centerY = rect.height / 2
const currentMouseOffsetX = mouseX - centerX
const currentMouseOffsetY = mouseY - centerY
const diagramPointX = (currentMouseOffsetX - dragOffset.value.x) / oldZoom
const diagramPointY = (currentMouseOffsetY - dragOffset.value.y) / oldZoom
fullscreenZoomLevel.value = Math.round(newZoom * 10) / 10
dragOffset.value.x = currentMouseOffsetX - (diagramPointX * newZoom)
dragOffset.value.y = currentMouseOffsetY - (diagramPointY * newZoom)
}
}
// Download rendered HTML
const downloadPreview = async () => {
try {
if (!markdownCode.value) {
alert('No preview to download. Please generate a preview first.')
return
}
// Use the rendered HTML
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Markdown Preview</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 40px; }
</style>
</head>
<body>
${marked.parse(markdownCode.value)}
</body>
</html>
`
const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `markdown-preview-${Date.now()}.html`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
alert('Preview downloaded successfully as HTML file!')
} catch (error) {
alert('Error downloading preview')
}
}
// Mount/unmount
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
}) })
</script> </script>
<style scoped> <style scoped>
textarea { .text-to-markdown {
/* max-width: 2400px; */
display: grid;
grid-template-rows: 1fr;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 10px;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.converter-container {
display: grid;
grid-template-columns: 1fr 1.5fr;
grid-template-rows: 1fr;
gap: 40px;
margin: 40px;
}
.input-section, .output-section {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.output-section {
/* background: #f8f9fa; */
padding: 20px;
border-radius: 8px;
border: 1px solid #e9ecef;
max-width: 100%;
}
.text-input {
width: 100%;
max-width: 100%; /* Increased maximum width */
padding: 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 14px;
resize: vertical;
box-sizing: border-box;
}
.button-group {
margin-top: 15px;
display: flex;
gap: 10px;
}
.convert-btn, .clear-btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
}
.convert-btn {
background-color: #007bff;
color: white;
}
.convert-btn:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.clear-btn {
background-color: #6c757d;
color: white;
}
.clear-btn:hover {
background-color: #545b62;
}
.status {
margin-top: 10px;
font-size: 14px;
}
.status.typing { color: #888; }
.status.processing { color: #007bff; }
.status.ready { color: #28a745; }
.status.error { color: #dc3545; }
/* Diagram Section (now first) */
.diagram-section {
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
margin-bottom: 20px; /* Add space between diagram and code */
}
/* Code Section (now second) */
.code-section {
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
overflow: hidden;
margin-bottom: 0; /* No bottom margin since it's last */
}
.code-section h4 {
margin: 0;
padding: 15px 20px;
background: #e9ecef;
color: #495057;
border-bottom: 1px solid #dee2e6;
}
.code-container {
max-height: 300px; /* Limit height */
overflow-y: auto; /* Vertical scroll */
overflow-x: auto; /* Horizontal scroll for long lines */
padding: 15px 20px;
}
.code-container pre {
margin: 0;
white-space: pre-wrap;
font-family: 'Courier New', monospace;
font-size: 12px;
color: #495057;
line-height: 1.4;
}
/* Diagram Header with Zoom Controls */
.diagram-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 32px; /* Increased right/left padding */
background: #e9ecef;
border-bottom: 1px solid #dee2e6;
max-width: 100%;
overflow-x: auto;
}
.diagram-header h4 {
margin: 0;
color: #495057;
}
/* Diagram Controls Layout */
.diagram-controls {
display: flex;
align-items: center;
gap: 15px;
flex-shrink: 0;
min-width: 320px; /* Ensure enough width for controls */
flex-wrap: nowrap; /* Prevent wrapping */
max-width: 100%;
overflow-x: auto;
}
/* Zoom Controls with Slider */
.zoom-controls {
display: flex;
align-items: center;
gap: 10px;
}
.zoom-label {
font-size: 14px;
color: #495057;
font-weight: 500;
white-space: nowrap;
}
.zoom-slider {
width: 100px;
height: 8px; /* Slightly taller */
border-radius: 4px;
background: #dee2e6; /* Darker background */
outline: none;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
border: 1px solid #adb5bd; /* Darker border */
}
/* Webkit browsers (Chrome, Safari, Edge) */
.zoom-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s;
}
.zoom-slider::-webkit-slider-thumb:hover {
background: #0056b3;
}
.zoom-slider::-webkit-slider-track {
height: 8px;
border-radius: 4px;
background: #dee2e6;
border: 1px solid #adb5bd;
}
/* Firefox */
.zoom-slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s;
}
.zoom-slider::-moz-range-thumb:hover {
background: #0056b3;
}
.zoom-slider::-moz-range-track {
height: 8px;
border-radius: 4px;
background: #dee2e6;
border: 1px solid #adb5bd;
}
.zoom-level {
min-width: 50px;
text-align: center;
font-weight: 500;
color: #495057;
font-size: 14px;
}
.reset-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
}
.reset-btn:hover {
background: #545b62;
}
.fullscreen-btn {
padding: 8px 16px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.fullscreen-btn:hover {
background: #218838;
}
.fullscreen-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Update mermaid container for zoom */
.markdown-container {
min-height: 100px; /* Very small minimum */
height: auto; /* Let content determine height */
max-height: 400px; /* Still limit maximum */
padding: 15px; /* Reduce padding */
background: white;
overflow: auto;
position: relative;
}
/* Target journey diagrams specifically */
.markdown-container:has(svg[data-diagram-type="journey"]) {
max-height: 350px; /* Smaller max for journey diagrams */
}
.markdown-preview {
text-align: left;
width: 100%; width: 100%;
min-height: 100px; max-width: 100%;
margin-bottom: 1em; transform-origin: top center;
transition: transform 0.2s ease;
}
/* Custom scrollbar for code container */
.code-container::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.code-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.code-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.code-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: #6c757d;
font-style: italic;
} }
pre { /* Fullscreen Modal */
.fullscreen-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
backdrop-filter: blur(5px);
}
.modal-content {
background: white;
border-radius: 8px;
width: 95vw;
height: 95vh;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
/* Modal Controls Layout */
.modal-controls {
display: flex;
align-items: center;
gap: 20px;
flex: 1; /* Take available space */
justify-content: flex-end; /* Align to the right */
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
position: relative;
}
.modal-header h3 {
margin: 0;
color: #495057;
flex-shrink: 0; /* Prevent title from shrinking */
}
.modal-header .zoom-controls {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0; /* Prevent zoom controls from shrinking */
}
/* Update close button positioning */
.close-btn {
background: #dc3545;
border: none;
font-size: 18px;
cursor: pointer;
color: white;
padding: 8px 12px;
width: auto;
height: auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s;
position: static; /* Remove absolute positioning */
top: auto;
right: auto;
z-index: auto;
font-weight: bold;
flex-shrink: 0; /* Prevent close button from shrinking */
}
.close-btn:hover {
background: #c82333;
color: white;
}
/* Ensure proper spacing */
.modal-header .reset-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
margin-right: 10px; /* Add space between reset and close */
}
.modal-header .reset-btn:hover {
background: #545b62;
}
/* Modal body for drag */
.modal-body {
flex: 1;
padding: 20px;
overflow: hidden; /* Hide overflow for drag */
display: flex;
align-items: center;
justify-content: center;
position: relative;
user-select: none; /* Prevent text selection during drag */
will-change: transform;
}
/* Optimize fullscreen diagram for smooth zoom */
.fullscreen-preview {
text-align: left;
width: 100%;
height: 100%;
display: block;
position: relative;
background: white;
padding: 20px;
overflow: auto;
white-space: normal;
word-break: break-word;
overflow-wrap: break-word;
user-select: none;
}
.fullscreen-preview-inner {
width: 100%;
height: 100%;
/* The transform is applied here via :style binding */
}
.fullscreen-preview:active {
cursor: grabbing;
}
.fullscreen-preview svg {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
pointer-events: none;
will-change: transform;
}
/* Add visual feedback for drag state */
.modal-body.dragging {
cursor: grabbing;
}
/* Optional: Add drag instructions */
.modal-header::after {
content: "Drag to pan • Scroll to zoom • Ctrl+Scroll for fine zoom";
font-size: 12px;
color: #6c757d;
margin-left: 20px;
font-style: italic;
}
/* Modal Controls Layout */
.modal-header .zoom-label {
font-size: 14px;
color: #495057;
font-weight: 500;
white-space: nowrap;
}
.modal-header .zoom-slider {
width: 120px; /* Slightly wider for fullscreen */
height: 8px;
border-radius: 4px;
background: #dee2e6;
outline: none;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
border: 1px solid #adb5bd;
}
/* Apply the same slider styling for fullscreen */
.modal-header .zoom-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s;
}
.modal-header .zoom-slider::-webkit-slider-thumb:hover {
background: #0056b3;
}
.modal-header .zoom-slider::-webkit-slider-track {
height: 8px;
border-radius: 4px;
background: #dee2e6;
border: 1px solid #adb5bd;
}
.modal-header .zoom-slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s;
}
.modal-header .zoom-slider::-moz-range-thumb:hover {
background: #0056b3;
}
.modal-header .zoom-slider::-moz-range-track {
height: 8px;
border-radius: 4px;
background: #dee2e6;
border: 1px solid #adb5bd;
}
.modal-header .zoom-level {
min-width: 50px;
text-align: center;
font-weight: 500;
color: #495057;
font-size: 14px;
}
.modal-header .reset-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
}
.modal-header .reset-btn:hover {
background: #545b62;
}
/* Keyboard shortcut hint */
.modal-header::after {
display: none;
}
/* Add smooth zoom transitions */
.fullscreen-preview {
text-align: left;
width: 100%;
height: 100%;
display: flex;
align-items: flex-start;
justify-content: flex-start;
transform-origin: center center;
transition: transform 0.1s ease; /* Smooth zoom and drag */
cursor: grab;
user-select: none;
}
/* Optional: Add zoom indicator */
.modal-body::before {
content: attr(data-zoom);
position: absolute;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
}
.modal-body.zooming::before {
opacity: 1;
}
/* Zoom indicator styles */
.zoom-indicator {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
z-index: 1000;
transition: opacity 0.3s;
opacity: 0;
}
/* Download button in fullscreen - same size as reset button */
.modal-header .download-btn {
padding: 6px 12px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: auto;
font-weight: bold;
flex-shrink: 0;
/* Ensure same dimensions as reset button */
min-width: 80px; /* Slightly wider for "Download" text */
min-height: 32px;
position: relative; /* For dropdown positioning */
}
.modal-header .download-btn:hover {
background: #5a6268;
}
/* Button group for consistent sizing */
.modal-header .button-group {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.modal-header .button-group .download-btn,
.modal-header .button-group .reset-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 32px;
font-weight: bold;
flex-shrink: 0;
}
.modal-header .button-group .download-btn:hover,
.modal-header .button-group .reset-btn:hover {
background: #5a6268;
}
/* Ensure reset button has same dimensions for consistency */
.modal-header .reset-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
min-width: 60px;
min-height: 32px;
font-weight: bold;
flex-shrink: 0;
}
.modal-header .reset-btn:hover {
background: #545b62;
}
/* Fullscreen button in normal window - same size as reset button */
.fullscreen-btn {
padding: 6px 12px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: auto;
font-weight: bold;
flex-shrink: 0;
/* Ensure same dimensions as reset button */
min-width: 80px; /* Slightly wider for "Fullscreen" text */
min-height: 32px;
}
.fullscreen-btn:hover {
background: #0056b3;
}
/* Ensure reset button in normal window has consistent styling */
.reset-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
min-width: 60px;
min-height: 32px;
font-weight: bold;
flex-shrink: 0;
}
.reset-btn:hover {
background: #545b62;
}
/* Ensure zoom controls have consistent button styling */
.zoom-controls .zoom-btn {
padding: 6px 12px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background-color 0.2s;
min-width: 60px;
min-height: 32px;
font-weight: bold;
flex-shrink: 0;
}
.zoom-controls .zoom-btn:hover {
background: #545b62;
}
/* @media (max-width: 768px) {
.converter-container {
grid-template-columns: 1fr;
}
.button-group {
flex-direction: column;
}
}
@media (min-width: 1920px) {
.text-to-mermaid {
max-width: 2800px;
}
.converter-container {
gap: 80px;
}
} */
.page-title {
font-size: 2.4rem; /* Larger, more prominent */
font-weight: 700; /* Bold */
letter-spacing: 1px;
text-align: center;
line-height: 1.2;
/* Optional: add a subtle shadow for depth */
text-shadow: 0 2px 8px rgba(76, 110, 245, 0.08);
/* Optional: add a divider line below */
border-bottom: 1px solid black;
padding-bottom: 12px;
}
/* Markdown content styles */
.markdown-preview,
.fullscreen-preview {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #222;
background: white;
padding: 0;
line-height: 1.7;
word-break: break-word;
}
.markdown-preview h1,
.fullscreen-preview h1 { font-size: 2em; margin: 0.67em 0; }
.markdown-preview h2,
.fullscreen-preview h2 { font-size: 1.5em; margin: 0.75em 0; }
.markdown-preview h3,
.fullscreen-preview h3 { font-size: 1.17em; margin: 0.83em 0; }
.markdown-preview h4,
.fullscreen-preview h4 { font-size: 1em; margin: 1.12em 0; }
.markdown-preview h5,
.fullscreen-preview h5 { font-size: 0.83em; margin: 1.5em 0; }
.markdown-preview h6,
.fullscreen-preview h6 { font-size: 0.75em; margin: 1.67em 0; }
.markdown-preview p,
.fullscreen-preview p { margin: 1em 0; }
.markdown-preview code,
.fullscreen-preview code {
background: #f4f4f4;
color: #c7254e;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 95%;
}
.markdown-preview pre,
.fullscreen-preview pre {
background: #f4f4f4;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 95%;
margin: 1em 0;
}
.markdown-preview blockquote,
.fullscreen-preview blockquote {
border-left: 4px solid #007bff;
background: #f8f9fa;
color: #555;
margin: 1em 0;
padding: 0.5em 1em;
border-radius: 4px;
}
.markdown-preview ul,
.fullscreen-preview ul,
.markdown-preview ol,
.fullscreen-preview ol {
margin: 1em 0 1em 2em;
padding: 0;
}
.markdown-preview table,
.fullscreen-preview table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
.markdown-preview th,
.fullscreen-preview th,
.markdown-preview td,
.fullscreen-preview td {
border: 1px solid #dee2e6;
padding: 8px 12px;
}
.markdown-preview th,
.fullscreen-preview th {
background: #e9ecef;
font-weight: bold;
}
.markdown-preview a,
.fullscreen-preview a {
color: #007bff;
text-decoration: underline;
word-break: break-all;
}
.markdown-preview img,
.fullscreen-preview img {
max-width: 100%;
height: auto;
display: block;
margin: 1em 0;
}
.markdown-preview ul,
.fullscreen-preview-inner ul,
.markdown-preview ol,
.fullscreen-preview-inner ol {
margin-left: 1.5em;
list-style-position: inside;
}
.markdown-preview,
.fullscreen-preview-inner {
padding-left: 1.5em;
}
.markdown-preview ul,
.fullscreen-preview-inner ul,
.markdown-preview ol,
.fullscreen-preview-inner ol {
margin-left: 1.5em;
list-style-position: inside;
}
.markdown-preview pre,
.fullscreen-preview-inner pre {
background: #f4f4f4;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 95%;
margin: 1em 0;
color: #222;
}
.markdown-preview code,
.fullscreen-preview-inner code {
background: #f4f4f4; background: #f4f4f4;
padding: 1em; color: #c7254e;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 95%;
} }
</style> </style>
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment