implemented SQL console as a type of code note

This commit is contained in:
zadam
2020-05-08 22:22:43 +02:00
parent 1bb895bb83
commit c3438e0f3f
9 changed files with 153 additions and 868 deletions

View File

@@ -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);

View File

@@ -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" },

View File

@@ -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();

View File

@@ -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;

View File

@@ -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 },

View File

@@ -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') %>

View File

@@ -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">&times;</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>