Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Clark Lin
vue-utility
Commits
af99f91a
Commit
af99f91a
authored
Jul 23, 2025
by
Administrator
Browse files
added basic function for markdown previewer
parent
95199a50
Changes
3
Show whitespace changes
Inline
Side-by-side
package-lock.json
View file @
af99f91a
...
...
@@ -10,6 +10,7 @@
"dependencies"
:
{
"@mdi/font"
:
"^7.4.47"
,
"jspdf"
:
"^3.0.1"
,
"marked"
:
"^16.1.1"
,
"mermaid"
:
"^11.9.0"
,
"svg2pdf.js"
:
"^2.5.0"
,
"vite-plugin-vuetify"
:
"^2.1.1"
,
...
...
package.json
View file @
af99f91a
...
...
@@ -11,6 +11,7 @@
"dependencies"
:
{
"
@mdi/font
"
:
"
^7.4.47
"
,
"
jspdf
"
:
"
^3.0.1
"
,
"
marked
"
:
"
^16.1.1
"
,
"
mermaid
"
:
"
^11.9.0
"
,
"
svg2pdf.js
"
:
"
^2.5.0
"
,
"
vite-plugin-vuetify
"
:
"
^2.1.1
"
,
...
...
src/components/TextToMarkdown.vue
View file @
af99f91a
<
template
>
<div>
<h2>
Text to Markdown Converter
</h2>
<!-- Add your converter UI here -->
<textarea
v-model=
"input"
placeholder=
"Enter text here"
></textarea>
<div>
<h3>
Markdown Output:
</h3>
<pre>
{{
output
}}
</pre>
<div
class=
"text-to-markdown"
>
<h1
class=
"page-title"
>
Text to Markdown Converter
</h1>
<div
class=
"converter-container"
>
<!-- Input Section -->
<div
class=
"input-section"
>
<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"
>
×
</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>
</
template
>
<
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
(
''
)
const
output
=
computed
(()
=>
{
// For now, just echo input. Replace with real conversion logic later.
return
input
.
value
// Fullscreen functions
const
toggleFullscreen
=
()
=>
{
if
(
isFullscreen
.
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
>
<
style
scoped
>
textarea
{
.text-to-markdown
{
/* max-width: 2400px; */
display
:
grid
;
grid-template-rows
:
1
fr
;
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
:
1
fr
1.5
fr
;
grid-template-rows
:
1
fr
;
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%
;
min-height
:
100px
;
margin-bottom
:
1em
;
max-width
:
100%
;
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
;
padding
:
1em
;
color
:
#c7254e
;
padding
:
2px
4px
;
border-radius
:
4px
;
font-family
:
'Courier New'
,
monospace
;
font-size
:
95%
;
}
</
style
>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment