Add markdown rendering support with KaTeX and highlight.js

- Updated package.json to include dependencies for rehype and remark plugins for markdown processing.
- Added default highlight.js CSS for code highlighting.
- Included KaTeX CSS for rendering mathematical expressions.
- Created markdown.css for styling markdown content in the Obsidian plugin, incorporating styles for headings, paragraphs, lists, tables, and code blocks.
- Customized highlight.js colors to match Obsidian theme.
This commit is contained in:
Andrew Beal 2025-09-16 17:14:37 +01:00
parent 0fb3e4632f
commit 3f37870aaa
28 changed files with 4628 additions and 201 deletions

View file

@ -1,5 +1,4 @@
import { AIProviderURL } from "Enums/ApiProvider";
import { isValidJson } from "Helpers";
import { request, type RequestUrlParam } from "obsidian";
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
@ -8,6 +7,7 @@ import type { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefini
import { create_file } from "Actioner/Actions";
import type { IAIClass } from "AIClasses/IAIClass";
import type { IPrompt } from "AIClasses/IPrompt";
import type { GenerateContentResponse, Part } from "@google/genai";
export class Gemini implements IAIClass {
private readonly apiKey: string;
@ -22,7 +22,7 @@ export class Gemini implements IAIClass {
this.actionDefinitions = Resolve(Services.IActionDefinitions);
}
public async apiRequest(userInput: string, actioner: IActioner): Promise<Part[] | null> { //AIResponse
public async apiRequest(userInput: string, actioner: IActioner): Promise<Part[] | null> {
let prompt: string = "The users prompt is: " + userInput;
let requestBody = JSON.stringify({
@ -55,22 +55,14 @@ export class Gemini implements IAIClass {
body: requestBody
};
let response: GeminiApiResponse = JSON.parse(await request(reqParam));
let response: GenerateContentResponse = JSON.parse(await request(reqParam));
console.log(response);
//TODO: tidy up this
let ai_response: Part[] = response.candidates[0]?.content.parts ?? "{}";
// if (isValidJson(ai_response)) {
// return JSON.parse(ai_response);
// }
let ai_response: Part[] = response.candidates?.first()?.content?.parts ?? [];
console.log(ai_response);
return ai_response;
return null;
}
}

View file

@ -1,3 +1,4 @@
import type { Part } from "@google/genai";
import type { IActioner } from "Actioner/IActioner";
export interface IAIClass {

View file

@ -25,7 +25,6 @@ export class AIPrompt implements IPrompt {
public readonly instructionsArr: string[] = [
"You are an AI assistant for the Obsidian note taking app.",
//"In addition to answering questions, you can execute helpful functions which are defined below.",
"The user has provided extra context to your responsibilities:",
"You are a DND expert and can provide detailed information about DND rules, character creation, and gameplay mechanics. Please give concise responses."
];
@ -34,17 +33,14 @@ export class AIPrompt implements IPrompt {
}
public readonly instructionsReminderArr: string[] = [
"Ensure your response is valid JSON following the format defined above."
""
]
public instructionsReminder(): string {
return this.instructionsReminderArr.join("\n");
}
public readonly responseFormatArr: string[] = [
"All responses that are not function calls should be in JSON parsable format. The response should not be wrapped in ```json```. The following fields should be used:",
"user_response - the response to be delivered to the user.",
//"function_name - a string name for the desired function or null.",
//"function_object - an object containing the function arguments or null."
"",
]
public responseFormat(): string {
return this.responseFormatArr.join("\n");

View file

@ -1,14 +1,4 @@
/**
* Interfaces for requested custom API responses.
*/
interface AIResponse {
function_calls: FunctionCall[];
// function_name: string | null;
// function_object: object | null;
// user_response: string;
}
interface CreateFileRequest {
file_path: string;

View file

@ -1,80 +0,0 @@
/**
* Interface for the top-level Gemini API response.
*/
interface GeminiApiResponse {
candidates: Candidate[];
usageMetadata: UsageMetadata;
modelVersion: string;
responseId: string;
}
/**
* Interface for a single candidate in the response.
*/
interface Candidate {
content: Content;
finishReason: string;
index: number;
citationMetadata: CitationMetadata;
}
/**
* Interface for the content of a candidate.
*/
interface Content {
parts: Part[];
role: string;
}
/**
* Interface for a single part of the content.
* The `text` property contains a JSON string, which needs to be parsed.
*/
interface Part {
text: string;
functionCall: FunctionCall;
}
/**
* Interface for a single function call.
*/
interface FunctionCall {
name: string;
args: object;
}
/**
* Interface for the metadata about citations.
*/
interface CitationMetadata {
citationSources: CitationSource[];
}
/**
* Interface for a single citation source.
*/
interface CitationSource {
startIndex: number;
endIndex: number;
uri: string;
license: string;
}
/**
* Interface for the usage metadata of the API call.
*/
interface UsageMetadata {
promptTokenCount: number;
candidatesTokenCount: number;
totalTokenCount: number;
promptTokensDetails: PromptTokensDetails[];
thoughtsTokenCount: number;
}
/**
* Interface for the details of prompt tokens.
*/
interface PromptTokensDetails {
modality: string;
tokenCount: number;
}

199
Components/ChatArea.svelte Normal file
View file

@ -0,0 +1,199 @@
<script lang="ts">
import { Resolve } from "Services/DependencyService";
import { MarkdownService } from "Services/MarkdownService";
import { Services } from "Services/Services";
export let messages: Array<{id: string, content: string, isUser: boolean}> = [];
let chatContainer: HTMLDivElement;
let markdownService: MarkdownService;
// Initialize service once
try {
markdownService = Resolve(Services.MarkdownService);
} catch (error) {
console.error('Failed to initialize MarkdownService:', error);
}
// Process each message content with markdown
$: processedMessages = messages.map((message) => {
if (message.isUser) {
return {
...message,
htmlContent: `<p>${message.content}</p>`
};
} else {
let htmlContent;
try {
htmlContent = markdownService?.formatToHTML(message.content) || `<p>${message.content}</p>`;
} catch (err) {
console.error('HTML processing failed:', err);
htmlContent = `<p>${message.content}</p>`;
}
return {
...message,
htmlContent
};
}
});
</script>
<div class="chat-area" bind:this={chatContainer}>
{#each processedMessages as message (message.id)}
<div class="message-container" class:user={message.isUser} class:assistant={!message.isUser}>
<div class="message-bubble" class:user={message.isUser} class:assistant={!message.isUser}>
{#if message.isUser}
<p>{message.content}</p>
{:else}
<div class="markdown-content">
{@html message.htmlContent}
</div>
{/if}
</div>
</div>
{/each}
{#if messages.length === 0}
<div class="empty-state">
<p>Start a conversation by typing a message below.</p>
</div>
{/if}
</div>
<!-- Keep your existing styles -->
<style>
.chat-area {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: var(--size-4-3);
gap: var(--p-spacing);
}
.message-container {
display: flex;
}
.message-container.user {
justify-content: flex-end;
}
.message-container.assistant {
justify-content: flex-start;
}
.message-bubble {
word-wrap: break-word;
}
.message-bubble.user {
word-wrap: break-word;
max-width: 70%;
border: var(--border-width) solid var(--background-modifier-border);
border-radius: var(--radius-m);
padding: 0px var(--size-4-2);
}
.message-bubble.assistant {
word-wrap: break-word;
max-width: 100%;
}
.empty-state {
justify-content: center;
align-items: center;
font-style: italic;
color: var(--text-muted);
pointer-events: none;
}
.markdown-content {
line-height: 1.6;
}
/* Keep all your existing markdown styles */
.markdown-content :global(h1),
.markdown-content :global(h2),
.markdown-content :global(h3),
.markdown-content :global(h4),
.markdown-content :global(h5),
.markdown-content :global(h6) {
margin: 1rem 0 0.5rem 0;
color: var(--text-normal);
}
.markdown-content :global(h1) { font-size: 1.5em; }
.markdown-content :global(h2) { font-size: 1.3em; }
.markdown-content :global(h3) { font-size: 1.1em; }
.markdown-content :global(p) {
margin: 0.5rem 0;
}
.markdown-content :global(pre) {
background-color: var(--background-modifier-border);
padding: 1rem;
border-radius: var(--radius-s);
overflow-x: auto;
margin: 0.5rem 0;
}
.markdown-content :global(code) {
background-color: var(--background-modifier-border);
padding: 2px 4px;
border-radius: var(--radius-s);
font-family: var(--font-monospace, 'Courier New', monospace);
font-size: 0.9em;
}
.markdown-content :global(pre code) {
background-color: transparent;
padding: 0;
}
.markdown-content :global(blockquote) {
border-left: 4px solid var(--background-modifier-border);
margin: 1rem 0;
padding-left: 1rem;
color: var(--text-muted);
}
.markdown-content :global(ul),
.markdown-content :global(ol) {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.markdown-content :global(li) {
margin: 0.25rem 0;
}
.markdown-content :global(table) {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
border: 1px solid var(--background-modifier-border);
}
.markdown-content :global(th),
.markdown-content :global(td) {
border: 1px solid var(--background-modifier-border);
padding: 8px 12px;
text-align: left;
}
.markdown-content :global(th) {
background-color: var(--background-modifier-border);
font-weight: bold;
}
.markdown-content :global(a) {
color: var(--text-accent);
text-decoration: none;
}
.markdown-content :global(a:hover) {
text-decoration: underline;
}
</style>

View file

@ -0,0 +1,160 @@
<script lang="ts">
import type { Part } from "@google/genai";
import type { IActioner } from "Actioner/IActioner";
import type { IAIClass } from "AIClasses/IAIClass";
import { Semaphore } from "Helpers";
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import ChatArea from "./ChatArea.svelte";
let ai: IAIClass = Resolve(Services.IAIClass);
let actioner: IActioner = Resolve(Services.IActioner);
let semaphore: Semaphore = new Semaphore(1, false);
let textareaElement: HTMLTextAreaElement;
let userRequest = "";
let isSubmitting = false;
let messages: Array<{id: string, content: string, isUser: boolean}> = [];
async function handleSubmit() {
if (userRequest.trim() === "" || isSubmitting) {
return;
}
isSubmitting = true;
const requestToSend = userRequest;
userRequest = "";
textareaElement.value = "";
autoResize();
// Add user message to chat
const userMessageId = `user-${Date.now()}`;
messages = [...messages, {
id: userMessageId,
content: requestToSend,
isUser: true
}];
if (!await semaphore.wait()) {
return;
}
try {
let response: Part[] | null = await ai.apiRequest(requestToSend, actioner);
console.log(response);
// Add AI response to chat
if (response && response.length > 0) {
const aiMessageId = `ai-${Date.now()}`;
const responseText = response.map(part => part.text || '').join(' ');
messages = [...messages, {
id: aiMessageId,
content: responseText,
isUser: false
}];
}
} finally {
semaphore.release();
isSubmitting = false;
}
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') {
if (e.shiftKey) {
return;
} else {
e.preventDefault();
handleSubmit();
}
}
}
function autoResize() {
if (textareaElement) {
textareaElement.style.height = 'auto';
textareaElement.style.height = textareaElement.scrollHeight + 'px';
}
}
</script>
<main class="container">
<div id="chat-container">
<ChatArea bind:messages />
</div>
<div id="input-container">
<textarea
id="input"
bind:this={textareaElement}
bind:value={userRequest}
on:keydown={handleKeydown}
on:input={autoResize}
placeholder="Type a message..."
disabled={isSubmitting}
rows="1">
</textarea>
<button id="submit" on:click={handleSubmit} disabled={isSubmitting || userRequest.trim() === ""}>
{isSubmitting ? 'Sending...' : 'Submit'}
</button>
</div>
</main>
<style>
.container {
display: grid;
grid-template-rows: 1fr auto;
grid-template-columns: 1fr;
height: 100%;
border-radius: var(--radius-m);
color: var(--font-interface-theme);
}
#chat-container {
height: 100%;
width: 100%;
grid-row: 1;
grid-column: 1;
overflow: hidden;
}
#input-container {
grid-row: 2;
grid-column: 1;
display: grid;
grid-template-rows: var(--size-4-3) 1fr var(--size-4-3);
grid-template-columns: var(--size-4-3) 1fr var(--size-4-3) auto var(--size-4-3);
border-radius: var(--modal-radius);
background-color: var(--modal-background);
}
#input {
grid-row: 2;
grid-column: 2;
min-height: var(--input-height);
max-height: var(--dialog-max-height);
border-radius: var(--input-radius);
font-weight: var(--input-font-weight);
border-width: var(--input-border-width);
resize: none;
overflow-y: auto;
color: var(--font-interface-theme);
}
#submit {
grid-row: 2;
grid-column: 4;
border-radius: var(--button-radius);
align-self: end;
background-color: var(--interactive-accent);
transition-duration: 0.25s;
}
#submit:hover {
background-color: var(--interactive-accent-hover);
}
</style>

View file

@ -1,77 +0,0 @@
<script lang="ts">
import { Resolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import type { IActioner } from "Actioner/IActioner";
import type { IAIClass } from "AIClasses/IAIClass";
interface Props {
input: string;
}
let {
input
}: Props = $props();
let output: string = $state("");
async function submit() {
let aiClass: IAIClass = Resolve(Services.IAIClass)
let actioner: IActioner = Resolve(Services.IActioner);
let aiResponse: Part[] | null = await aiClass.apiRequest(input, actioner);
if (aiResponse == null) {
throw "Response was invalid JSON";
}
for (let part of aiResponse) {
if (part.functionCall) {
let functionName: string = part.functionCall.name;
let functionObject: object = part.functionCall.args;
await actioner[Symbol.for(functionName)](functionObject);
};
}
output = `Done: ${aiResponse}`
}
</script>
<div class="container">
<input
type="string"
bind:value={input}
placeholder="Enter a prompt"
aria-label="Enter a prompt"
/>
<button onclick={submit}>Submit</button>
<p>{output}</p>
</div>
<style>
.container {
display: flex;
background-color: hotpink;
flex-direction: column;
gap: 0.5rem;
max-width: 200px;
}
input {
padding: 0.4rem;
font-size: 1rem;
}
button {
background: #0066ff;
color: white;
border: none;
padding: 0.4rem;
cursor: pointer;
}
button:hover {
background: #0055dd;
}
</style>

View file

@ -1,4 +1,4 @@
import { TFile, TFolder, type Vault } from "obsidian";
import { type Vault } from "obsidian";
export function isValidJson(str: string): boolean {
try {
@ -11,7 +11,7 @@ export function isValidJson(str: string): boolean {
export async function createDirectories(vault: Vault, filePath: string) {
const dirPath: string = filePath.substring(0, filePath.lastIndexOf('/'));
const dirs: string[] = dirPath.split('/');
let currentPath = "";
@ -23,4 +23,46 @@ export async function createDirectories(vault: Vault, filePath: string) {
}
}
}
}
export class Semaphore {
private max: number;
private count: number;
private readonly waitAsync: boolean;
private readonly queue: ((value: boolean) => void)[];
constructor(max: number, waitAsync: boolean) {
this.max = max;
this.count = max;
this.waitAsync = waitAsync;
this.queue = [];
}
async wait(): Promise<boolean> {
if (this.count > 0) {
this.count--;
return true;
}
if (!this.waitAsync) {
return false;
}
return new Promise<boolean>((resolve) => {
this.queue.push(resolve);
});
}
release(): void {
if (this.queue.length > 0) {
const resolve = this.queue.shift();
if (resolve) {
resolve(true);
}
} else {
if (this.count < this.max) {
this.count++;
}
}
}
}

112
Services/MarkdownService.ts Normal file
View file

@ -0,0 +1,112 @@
import { unified, type Processor } from 'unified';
import remarkParse from 'remark-parse';
import remarkMath from 'remark-math';
import remarkGfm from 'remark-gfm';
import remarkToc from 'remark-toc';
import remarkEmoji from 'remark-emoji';
import remarkRehype from 'remark-rehype';
import rehypeKatex from 'rehype-katex';
import rehypeHighlight from 'rehype-highlight';
import rehypeStringify from 'rehype-stringify';
export class MarkdownService {
private static processor: Processor<any, any, any, any, any> | null = null;
constructor() {
if (!MarkdownService.processor) {
MarkdownService.processor = this.createProcessor();
}
}
private createProcessor() {
return unified()
.use(remarkParse)
.use(remarkMath)
.use(remarkGfm)
.use(remarkToc)
.use(remarkEmoji)
.use(remarkRehype, { allowDangerousHtml: false })
.use(rehypeKatex)
.use(rehypeHighlight)
.use(rehypeStringify);
}
/**
* Formats LLM response text into HTML-friendly format
* Handles output from Claude, ChatGPT, and Gemini
* @param content - The raw LLM response text
* @returns HTML string
*/
formatToHTML(content: string): string {
try {
const preprocessedContent = this.preprocessContent(content);
const result = MarkdownService.processor!.processSync(preprocessedContent);
return String(result);
} catch (error) {
console.error('Markdown processing failed:', error);
return `<p>${this.escapeHtml(content)}</p>`;
}
}
private preprocessContent(content: string): string {
return content
// 1. LaTeX Math Notation (normalize different formats)
.replace(/\\\[([\s\S]*?)\\\]/g, (_, equation) => `$$${equation}$$`) // block math
.replace(/\\\(([\s\S]*?)\\\)/g, (_, equation) => `$${equation}$`) // inline math
// 2. Handle Code Block Language Tags (normalize variations)
.replace(/```(\w+)\s*\n/g, '```$1\n')
// 3. Fix Common Escape Issues (AI models sometimes over-escape)
.replace(/\\`/g, '`')
.replace(/\\\*/g, '*')
.replace(/\\_/g, '_')
// 4. Normalize Line Endings
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
// 5. Handle Triple Quotes (some models use these for code)
.replace(/"""\s*(\w+)?\s*\n([\s\S]*?)\n"""/g, (match, lang, code) => {
return lang ? `\`\`\`${lang}\n${code}\n\`\`\`` : `\`\`\`\n${code}\n\`\`\``;
})
// 6. Clean up Multiple Consecutive Newlines
.replace(/\n{4,}/g, '\n\n\n')
// 7. Handle Gemini's math-in-code-blocks tendency
.replace(/```math\n([\s\S]*?)\n```/g, (_, equation) => {
if (equation.includes('\\') || equation.includes('{') || equation.includes('^') || equation.includes('_')) {
return `$$${equation}$$`;
}
return `\`\`\`\n${equation}\n\`\`\``;
})
// 8. Fix Bold/Italic Formatting Issues
.replace(/\*\*\s+([^*]+)\s+\*\*/g, '**$1**') // Remove extra spaces in bold
.replace(/\*\s+([^*]+)\s+\*/g, '*$1*') // Remove extra spaces in italic
// 9. Fix Table Formatting Issues
.replace(/^\|\s+/gm, '| ') // Clean up table cell spacing
.replace(/\s+\|$/gm, ' |') // Clean up table cell spacing
// 10. Handle Alternative List Markers
.replace(/^•\s/gm, '- ') // Convert bullet points to markdown
.replace(/^→\s/gm, '- ') // Convert arrows to markdown
// 11. Fix Link Formatting Issues
.replace(/\[\s+([^\]]+)\s+\]/g, '[$1]') // Remove spaces in link text
// 12. Handle Footnote Variations
.replace(/\[\^(\w+)\]:/g, '[^$1]:') // Normalize footnote definitions
// 13. Clean up Heading Formatting
.replace(/^#+\s+/gm, (match) => match.replace(/\s+/g, ' ')); // Single space after #
}
private escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}

View file

@ -11,6 +11,7 @@ import type { IAIClass } from "AIClasses/IAIClass";
import { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefinitions";
import type { IActionDefinitions } from "Actioner/IActionDefinitions";
import { Gemini } from "AIClasses/Gemini/Gemini";
import { MarkdownService } from "./MarkdownService";
export function RegisterDependencies(plugin: DmsAssistantPlugin) {
RegisterSingleton(Services.DmsAssistantPlugin, plugin);
@ -18,8 +19,9 @@ export function RegisterDependencies(plugin: DmsAssistantPlugin) {
RegisterSingleton(Services.ModalService, new ModalService())
RegisterSingleton<IPrompt>(Services.IPrompt, new AIPrompt());
RegisterSingleton<IActioner>(Services.IActioner, new Actioner())
RegisterSingleton<IActioner>(Services.IActioner, new Actioner());
RegisterTransient<MarkdownService>(Services.MarkdownService, () => new MarkdownService());
RegisterAiProvider(plugin);
}

View file

@ -2,6 +2,7 @@ export class Services {
static DmsAssistantPlugin = Symbol("DmsAssistantPlugin");
static OdbCache = Symbol("OdbCache");
static ModalService = Symbol("ModalService");
static MarkdownService = Symbol("MarkdownService");
// interfaces
static IAIClass = Symbol("IAIClass");

9
Styles/highlight-default.min.css vendored Normal file
View file

@ -0,0 +1,9 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

373
Styles/katex.min.css vendored Normal file
View file

@ -0,0 +1,373 @@
/* KaTeX CSS with CDN font URLs */
@font-face {
font-family: 'KaTeX_AMS';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_AMS-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_AMS-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Caligraphic';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Caligraphic';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Fraktur';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Fraktur';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Main';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Main';
font-style: italic;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Italic.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Italic.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Main';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Math';
font-style: italic;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Math-Italic.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Math-Italic.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_SansSerif';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_SansSerif';
font-style: italic;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Italic.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Italic.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_SansSerif';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Script';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Script-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Script-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size1';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size1-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size1-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size2';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size2-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size2-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size3';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size3-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size3-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size4';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size4-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size4-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Typewriter';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Typewriter-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Typewriter-Regular.woff') format('woff');
}
/* Rest of KaTeX CSS styles */
.katex {
font: normal 1.21em KaTeX_Main, Times, serif;
line-height: 1.2;
white-space: nowrap;
text-indent: 0;
text-rendering: auto;
}
.katex * {
border-color: currentColor;
}
.katex .katex-version::after {
content: "0.16.0";
}
.katex .katex-mathml {
position: absolute;
visibility: hidden;
height: 1px;
overflow: hidden;
}
.katex .katex-html > .newline {
display: block;
}
.katex .base {
position: relative;
white-space: nowrap;
width: min-content;
}
.katex .strut {
display: inline-block;
}
.katex .textbf {
font-weight: bold;
}
.katex .textit {
font-style: italic;
}
.katex .textrm {
font-family: KaTeX_Main;
}
.katex .textsf {
font-family: KaTeX_SansSerif;
}
.katex .texttt {
font-family: KaTeX_Typewriter;
}
.katex .mathnormal {
font-family: KaTeX_Math;
font-style: italic;
}
.katex .mathit {
font-family: KaTeX_Main;
font-style: italic;
}
.katex .mathrm {
font-style: normal;
}
.katex .mathbf {
font-family: KaTeX_Main;
font-weight: bold;
}
.katex .boldsymbol {
font-family: KaTeX_Math;
font-weight: bold;
font-style: italic;
}
.katex .amsrm {
font-family: KaTeX_AMS;
}
.katex .mathbb,
.katex .textbb {
font-family: KaTeX_AMS;
}
.katex .mathcal {
font-family: KaTeX_Caligraphic;
}
.katex .mathfrak,
.katex .textfrak {
font-family: KaTeX_Fraktur;
}
.katex .mathtt {
font-family: KaTeX_Typewriter;
}
.katex .mathscr,
.katex .textscr {
font-family: KaTeX_Script;
}
.katex .mathsf,
.katex .textsf {
font-family: KaTeX_SansSerif;
}
.katex .mathboldsf,
.katex .textboldsf {
font-family: KaTeX_SansSerif;
font-weight: bold;
}
.katex .mathitsf,
.katex .textitsf {
font-family: KaTeX_SansSerif;
font-style: italic;
}
.katex .mainrm {
font-family: KaTeX_Main;
font-style: normal;
}
.katex .vlist-t {
display: inline-table;
table-layout: fixed;
border-collapse: collapse;
}
.katex .vlist-r {
display: table-row;
}
.katex .vlist {
display: table-cell;
vertical-align: bottom;
position: relative;
}
.katex .vlist > span {
display: block;
height: 0;
position: relative;
}
.katex .vlist > span > span {
display: inline-block;
}
.katex .vlist > span > .pstrut {
overflow: hidden;
width: 0;
}
.katex .hlist {
display: inline-flex;
flex-direction: row;
}
.katex .hlist > span {
flex: 0 0 auto;
}
.katex .vcenter {
display: inline-block;
vertical-align: middle;
}
.katex .alignleft {
text-align: left;
}
.katex .aligncenter {
text-align: center;
}
.katex .alignright {
text-align: right;
}
.katex .gothfrak,
.katex .textgothfrak {
font-family: KaTeX_Fraktur;
}
.katex .cmr {
font-family: KaTeX_Main;
}
.katex .cmss {
font-family: KaTeX_SansSerif;
}
.katex .cmtt {
font-family: KaTeX_Typewriter;
}
.katex .katex-display {
display: block;
margin: 1em 0;
text-align: center;
}
.katex .katex-display > .katex {
display: block;
white-space: nowrap;
max-width: 100%;
overflow-x: auto;
text-align: initial;
}
.katex .katex-display.leqno > .katex {
text-align: left;
}
.katex .katex-display.fleqn > .katex {
text-align: left;
padding-left: 2em;
}

153
Styles/markdown.css Normal file
View file

@ -0,0 +1,153 @@
/* Import the downloaded CSS files */
@import './katex.min.css';
@import './highlight-default.min.css';
/* Additional markdown styling for Obsidian plugin */
.markdown-processor-content {
line-height: 1.6;
color: var(--text-normal);
}
.markdown-processor-content h1,
.markdown-processor-content h2,
.markdown-processor-content h3,
.markdown-processor-content h4,
.markdown-processor-content h5,
.markdown-processor-content h6 {
margin: 1rem 0 0.5rem 0;
color: var(--text-normal);
font-weight: var(--font-weight-bold);
}
.markdown-processor-content h1 { font-size: 1.5em; }
.markdown-processor-content h2 { font-size: 1.3em; }
.markdown-processor-content h3 { font-size: 1.1em; }
.markdown-processor-content p {
margin: 0.5rem 0;
}
.markdown-processor-content pre {
background-color: var(--background-primary-alt);
padding: 1rem;
border-radius: var(--radius-s);
overflow-x: auto;
margin: 0.5rem 0;
border: 1px solid var(--background-modifier-border);
}
.markdown-processor-content code {
background-color: var(--background-primary-alt);
padding: 2px 4px;
border-radius: var(--radius-s);
font-family: var(--font-monospace);
font-size: 0.9em;
}
.markdown-processor-content pre code {
background-color: transparent;
padding: 0;
}
.markdown-processor-content blockquote {
border-left: 4px solid var(--background-modifier-border);
margin: 1rem 0;
padding-left: 1rem;
color: var(--text-muted);
font-style: italic;
}
.markdown-processor-content ul,
.markdown-processor-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.markdown-processor-content li {
margin: 0.25rem 0;
}
.markdown-processor-content table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
border: 1px solid var(--background-modifier-border);
}
.markdown-processor-content th,
.markdown-processor-content td {
border: 1px solid var(--background-modifier-border);
padding: 8px 12px;
text-align: left;
}
.markdown-processor-content th {
background-color: var(--background-primary-alt);
font-weight: var(--font-weight-bold);
}
.markdown-processor-content a {
color: var(--text-accent);
text-decoration: none;
}
.markdown-processor-content a:hover {
text-decoration: underline;
}
/* Math content styling adjustments for Obsidian */
.markdown-processor-content .katex {
font-size: 1.1em;
}
.markdown-processor-content .katex-display {
margin: 1rem 0;
text-align: center;
}
/* Task list styling */
.markdown-processor-content input[type="checkbox"] {
margin-right: 0.5rem;
}
/* Footnotes styling */
.markdown-processor-content .footnotes {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid var(--background-modifier-border);
font-size: 0.9em;
}
/* Override highlight.js colors to use Obsidian theme colors */
.markdown-processor-content .hljs {
background: var(--background-primary-alt) !important;
color: var(--text-normal) !important;
}
.markdown-processor-content .hljs-comment,
.markdown-processor-content .hljs-quote {
color: var(--text-muted) !important;
}
.markdown-processor-content .hljs-keyword,
.markdown-processor-content .hljs-selector-tag,
.markdown-processor-content .hljs-literal {
color: var(--text-accent) !important;
}
.markdown-processor-content .hljs-string,
.markdown-processor-content .hljs-doctag {
color: var(--color-green) !important;
}
.markdown-processor-content .hljs-title,
.markdown-processor-content .hljs-section,
.markdown-processor-content .hljs-type {
color: var(--color-blue) !important;
}
.markdown-processor-content .hljs-number,
.markdown-processor-content .hljs-variable,
.markdown-processor-content .hljs-template-variable {
color: var(--color-purple) !important;
}

View file

View file

View file

@ -1,7 +1,6 @@
import { ItemView, WorkspaceLeaf } from 'obsidian';
import Input from '../Components/Input.svelte';
import { mount, unmount } from 'svelte';
import ChatWindow from 'Components/ChatWindow.svelte';
export const VIEW_TYPE_MAIN = 'main-view';
@ -10,7 +9,7 @@ export class MainView extends ItemView {
super(leaf);
}
input: ReturnType<typeof Input> | undefined;
input: ReturnType<typeof ChatWindow> | undefined;
getViewType() {
return VIEW_TYPE_MAIN;
@ -24,11 +23,9 @@ export class MainView extends ItemView {
const container = this.contentEl;
container.empty();
this.input = mount(Input, {
this.input = mount(ChatWindow, {
target: container,
props: {
input: "",
}
props: {}
});
}

View file

View file

@ -1,6 +1,8 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import { copyFileSync, mkdirSync, existsSync, readdirSync, statSync } from "fs";
import { join } from "path";
import esbuildSvelte from 'esbuild-svelte';
import { sveltePreprocess } from 'svelte-preprocess';
@ -14,6 +16,27 @@ if you want to view the source, please visit the github repository of this plugi
const prod = (process.argv[2] === "production");
// Function to copy directory recursively
function copyDir(src, dest) {
if (!existsSync(dest)) {
mkdirSync(dest, { recursive: true });
}
const files = readdirSync(src);
for (const file of files) {
const srcPath = join(src, file);
const destPath = join(dest, file);
if (statSync(srcPath).isDirectory()) {
copyDir(srcPath, destPath);
} else {
copyFileSync(srcPath, destPath);
console.log(`📁 Copied: ${srcPath}${destPath}`);
}
}
}
const context = await esbuild.context({
plugins: [
esbuildSvelte({
@ -52,7 +75,24 @@ const context = await esbuild.context({
if (prod) {
await context.rebuild();
// Copy styles after production build
if (existsSync("Styles")) {
copyDir("Styles", "styles");
console.log("✅ Copied Styles directory to styles/");
} else {
console.log("⚠️ Styles directory not found, skipping copy");
}
process.exit(0);
} else {
// Copy styles for development
if (existsSync("Styles")) {
copyDir("Styles", "styles");
console.log("✅ Copied Styles directory to styles/");
} else {
console.log("⚠️ Styles directory not found, skipping copy");
}
await context.watch();
}
}

510
main.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -9,6 +9,8 @@ import { FileAction } from 'Enums/FileAction';
import { Path } from 'Enums/Path';
import { RegisterAiProvider, RegisterDependencies } from 'Services/ServiceRegistration';
import './styles/markdown.css';
interface DmsAssistantSettings {
apiProvider: string;
apiKey: string;

2469
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,17 @@
"dependencies": {
"@google/genai": "^1.17.0",
"express": "^5.1.0",
"rehype-highlight": "^7.0.2",
"rehype-katex": "^7.0.1",
"rehype-stringify": "^10.0.1",
"remark-emoji": "^5.0.2",
"remark-footnotes": "^4.0.1",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-toc": "^9.0.0",
"unified": "^11.0.5",
"uuid": "^11.1.0"
}
}

9
styles/highlight-default.min.css vendored Normal file
View file

@ -0,0 +1,9 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

373
styles/katex.min.css vendored Normal file
View file

@ -0,0 +1,373 @@
/* KaTeX CSS with CDN font URLs */
@font-face {
font-family: 'KaTeX_AMS';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_AMS-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_AMS-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Caligraphic';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Caligraphic';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Caligraphic-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Fraktur';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Fraktur';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Fraktur-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Main';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Main';
font-style: italic;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Italic.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Italic.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Main';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Math';
font-style: italic;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Math-Italic.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Math-Italic.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_SansSerif';
font-style: normal;
font-weight: 700;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Bold.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Bold.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_SansSerif';
font-style: italic;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Italic.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Italic.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_SansSerif';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_SansSerif-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Script';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Script-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Script-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size1';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size1-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size1-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size2';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size2-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size2-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size3';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size3-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size3-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Size4';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size4-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Size4-Regular.woff') format('woff');
}
@font-face {
font-family: 'KaTeX_Typewriter';
font-style: normal;
font-weight: 400;
src: url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Typewriter-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Typewriter-Regular.woff') format('woff');
}
/* Rest of KaTeX CSS styles */
.katex {
font: normal 1.21em KaTeX_Main, Times, serif;
line-height: 1.2;
white-space: nowrap;
text-indent: 0;
text-rendering: auto;
}
.katex * {
border-color: currentColor;
}
.katex .katex-version::after {
content: "0.16.0";
}
.katex .katex-mathml {
position: absolute;
visibility: hidden;
height: 1px;
overflow: hidden;
}
.katex .katex-html > .newline {
display: block;
}
.katex .base {
position: relative;
white-space: nowrap;
width: min-content;
}
.katex .strut {
display: inline-block;
}
.katex .textbf {
font-weight: bold;
}
.katex .textit {
font-style: italic;
}
.katex .textrm {
font-family: KaTeX_Main;
}
.katex .textsf {
font-family: KaTeX_SansSerif;
}
.katex .texttt {
font-family: KaTeX_Typewriter;
}
.katex .mathnormal {
font-family: KaTeX_Math;
font-style: italic;
}
.katex .mathit {
font-family: KaTeX_Main;
font-style: italic;
}
.katex .mathrm {
font-style: normal;
}
.katex .mathbf {
font-family: KaTeX_Main;
font-weight: bold;
}
.katex .boldsymbol {
font-family: KaTeX_Math;
font-weight: bold;
font-style: italic;
}
.katex .amsrm {
font-family: KaTeX_AMS;
}
.katex .mathbb,
.katex .textbb {
font-family: KaTeX_AMS;
}
.katex .mathcal {
font-family: KaTeX_Caligraphic;
}
.katex .mathfrak,
.katex .textfrak {
font-family: KaTeX_Fraktur;
}
.katex .mathtt {
font-family: KaTeX_Typewriter;
}
.katex .mathscr,
.katex .textscr {
font-family: KaTeX_Script;
}
.katex .mathsf,
.katex .textsf {
font-family: KaTeX_SansSerif;
}
.katex .mathboldsf,
.katex .textboldsf {
font-family: KaTeX_SansSerif;
font-weight: bold;
}
.katex .mathitsf,
.katex .textitsf {
font-family: KaTeX_SansSerif;
font-style: italic;
}
.katex .mainrm {
font-family: KaTeX_Main;
font-style: normal;
}
.katex .vlist-t {
display: inline-table;
table-layout: fixed;
border-collapse: collapse;
}
.katex .vlist-r {
display: table-row;
}
.katex .vlist {
display: table-cell;
vertical-align: bottom;
position: relative;
}
.katex .vlist > span {
display: block;
height: 0;
position: relative;
}
.katex .vlist > span > span {
display: inline-block;
}
.katex .vlist > span > .pstrut {
overflow: hidden;
width: 0;
}
.katex .hlist {
display: inline-flex;
flex-direction: row;
}
.katex .hlist > span {
flex: 0 0 auto;
}
.katex .vcenter {
display: inline-block;
vertical-align: middle;
}
.katex .alignleft {
text-align: left;
}
.katex .aligncenter {
text-align: center;
}
.katex .alignright {
text-align: right;
}
.katex .gothfrak,
.katex .textgothfrak {
font-family: KaTeX_Fraktur;
}
.katex .cmr {
font-family: KaTeX_Main;
}
.katex .cmss {
font-family: KaTeX_SansSerif;
}
.katex .cmtt {
font-family: KaTeX_Typewriter;
}
.katex .katex-display {
display: block;
margin: 1em 0;
text-align: center;
}
.katex .katex-display > .katex {
display: block;
white-space: nowrap;
max-width: 100%;
overflow-x: auto;
text-align: initial;
}
.katex .katex-display.leqno > .katex {
text-align: left;
}
.katex .katex-display.fleqn > .katex {
text-align: left;
padding-left: 2em;
}

153
styles/markdown.css Normal file
View file

@ -0,0 +1,153 @@
/* Import the downloaded CSS files */
@import './katex.min.css';
@import './highlight-default.min.css';
/* Additional markdown styling for Obsidian plugin */
.markdown-processor-content {
line-height: 1.6;
color: var(--text-normal);
}
.markdown-processor-content h1,
.markdown-processor-content h2,
.markdown-processor-content h3,
.markdown-processor-content h4,
.markdown-processor-content h5,
.markdown-processor-content h6 {
margin: 1rem 0 0.5rem 0;
color: var(--text-normal);
font-weight: var(--font-weight-bold);
}
.markdown-processor-content h1 { font-size: 1.5em; }
.markdown-processor-content h2 { font-size: 1.3em; }
.markdown-processor-content h3 { font-size: 1.1em; }
.markdown-processor-content p {
margin: 0.5rem 0;
}
.markdown-processor-content pre {
background-color: var(--background-primary-alt);
padding: 1rem;
border-radius: var(--radius-s);
overflow-x: auto;
margin: 0.5rem 0;
border: 1px solid var(--background-modifier-border);
}
.markdown-processor-content code {
background-color: var(--background-primary-alt);
padding: 2px 4px;
border-radius: var(--radius-s);
font-family: var(--font-monospace);
font-size: 0.9em;
}
.markdown-processor-content pre code {
background-color: transparent;
padding: 0;
}
.markdown-processor-content blockquote {
border-left: 4px solid var(--background-modifier-border);
margin: 1rem 0;
padding-left: 1rem;
color: var(--text-muted);
font-style: italic;
}
.markdown-processor-content ul,
.markdown-processor-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.markdown-processor-content li {
margin: 0.25rem 0;
}
.markdown-processor-content table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
border: 1px solid var(--background-modifier-border);
}
.markdown-processor-content th,
.markdown-processor-content td {
border: 1px solid var(--background-modifier-border);
padding: 8px 12px;
text-align: left;
}
.markdown-processor-content th {
background-color: var(--background-primary-alt);
font-weight: var(--font-weight-bold);
}
.markdown-processor-content a {
color: var(--text-accent);
text-decoration: none;
}
.markdown-processor-content a:hover {
text-decoration: underline;
}
/* Math content styling adjustments for Obsidian */
.markdown-processor-content .katex {
font-size: 1.1em;
}
.markdown-processor-content .katex-display {
margin: 1rem 0;
text-align: center;
}
/* Task list styling */
.markdown-processor-content input[type="checkbox"] {
margin-right: 0.5rem;
}
/* Footnotes styling */
.markdown-processor-content .footnotes {
margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid var(--background-modifier-border);
font-size: 0.9em;
}
/* Override highlight.js colors to use Obsidian theme colors */
.markdown-processor-content .hljs {
background: var(--background-primary-alt) !important;
color: var(--text-normal) !important;
}
.markdown-processor-content .hljs-comment,
.markdown-processor-content .hljs-quote {
color: var(--text-muted) !important;
}
.markdown-processor-content .hljs-keyword,
.markdown-processor-content .hljs-selector-tag,
.markdown-processor-content .hljs-literal {
color: var(--text-accent) !important;
}
.markdown-processor-content .hljs-string,
.markdown-processor-content .hljs-doctag {
color: var(--color-green) !important;
}
.markdown-processor-content .hljs-title,
.markdown-processor-content .hljs-section,
.markdown-processor-content .hljs-type {
color: var(--color-blue) !important;
}
.markdown-processor-content .hljs-number,
.markdown-processor-content .hljs-variable,
.markdown-processor-content .hljs-template-variable {
color: var(--color-purple) !important;
}