mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
feat: adding inline codeblocks support
This commit is contained in:
parent
d0a76c1daa
commit
3c833abbff
7 changed files with 289 additions and 6 deletions
2
docs/inline-codeblocks.md
Normal file
2
docs/inline-codeblocks.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Inline Codeblocks
|
||||
SQLSeal supports inline codeblocks. This allows you to put data inline in your note as if it's part of the sentence.
|
||||
32
src/codeblockHandler/inline/InlineCodeHandler.ts
Normal file
32
src/codeblockHandler/inline/InlineCodeHandler.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { App, MarkdownPostProcessorContext } from "obsidian";
|
||||
import { InlineProcessor } from "./InlineProcessor";
|
||||
import { SqlSealDatabase } from "src/database/database";
|
||||
import { Sync } from "src/datamodel/sync";
|
||||
|
||||
export class SqlSealInlineHandler {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly db: SqlSealDatabase,
|
||||
private sync: Sync
|
||||
) { }
|
||||
|
||||
getHandler() {
|
||||
return async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
ctx.addChild(this.instantiateProcessor(source, el, ctx.sourcePath));
|
||||
};
|
||||
}
|
||||
|
||||
instantiateProcessor(source: string, el: HTMLElement, sourcePath: string) {
|
||||
const query = source.replace(/^S>\s*/, "").trim();
|
||||
const processor = new InlineProcessor(
|
||||
el,
|
||||
query,
|
||||
sourcePath,
|
||||
this.db,
|
||||
this.app,
|
||||
this.sync
|
||||
);
|
||||
|
||||
return processor
|
||||
}
|
||||
}
|
||||
71
src/codeblockHandler/inline/InlineProcessor.ts
Normal file
71
src/codeblockHandler/inline/InlineProcessor.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { OmnibusRegistrator } from "@hypersphere/omnibus";
|
||||
import { App, MarkdownPostProcessorContext, MarkdownRenderChild } from "obsidian";
|
||||
import { SqlSealDatabase } from "src/database/database";
|
||||
import { Sync } from "src/datamodel/sync";
|
||||
import { RendererRegistry, RenderReturn } from "src/renderer/rendererRegistry";
|
||||
import { transformQuery } from "src/sql/transformer";
|
||||
import { displayError } from "src/utils/ui";
|
||||
|
||||
export class InlineProcessor extends MarkdownRenderChild {
|
||||
private registrator: OmnibusRegistrator;
|
||||
private renderer: RenderReturn;
|
||||
|
||||
constructor(
|
||||
private el: HTMLElement,
|
||||
private query: string,
|
||||
private sourcePath: string,
|
||||
private db: SqlSealDatabase,
|
||||
private app: App,
|
||||
private sync: Sync
|
||||
) {
|
||||
super(el);
|
||||
this.registrator = this.sync.getRegistrator();
|
||||
}
|
||||
|
||||
async onload() {
|
||||
try {
|
||||
await this.render();
|
||||
} catch (e) {
|
||||
displayError(this.el, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.registrator.offAll();
|
||||
}
|
||||
|
||||
async render() {
|
||||
try {
|
||||
const registeredTablesForContext = await this.sync.getTablesMappingForContext(this.sourcePath);
|
||||
const transformedQuery = transformQuery(this.query, registeredTablesForContext);
|
||||
|
||||
this.registrator.offAll();
|
||||
Object.values(registeredTablesForContext).forEach(v => {
|
||||
this.registrator.on(`change::${v}`, () => {
|
||||
this.render();
|
||||
});
|
||||
});
|
||||
|
||||
this.registrator.on('file::change::' + this.sourcePath, () => {
|
||||
setTimeout(() => this.render(), 250);
|
||||
});
|
||||
|
||||
const file = this.app.vault.getFileByPath(this.sourcePath);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileCache = this.app.metadataCache.getFileCache(file);
|
||||
const { data, columns } = await this.db.select(
|
||||
transformedQuery,
|
||||
fileCache?.frontmatter ?? {}
|
||||
);
|
||||
|
||||
this.el.empty()
|
||||
this.el.createSpan({ text: data[0][columns[0]] ?? '' })
|
||||
|
||||
} catch (e) {
|
||||
displayError(this.el, e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
124
src/editorExtension/inlineCodeBlock.ts
Normal file
124
src/editorExtension/inlineCodeBlock.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { EditorView, ViewPlugin, ViewUpdate, DecorationSet, Decoration, WidgetType } from "@codemirror/view";
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { App } from "obsidian";
|
||||
import { SqlSealDatabase } from "../database/database";
|
||||
import { RendererRegistry } from "../renderer/rendererRegistry";
|
||||
import { Sync } from "../datamodel/sync";
|
||||
import { SqlSealInlineHandler } from "src/codeblockHandler/inline/InlineCodeHandler";
|
||||
import { InlineProcessor } from "src/codeblockHandler/inline/InlineProcessor";
|
||||
|
||||
export function createSqlSealEditorExtension(
|
||||
app: App,
|
||||
db: SqlSealDatabase,
|
||||
sync: Sync,
|
||||
) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet;
|
||||
inlineHandler: SqlSealInlineHandler;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.inlineHandler = new SqlSealInlineHandler(app, db, sync);
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (update.docChanged || update.viewportChanged || update.selectionSet) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView) {
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
const tree = syntaxTree(view.state);
|
||||
|
||||
tree.iterate({
|
||||
enter: ({ type, from, to }) => {
|
||||
if (type.name.includes("inline-code")) {
|
||||
const text = view.state.doc.sliceString(from, to);
|
||||
if (text.startsWith('S>')) {
|
||||
// Check if we're currently editing this specific inline code
|
||||
const isEditing = view.hasFocus &&
|
||||
view.state.selection.ranges.some(range =>
|
||||
range.from >= from && range.to <= to);
|
||||
|
||||
if (!isEditing) {
|
||||
// Create a replacement decoration
|
||||
builder.add(from, to, Decoration.replace({
|
||||
widget: new SqlSealInlineWidget(
|
||||
text,
|
||||
this.inlineHandler,
|
||||
view.state.doc.lineAt(from).number,
|
||||
app
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class SqlSealInlineWidget extends WidgetType {
|
||||
constructor(
|
||||
private query: string,
|
||||
private handler: SqlSealInlineHandler,
|
||||
private line: number,
|
||||
private app: App
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: SqlSealInlineWidget): boolean {
|
||||
return (
|
||||
other.query === this.query &&
|
||||
other.line === this.line
|
||||
);
|
||||
}
|
||||
|
||||
processor: InlineProcessor
|
||||
|
||||
toDOM(): HTMLElement {
|
||||
const container = document.createElement('span');
|
||||
container.classList.add('sqlseal-inline-result');
|
||||
|
||||
// Create a new context for the inline query
|
||||
const ctx = {
|
||||
sourcePath: this.app.workspace.getActiveFile()?.path ?? '',
|
||||
addChild: () => {},
|
||||
getSectionInfo: () => ({
|
||||
lineStart: this.line,
|
||||
lineEnd: this.line
|
||||
})
|
||||
};
|
||||
|
||||
// Show the original query on hover
|
||||
container.setAttribute('aria-label', this.query);
|
||||
container.classList.add('has-tooltip');
|
||||
|
||||
this.processor = this.handler.instantiateProcessor(this.query, container, ctx.sourcePath)
|
||||
|
||||
this.processor.load()
|
||||
return container;
|
||||
}
|
||||
|
||||
destroy(dom: HTMLElement): void {
|
||||
if (this.processor) {
|
||||
this.processor.unload()
|
||||
}
|
||||
}
|
||||
|
||||
ignoreEvent(event: Event): boolean {
|
||||
// Return false to allow events to propagate (important for editing)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
33
src/main.ts
33
src/main.ts
|
|
@ -10,6 +10,7 @@ import { FilesFileSyncTable } from 'src/vaultSync/tables/filesTable';
|
|||
import { TagsFileSyncTable } from 'src/vaultSync/tables/tagsTable';
|
||||
import { TasksFileSyncTable } from 'src/vaultSync/tables/tasksTable';
|
||||
import { CSV_VIEW_TYPE, CSVView } from 'src/view/CSVView';
|
||||
import { createSqlSealEditorExtension } from './editorExtension/inlineCodeBlock';
|
||||
|
||||
const GLOBAL_KEY = 'sqlSealApi'
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ export default class SqlSealPlugin extends Plugin {
|
|||
|
||||
// start syncing when files are loaded
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
sqlSeal.db.connect().then(() => {
|
||||
sqlSeal.db.connect().then(async () => {
|
||||
|
||||
this.fileSync = new SealFileSync(this.app, this)
|
||||
|
||||
|
|
@ -51,8 +52,9 @@ export default class SqlSealPlugin extends Plugin {
|
|||
this.fileSync.addTablePlugin(new TagsFileSyncTable(sqlSeal.db, this.app))
|
||||
this.fileSync.addTablePlugin(new TasksFileSyncTable(sqlSeal.db, this.app))
|
||||
|
||||
this.fileSync.init()
|
||||
await this.fileSync.init()
|
||||
this.registerMarkdownCodeBlockProcessor("sqlseal", sqlSeal.getHandler())
|
||||
this.registerInlineCodeblocks()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -63,6 +65,33 @@ export default class SqlSealPlugin extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
private registerInlineCodeblocks() {
|
||||
|
||||
// Extension for Live Preview
|
||||
const editorExtension = createSqlSealEditorExtension(
|
||||
this.app,
|
||||
this.sqlSeal.db,
|
||||
this.sqlSeal.sync,
|
||||
);
|
||||
this.registerEditorExtension(editorExtension);
|
||||
|
||||
// Extension for Read mode
|
||||
this.registerMarkdownPostProcessor((el, ctx) => {
|
||||
const inlineCodeBlocks = el.querySelectorAll('code');
|
||||
inlineCodeBlocks.forEach((node: HTMLSpanElement) => {
|
||||
const text = node.innerText;
|
||||
if (text.startsWith('S>')) {
|
||||
const container = createEl('span', { cls: 'sqlseal-inline-result' });
|
||||
container.setAttribute('aria-label', text.slice(3));
|
||||
container.classList.add('has-tooltip');
|
||||
node.replaceWith(container);
|
||||
this.sqlSeal.getInlineHandler()(text, container, ctx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private addCSVCreatorMenuItem(menu: Menu, file: TAbstractFile) {
|
||||
menu.addItem((item) => {
|
||||
item
|
||||
|
|
|
|||
|
|
@ -4,21 +4,29 @@ import { SqlSealCodeblockHandler } from "./codeblockHandler/SqlSealCodeblockHand
|
|||
import { Logger } from "./utils/logger";
|
||||
import { RendererRegistry } from "./renderer/rendererRegistry";
|
||||
import { Sync } from "./datamodel/sync";
|
||||
import { SqlSealInlineHandler } from "./codeblockHandler/inline/InlineCodeHandler";
|
||||
|
||||
export class SqlSeal {
|
||||
public db: SqlSealDatabase
|
||||
public codeBlockHandler: SqlSealCodeblockHandler
|
||||
private codeBlockHandler: SqlSealCodeblockHandler
|
||||
private inlineCodeBlock: SqlSealInlineHandler
|
||||
public sync: Sync
|
||||
constructor(private readonly app: App, verbose = false, rendererRegistry: RendererRegistry) {
|
||||
this.db = new SqlSealDatabase(app, verbose)
|
||||
const logger = new Logger(verbose)
|
||||
|
||||
const sync = new Sync(this.db, this.app.vault)
|
||||
sync.init()
|
||||
this.sync = new Sync(this.db, this.app.vault)
|
||||
this.sync.init()
|
||||
|
||||
this.codeBlockHandler = new SqlSealCodeblockHandler(app, this.db, sync, rendererRegistry)
|
||||
this.codeBlockHandler = new SqlSealCodeblockHandler(app, this.db, this.sync, rendererRegistry)
|
||||
this.inlineCodeBlock = new SqlSealInlineHandler(app, this.db, this.sync)
|
||||
}
|
||||
|
||||
getHandler() {
|
||||
return this.codeBlockHandler.getHandler()
|
||||
}
|
||||
|
||||
getInlineHandler() {
|
||||
return this.inlineCodeBlock.getHandler()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
styles.css
17
styles.css
|
|
@ -104,4 +104,21 @@
|
|||
width: 100%;
|
||||
font-family: monospace;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
|
||||
/* INLINE QUERIES */
|
||||
.sqlseal-inline-result {
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
margin: 0 2px;
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sqlseal-inline-result .error {
|
||||
color: var(--text-error);
|
||||
font-style: italic;
|
||||
}
|
||||
Loading…
Reference in a new issue