mirror of
https://github.com/zadam/trilium.git
synced 2025-11-07 05:46:10 +01:00
implemented SQL console as a type of code note
This commit is contained in:
@@ -1,138 +0,0 @@
|
||||
import libraryLoader from '../services/library_loader.js';
|
||||
import server from '../services/server.js';
|
||||
import toastService from "../services/toast.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
const $dialog = $("#sql-console-dialog");
|
||||
const $query = $('#sql-console-query');
|
||||
const $executeButton = $('#sql-console-execute');
|
||||
const $tableSchemas = $("#sql-console-table-schemas");
|
||||
const $resultContainer = $("#result-container");
|
||||
|
||||
let codeEditor;
|
||||
|
||||
$dialog.on("shown.bs.modal", e => initEditor());
|
||||
|
||||
export async function showDialog() {
|
||||
await showTableSchemas();
|
||||
|
||||
utils.openDialog($dialog);
|
||||
}
|
||||
|
||||
async function initEditor() {
|
||||
if (!codeEditor) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
||||
|
||||
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
||||
CodeMirror.keyMap.default["Tab"] = "indentMore";
|
||||
|
||||
// removing Escape binding so that Escape will propagate to the dialog (which will close on escape)
|
||||
delete CodeMirror.keyMap.basic["Esc"];
|
||||
|
||||
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
|
||||
|
||||
codeEditor = CodeMirror($query[0], {
|
||||
value: "",
|
||||
viewportMargin: Infinity,
|
||||
indentUnit: 4,
|
||||
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}
|
||||
});
|
||||
|
||||
codeEditor.setOption("mode", "text/x-sqlite");
|
||||
CodeMirror.autoLoadMode(codeEditor, "sql");
|
||||
|
||||
codeEditor.setValue(`SELECT title, isProtected, type, mime FROM notes WHERE noteId = 'root';
|
||||
---
|
||||
SELECT noteId, parentNoteId, notePosition, prefix FROM branches WHERE branchId = 'root';`);
|
||||
}
|
||||
|
||||
codeEditor.focus();
|
||||
}
|
||||
|
||||
async function execute() {
|
||||
// execute the selected text or the whole content if there's no selection
|
||||
let sqlQuery = codeEditor.getSelection();
|
||||
|
||||
if (!sqlQuery) {
|
||||
sqlQuery = codeEditor.getValue();
|
||||
}
|
||||
|
||||
const result = await server.post("sql/execute", {
|
||||
query: sqlQuery
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toastService.showError(result.error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
toastService.showMessage("Query was executed successfully.");
|
||||
}
|
||||
|
||||
const results = result.results;
|
||||
|
||||
$resultContainer.empty();
|
||||
|
||||
for (const rows of results) {
|
||||
if (rows.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $table = $('<table class="table table-striped">');
|
||||
$resultContainer.append($table);
|
||||
|
||||
const result = rows[0];
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
$row.append($("<th>").html(key));
|
||||
}
|
||||
|
||||
$table.append($row);
|
||||
|
||||
for (const result of rows) {
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
$row.append($("<td>").html(result[key]));
|
||||
}
|
||||
|
||||
$table.append($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function showTableSchemas() {
|
||||
const tables = await server.get('sql/schema');
|
||||
|
||||
$tableSchemas.empty();
|
||||
|
||||
for (const table of tables) {
|
||||
const $tableLink = $('<button class="btn">').text(table.name);
|
||||
|
||||
const $columns = $("<ul>");
|
||||
|
||||
for (const column of table.columns) {
|
||||
$columns.append(
|
||||
$("<li>")
|
||||
.append($("<span>").text(column.name))
|
||||
.append($("<span>").text(column.type))
|
||||
);
|
||||
}
|
||||
|
||||
$tableSchemas.append($tableLink).append(" ");
|
||||
|
||||
$tableLink
|
||||
.tooltip({
|
||||
html: true,
|
||||
placement: 'bottom',
|
||||
boundary: 'window',
|
||||
title: $columns[0].outerHTML
|
||||
})
|
||||
.on('click', () => codeEditor.setValue("SELECT * FROM " + table.name + " LIMIT 100"));
|
||||
}
|
||||
}
|
||||
|
||||
utils.bindElShortcut($query, 'ctrl+return', execute);
|
||||
|
||||
$executeButton.on('click', execute);
|
||||
@@ -123,7 +123,7 @@ const MIME_TYPES_DICT = [
|
||||
{ title: "Spreadsheet", mime: "text/x-spreadsheet" },
|
||||
{ default: true, title: "SQL", mime: "text/x-sql" },
|
||||
{ title: "SQLite", mime: "text/x-sqlite" },
|
||||
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite+trilium" },
|
||||
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium" },
|
||||
{ title: "Squirrel", mime: "text/x-squirrel" },
|
||||
{ title: "Stylus", mime: "text/x-styl" },
|
||||
{ default: true, title: "Swift", mime: "text/x-swift" },
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail-code note-detail-printable">
|
||||
@@ -8,25 +11,76 @@ const TPL = `
|
||||
.note-detail-code {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.note-detail-code-editor {
|
||||
min-height: 500px;
|
||||
flex-basis: 200px;
|
||||
min-height: 200px;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.sql-console-table-schemas button {
|
||||
padding: 0.25rem 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 0.5;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.sql-console-result-wrapper {
|
||||
flex-grow: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sql-console-result-container {
|
||||
width: 100%;
|
||||
font-size: smaller;
|
||||
margin-top: 10px;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sql-console-area">
|
||||
Tables:
|
||||
<span class="sql-console-table-schemas"></span>
|
||||
</div>
|
||||
|
||||
<div class="note-detail-code-editor"></div>
|
||||
|
||||
<div class="sql-console-area sql-console-result-wrapper">
|
||||
<div style="text-align: center">
|
||||
<button class="btn btn-danger sql-console-execute">Execute query <kbd>Ctrl+Enter</kbd></button>
|
||||
</div>
|
||||
|
||||
<div class="sql-console-result-container"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let TABLE_SCHEMA;
|
||||
|
||||
export default class EditableCodeTypeWidget extends TypeWidget {
|
||||
static getType() { return "editable-code"; }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$editor = this.$widget.find('.note-detail-code-editor');
|
||||
this.$sqlConsoleArea = this.$widget.find('.sql-console-area');
|
||||
this.$sqlConsoleTableSchemas = this.$widget.find('.sql-console-table-schemas');
|
||||
this.$sqlConsoleExecuteButton = this.$widget.find('.sql-console-execute');
|
||||
this.$sqlConsoleResultContainer = this.$widget.find('.sql-console-result-container');
|
||||
|
||||
keyboardActionService.setupActionsForElement('code-detail', this.$widget, this);
|
||||
|
||||
utils.bindElShortcut(this.$editor, 'ctrl+return', () => this.execute());
|
||||
|
||||
this.$sqlConsoleExecuteButton.on('click', () => this.execute());
|
||||
|
||||
this.initialized = this.initEditor();
|
||||
|
||||
return this.$widget;
|
||||
@@ -81,9 +135,103 @@ export default class EditableCodeTypeWidget extends TypeWidget {
|
||||
}
|
||||
});
|
||||
|
||||
const isSqlConsole = note.mime === 'text/x-sqlite;schema=trilium';
|
||||
|
||||
this.$sqlConsoleArea.toggle(isSqlConsole);
|
||||
|
||||
if (isSqlConsole) {
|
||||
await this.showTableSchemas();
|
||||
}
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
async showTableSchemas() {
|
||||
if (!TABLE_SCHEMA) {
|
||||
TABLE_SCHEMA = await server.get('sql/schema');
|
||||
}
|
||||
|
||||
this.$sqlConsoleTableSchemas.empty();
|
||||
|
||||
for (const table of TABLE_SCHEMA) {
|
||||
const $tableLink = $('<button class="btn">').text(table.name);
|
||||
|
||||
const $columns = $("<ul>");
|
||||
|
||||
for (const column of table.columns) {
|
||||
$columns.append(
|
||||
$("<li>")
|
||||
.append($("<span>").text(column.name))
|
||||
.append($("<span>").text(column.type))
|
||||
);
|
||||
}
|
||||
|
||||
this.$sqlConsoleTableSchemas.append($tableLink).append(" ");
|
||||
|
||||
$tableLink
|
||||
.tooltip({
|
||||
html: true,
|
||||
placement: 'bottom',
|
||||
boundary: 'window',
|
||||
title: $columns[0].outerHTML
|
||||
})
|
||||
.on('click', () => this.codeEditor.setValue("SELECT * FROM " + table.name + " LIMIT 100"));
|
||||
}
|
||||
}
|
||||
|
||||
async execute() {
|
||||
// execute the selected text or the whole content if there's no selection
|
||||
let sqlQuery = this.codeEditor.getSelection();
|
||||
|
||||
if (!sqlQuery) {
|
||||
sqlQuery = this.codeEditor.getValue();
|
||||
}
|
||||
|
||||
const result = await server.post("sql/execute", {
|
||||
query: sqlQuery
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
toastService.showError(result.error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
toastService.showMessage("Query was executed successfully.");
|
||||
}
|
||||
|
||||
const results = result.results;
|
||||
|
||||
this.$sqlConsoleResultContainer.empty();
|
||||
|
||||
for (const rows of results) {
|
||||
if (rows.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $table = $('<table class="table table-striped">');
|
||||
this.$sqlConsoleResultContainer.append($table);
|
||||
|
||||
const result = rows[0];
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
$row.append($("<th>").html(key));
|
||||
}
|
||||
|
||||
$table.append($row);
|
||||
|
||||
for (const result of rows) {
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
$row.append($("<td>").html(result[key]));
|
||||
}
|
||||
|
||||
$table.append($row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
this.$widget.show();
|
||||
|
||||
|
||||
@@ -661,12 +661,7 @@ div[data-notify="container"] {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#sql-console-table-schemas button {
|
||||
padding: 0.25rem 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 0.5;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
|
||||
a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href^="https://"]:not(.no-arrow):after {
|
||||
font-size: smaller;
|
||||
|
||||
@@ -76,7 +76,7 @@ const defaultOptions = [
|
||||
{ name: 'imageMaxWidthHeight', value: '1200', isSynced: true },
|
||||
{ name: 'imageJpegQuality', value: '75', isSynced: true },
|
||||
{ name: 'autoFixConsistencyIssues', value: 'true', isSynced: false },
|
||||
{ name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-swift","text/xml","text/x-yaml"]', isSynced: true },
|
||||
{ name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml"]', isSynced: true },
|
||||
{ name: 'leftPaneWidth', value: '25', isSynced: false },
|
||||
{ name: 'leftPaneVisible', value: 'true', isSynced: false },
|
||||
{ name: 'rightPaneWidth', value: '25', isSynced: false },
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<%- include('dialogs/options.ejs') %>
|
||||
<%- include('dialogs/protected_session_password.ejs') %>
|
||||
<%- include('dialogs/recent_changes.ejs') %>
|
||||
<%- include('dialogs/sql_console.ejs') %>
|
||||
<%- include('dialogs/info.ejs') %>
|
||||
<%- include('dialogs/prompt.ejs') %>
|
||||
<%- include('dialogs/confirm.ejs') %>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<div id="sql-console-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">SQL console</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
Tables:
|
||||
<span id="sql-console-table-schemas"></span>
|
||||
</div>
|
||||
|
||||
<div id="sql-console-query"></div>
|
||||
|
||||
<div style="text-align: center">
|
||||
<button class="btn btn-danger" id="sql-console-execute">Execute <kbd>CTRL+ENTER</kbd></button>
|
||||
</div>
|
||||
|
||||
<div id="result-container" style="width: 100%; font-size: smaller; margin-top: 10px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user