mirror of
https://github.com/lmg-anon/mikupad.git
synced 2026-08-17 12:34:31 +00:00
Add basic markdown preview
This commit is contained in:
parent
bac457d7b6
commit
8fe8b9f2d4
1 changed files with 108 additions and 11 deletions
119
mikupad.html
119
mikupad.html
|
|
@ -17,7 +17,8 @@
|
|||
"react": "https://esm.sh/v135/react@18",
|
||||
"react-dom/client": "https://esm.sh/v135/react-dom@18/client?external=react",
|
||||
"htm/react": "https://esm.sh/v135/htm@3/react?external=react&react=18",
|
||||
"scrollview-resize": "https://esm.sh/v135/scrollview-resize@1.0.2"
|
||||
"scrollview-resize": "https://esm.sh/v135/scrollview-resize@1.0.2",
|
||||
"marked": "https://esm.sh/v135/marked@16.4.1"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1172,6 +1173,38 @@ button.completing {
|
|||
left: 210px;
|
||||
}
|
||||
|
||||
#markdown-preview {
|
||||
display: none;
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
background: var(--color-base-100);
|
||||
color: var(--color-base-10);
|
||||
padding: 2em 3em;
|
||||
margin: 0;
|
||||
scrollbar-gutter: stable;
|
||||
font: inherit;
|
||||
box-sizing: border-box;
|
||||
font-size: calc(1.125rem * var(--font-size-multiplier));
|
||||
text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
font-family: serif;
|
||||
word-wrap: break-word;
|
||||
overflow-y: scroll;
|
||||
height: calc(100vh);
|
||||
}
|
||||
|
||||
body.markdown-preview-on #prompt-container {
|
||||
max-width: none;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
body.markdown-preview-on #markdown-preview {
|
||||
display: block;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style id="dynamic-theme-style"></style>
|
||||
<script type="module">
|
||||
|
|
@ -1180,6 +1213,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|||
import { createRoot } from 'react-dom/client';
|
||||
import { html } from 'htm/react';
|
||||
import { SVResizeObserver } from 'scrollview-resize';
|
||||
import { marked } from 'marked';
|
||||
|
||||
const API_LLAMA_CPP = 0;
|
||||
const API_KOBOLD_CPP = 2;
|
||||
|
|
@ -2911,6 +2945,20 @@ const SVG_Stop = ({ ...props }) => {
|
|||
<path fill="var(--color-light)" fillRule="evenodd" d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4z"/>
|
||||
</${SVG}>
|
||||
`};
|
||||
const SVG_SplitView = ({ ...props }) => {
|
||||
return html`
|
||||
<${SVG}
|
||||
...${props}
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
style=${{height:"1.3em"}}>
|
||||
<path d="M8 3.5a.5.5 0 0 0-1 0V14a.5.5 0 0 0 1 0V3.5z"/>
|
||||
<path d="M1.5 3a.5.5 0 0 1 .5-.5h5.5a1.5 1.5 0 0 1 1.5 1.5v10a.5.5 0 0 1-.5.5H2a1.5 1.5 0 0 1-1.5-1.5V3zm1 .5v9a.5.5 0 0 0 .5.5h5V4a.5.5 0 0 0-.5-.5H2.5z"/>
|
||||
<path d="M14.5 3a.5.5 0 0 0-.5-.5H8.5A1.5 1.5 0 0 0 7 4v10a.5.5 0 0 0 .5.5H14a1.5 1.5 0 0 0 1.5-1.5V3zm-1 .5v9a.5.5 0 0 1-.5.5H8V4a.5.5 0 0 1 .5-.5h5z"/>
|
||||
<path d="M3 5h3v1H3V5zm0 2h3v1H3V7zm7-2h3v1h-3V5zm0 2h3v1h-3V7z" opacity="0.6"/>
|
||||
</${SVG}>
|
||||
`;
|
||||
};
|
||||
|
||||
function Modal({ isOpen, onClose, title, description, children, ...props }) {
|
||||
if (!isOpen) {
|
||||
|
|
@ -5938,6 +5986,10 @@ html.monospace-dark .MenuItem:not(.disabled):hover {
|
|||
html.monospace-dark .MenuItem.disabled {
|
||||
background: var(--color-base-10);
|
||||
color: var(--color-base-50);
|
||||
}
|
||||
html.monospace-dark #markdown-preview {
|
||||
background: var(--color-base-20);
|
||||
color: var(--color-base-0);
|
||||
}
|
||||
`},
|
||||
"NockoffAI": {
|
||||
|
|
@ -6113,6 +6165,10 @@ html.nockoffAI .MenuItem:not(.disabled):hover {
|
|||
html.nockoffAI .MenuItem.disabled {
|
||||
background: var(--color-base-10);
|
||||
color: var(--color-base-50);
|
||||
}
|
||||
html.nockoffAI #markdown-preview {
|
||||
background: var(--color-prompt-area);
|
||||
color: var(--color-text);
|
||||
}
|
||||
`},
|
||||
"E-Reader": {
|
||||
|
|
@ -6404,6 +6460,8 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
const useScrollSmoothing = useRef(true);
|
||||
const hordeTaskId = useRef();
|
||||
const promptPreviewElement = useRef();
|
||||
const markdownPreviewRef = useRef(null);
|
||||
const isSyncingScroll = useRef(false);
|
||||
const [templates, setTemplates] = useDBTemplates(defaultPresets.instructTemplates);
|
||||
const [templateReplacements, setTemplateReplacements] = useState(false);
|
||||
const [templatesImport, setTemplatesImport] = useState(false);
|
||||
|
|
@ -6512,6 +6570,7 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
}
|
||||
})());
|
||||
const [allThemes, setAllThemes] = useDBThemes(defaultThemes);
|
||||
const [showMarkdownPreview, setShowMarkdownPreview] = usePersistentState('showMarkdownPreview', false);
|
||||
// Text-To-Speech stuff
|
||||
const ttsNewText = useRef("");
|
||||
const ttsLastChunk = useRef("");
|
||||
|
|
@ -7191,14 +7250,10 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
case 'fill_suffix':
|
||||
if (fimPromptInfo !== undefined) {
|
||||
const { fimLeftChunks, fimRightChunks } = fimPromptInfo;
|
||||
const elem = promptArea.current;
|
||||
if (elem) {
|
||||
const suffix = fimRightChunks[0].content;
|
||||
if (suffix.length > 0) {
|
||||
stopParam = [suffix.trim().substring(0, 2)];
|
||||
}
|
||||
const suffix = fimRightChunks[0]?.content;
|
||||
if (suffix && suffix.length > 0) {
|
||||
stopParam = [suffix.trim().substring(0, 2)];
|
||||
}
|
||||
console.log(`Suffix: ${stopParam}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -7419,6 +7474,19 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
}
|
||||
}, [triggerPredict]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (showMarkdownPreview) {
|
||||
document.body.classList.add('markdown-preview-on');
|
||||
} else {
|
||||
document.body.classList.remove('markdown-preview-on');
|
||||
}
|
||||
}, [showMarkdownPreview]);
|
||||
|
||||
const markdownHtml = useMemo(() => {
|
||||
if (!showMarkdownPreview) return '';
|
||||
return marked.parse(promptText, { gfm: true, breaks: true });
|
||||
}, [promptText, showMarkdownPreview]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
document.body.style.setProperty('--font-size-multiplier', fontSizeMultiplier);
|
||||
}, [fontSizeMultiplier]);
|
||||
|
|
@ -7872,8 +7940,8 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
|
||||
let isDragging = false;
|
||||
let startX;
|
||||
let startMaxWidth;
|
||||
let startEdge;
|
||||
let startNumericWidth;
|
||||
let edgeDetectionZone = 5; // Pixels from edge to trigger resize
|
||||
|
||||
function getNearEdge(e) {
|
||||
|
|
@ -7897,7 +7965,7 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
|
||||
const invEdgePos = edge == 'right' ? container.getBoundingClientRect().left : container.getBoundingClientRect().right;
|
||||
startX = e.clientX - invEdgePos;
|
||||
startMaxWidth = getComputedStyle(container).getPropertyValue('max-width');
|
||||
startNumericWidth = container.getBoundingClientRect().width;
|
||||
startEdge = edge;
|
||||
}
|
||||
|
||||
|
|
@ -7926,9 +7994,15 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
// reset selection
|
||||
promptArea.current.selectionStart = promptArea.current.selectionEnd;
|
||||
|
||||
const minWidth = 200;
|
||||
const invEdgePos = startEdge == 'right' ? container.getBoundingClientRect().left : container.getBoundingClientRect().right;
|
||||
const currentX = e.clientX - invEdgePos;
|
||||
setPromptAreaWidth(`calc(${startMaxWidth} + ${(currentX - startX) * (startEdge == 'right' ? 1 : -1)}px)`);
|
||||
const delta = (currentX - startX) * (startEdge == 'right' ? 1 : -1);
|
||||
|
||||
// Calculate the new width and ensure it's not less than minWidth
|
||||
const newWidth = Math.max(minWidth, startNumericWidth + delta);
|
||||
|
||||
setPromptAreaWidth(`${newWidth}px`);
|
||||
}
|
||||
|
||||
function stopDragging() {
|
||||
|
|
@ -8046,6 +8120,19 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
currentPromptChunk.top += oldTop - newTop;
|
||||
}
|
||||
}
|
||||
|
||||
if (showMarkdownPreview && markdownPreviewRef.current && !isSyncingScroll.current) {
|
||||
isSyncingScroll.current = true;
|
||||
const editor = target;
|
||||
const preview = markdownPreviewRef.current;
|
||||
if (editor.scrollHeight > editor.clientHeight) {
|
||||
const scrollPercentage = editor.scrollTop / (editor.scrollHeight - editor.clientHeight);
|
||||
preview.scrollTop = scrollPercentage * (preview.scrollHeight - preview.clientHeight);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
isSyncingScroll.current = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onPromptMouseMove({ clientX, clientY }) {
|
||||
|
|
@ -8342,6 +8429,13 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
onClick=${() => toggleModal("searchAndReplace")}>
|
||||
<${SVG_SearchAndReplace} style=${{"height":"1.3em"}} />
|
||||
</button>
|
||||
<button
|
||||
title="Toggle Markdown Preview"
|
||||
style=${{"margin-top":"3em"}}
|
||||
className="textAreaSettings"
|
||||
onClick=${() => setShowMarkdownPreview(p => !p)}>
|
||||
<${SVG_SplitView}/>
|
||||
</button>
|
||||
<textarea
|
||||
ref=${promptArea}
|
||||
readOnly=${!!cancel}
|
||||
|
|
@ -8402,6 +8496,9 @@ export function App({ sessionStorage, templateStorage, themeStorage, useSessionS
|
|||
promptText=${promptText}
|
||||
cancel=${cancel}/>
|
||||
</div>
|
||||
<div id="markdown-preview" ref=${markdownPreviewRef}>
|
||||
<div dangerouslySetInnerHTML=${{__html: markdownHtml}} />
|
||||
</div>
|
||||
${probs ? html`
|
||||
<div
|
||||
id="probs"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue