refactoring of sql console into separate widgets

This commit is contained in:
zadam 2020-12-29 22:27:31 +01:00
parent 1d64129572
commit 02d9752abf
12 changed files with 201 additions and 218 deletions

32
package-lock.json generated
View File

@ -1792,9 +1792,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001168",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001168.tgz",
"integrity": "sha512-P2zmX7swIXKu+GMMR01TWa4csIKELTNnZKc+f1CjebmZJQtTAEXmpQSoKVJVVcvPGAA0TEYTOUp3VehavZSFPQ==",
"version": "1.0.30001170",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001170.tgz",
"integrity": "sha512-Dd4d/+0tsK0UNLrZs3CvNukqalnVTRrxb5mcQm8rHL49t7V5ZaTygwXkrq+FB+dVDf++4ri8eJnFEJAB8332PA==",
"dev": true
},
"caseless": {
@ -3209,9 +3209,9 @@
}
},
"electron-to-chromium": {
"version": "1.3.629",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.629.tgz",
"integrity": "sha512-iSPPJtPvHrMAvYOt+9cdbDmTasPqwnwz4lkP8Dn200gDNUBQOLQ96xUsWXBwXslAo5XxdoXAoQQ3RAy4uao9IQ==",
"version": "1.3.633",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.633.tgz",
"integrity": "sha512-bsVCsONiVX1abkWdH7KtpuDAhsQ3N3bjPYhROSAXE78roJKet0Y5wznA14JE9pzbwSZmSMAW6KiKYf1RvbTJkA==",
"dev": true
},
"electron-window-state": {
@ -3250,13 +3250,13 @@
}
},
"enhanced-resolve": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.4.0.tgz",
"integrity": "sha512-ZmqfWURB2lConOBM1JdCVfPyMRv5RdKWktLXO6123p97ovVm2CLBgw9t5MBj3jJWA6eHyOeIws9iJQoGFR4euQ==",
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.4.1.tgz",
"integrity": "sha512-4GbyIMzYktTFoRSmkbgZ1LU+RXwf4AQ8Z+rSuuh1dC8plp0PPeaWvx6+G4hh4KnUJ48VoxKbNyA1QQQIUpXjYA==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
"tapable": "^2.0.0"
"tapable": "^2.2.0"
},
"dependencies": {
"graceful-fs": {
@ -4142,9 +4142,9 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"helmet": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.2.0.tgz",
"integrity": "sha512-aoiSxXMd0ks1ojYpSCFoCRzgv4rY/uB9jKStaw8PkXwsdLYa/Gq+Nc5l0soH0cwBIsLAlujPnx4HLQs+LaXCrQ=="
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.3.1.tgz",
"integrity": "sha512-WsafDyKsIexB0+pUNkq3rL1rB5GVAghR68TP8ssM9DPEMzfBiluEQlVzJ/FEj6Vq2Ag3CNuxf7aYMjXrN0X49Q=="
},
"hosted-git-info": {
"version": "2.8.5",
@ -7597,9 +7597,9 @@
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
},
"webpack": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.11.0.tgz",
"integrity": "sha512-ubWv7iP54RqAC/VjixgpnLLogCFbAfSOREcSWnnOlZEU8GICC5eKmJSu6YEnph2N2amKqY9rvxSwgyHxVqpaRw==",
"version": "5.11.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.11.1.tgz",
"integrity": "sha512-tNUIdAmYJv+nupRs/U/gqmADm6fgrf5xE+rSlSsf2PgsGO7j2WG7ccU6AWNlOJlHFl+HnmXlBmHIkiLf+XA9mQ==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",

View File

@ -41,7 +41,7 @@
"express": "4.17.1",
"express-session": "1.17.1",
"fs-extra": "9.0.1",
"helmet": "4.2.0",
"helmet": "4.3.1",
"html": "1.0.0",
"html2plaintext": "2.1.2",
"http-proxy-agent": "4.0.1",
@ -86,7 +86,7 @@
"jsdoc": "3.6.6",
"lorem-ipsum": "2.0.3",
"rcedit": "3.0.0",
"webpack": "5.11.0",
"webpack": "5.11.1",
"webpack-cli": "4.3.0"
},
"optionalDependencies": {

View File

@ -5,7 +5,6 @@ import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import TabCachingWidget from "../widgets/tab_caching_widget.js";
import NoteTitleWidget from "../widgets/note_title.js";
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "../widgets/note_actions.js";
import NoteDetailWidget from "../widgets/note_detail.js";
@ -35,7 +34,6 @@ export default class DesktopExtraWindowLayout {
.overflowing()
.cssBlock('.title-row > * { margin: 5px 5px 0 5px; }')
.child(new NoteTitleWidget())
.child(new RunScriptButtonsWidget().hideInZenMode())
.child(new NoteTypeWidget().hideInZenMode())
.child(new NoteActionsWidget().hideInZenMode())
)

View File

@ -9,7 +9,6 @@ import TabCachingWidget from "../widgets/tab_caching_widget.js";
import NotePathsWidget from "../widgets/note_paths.js";
import NoteTitleWidget from "../widgets/note_title.js";
import OwnedAttributeListWidget from "../widgets/attribute_widgets/owned_attribute_list.js";
import RunScriptButtonsWidget from "../widgets/run_script_buttons.js";
import NoteTypeWidget from "../widgets/note_type.js";
import NoteActionsWidget from "../widgets/note_actions.js";
import NoteDetailWidget from "../widgets/note_detail.js";
@ -27,6 +26,8 @@ import InheritedAttributesWidget from "../widgets/inherited_attribute_list.js";
import NoteListWidget from "../widgets/note_list.js";
import SearchDefinitionWidget from "../widgets/search_definition.js";
import Container from "../widgets/container.js";
import SqlResultWidget from "../widgets/sql_result.js";
import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js";
const RIGHT_PANE_CSS = `
<style>
@ -149,7 +150,6 @@ export default class DesktopMainWindowLayout {
.cssBlock('.title-row > * { margin: 5px 5px 0 5px; }')
.overflowing()
.child(new NoteTitleWidget())
.child(new RunScriptButtonsWidget().hideInZenMode())
.child(new NoteTypeWidget().hideInZenMode())
.child(new NoteActionsWidget().hideInZenMode())
)
@ -163,8 +163,10 @@ export default class DesktopMainWindowLayout {
)
.child(new Container()
.css('height: 100%; overflow: auto;')
.child(new TabCachingWidget(() => new SqlTableSchemasWidget()))
.child(new TabCachingWidget(() => new NoteDetailWidget()))
.child(new TabCachingWidget(() => new NoteListWidget()))
.child(new TabCachingWidget(() => new SqlResultWidget()))
)
.child(new TabCachingWidget(() => new SimilarNotesWidget()))
.child(...this.customWidgets.get('center-pane'))

View File

@ -211,10 +211,12 @@ export default class Entrypoints extends Component {
if (note.mime.endsWith("env=frontend")) {
await bundleService.getAndExecuteBundle(note.noteId);
}
if (note.mime.endsWith("env=backend")) {
} else if (note.mime.endsWith("env=backend")) {
await server.post('script/run/' + note.noteId);
} else if (note.mime === 'text/x-sqlite;schema=trilium') {
const result = await server.post("sql/execute/" + note.noteId);
this.triggerEvent('sqlQueryResults', {results: result.results});
}
toastService.showMessage("Note executed");

View File

@ -22,10 +22,12 @@ const TPL = `
export default class NoteListWidget extends TabAwareWidget {
isEnabled() {
return super.isEnabled() && (
['book', 'search'].includes(this.note.type)
|| (this.note.type === 'text' && this.note.hasChildren())
);
return super.isEnabled()
&& this.note.mime !== 'text/x-sqlite;schema=trilium'
&& (
['book', 'search', 'code'].includes(this.note.type)
|| (this.note.type === 'text' && this.note.hasChildren())
);
}
doRender() {
@ -46,10 +48,6 @@ export default class NoteListWidget extends TabAwareWidget {
}
checkRenderStatus() {
console.log("this.isIntersecting", this.isIntersecting);
console.log("this.noteIdRefreshed === this.noteId", this.noteIdRefreshed === this.noteId);
console.log("this.shownNoteId !== this.noteId", this.shownNoteId !== this.noteId);
if (this.isIntersecting
&& this.noteIdRefreshed === this.noteId
&& this.shownNoteId !== this.noteId) {

View File

@ -1,33 +0,0 @@
import TabAwareWidget from "./tab_aware_widget.js";
const TPL = `
<div style="display: inline-flex;">
<button class="btn btn-sm icon-button bx bx-play-circle render-button"
data-trigger-command="renderActiveNote"
title="Render"></button>
<button class="btn btn-sm icon-button bx bx-play-circle execute-script-button"
data-trigger-command="runActiveNote"
title="Execute"></button>
</div>`;
export default class RunScriptButtonsWidget extends TabAwareWidget {
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$renderButton = this.$widget.find('.render-button');
this.$executeScriptButton = this.$widget.find('.execute-script-button');
}
refreshWithNote(note) {
this.$renderButton.toggle(note.type === 'render');
this.$executeScriptButton.toggle(note.type === 'code' && note.mime.startsWith('application/javascript'));
}
async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh();
}
}
}

View File

@ -0,0 +1,64 @@
import TabAwareWidget from "./tab_aware_widget.js";
import treeService from "../services/tree.js";
import linkService from "../services/link.js";
import hoistedNoteService from "../services/hoisted_note.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
const TPL = `
<div class="sql-result-widget">
<style>
.sql-result-widget {
padding: 15px;
}
</style>
<div class="sql-console-result-container"></div>
</div>`;
export default class SqlResultWidget extends TabAwareWidget {
isEnabled() {
return this.note
&& this.note.mime === 'text/x-sqlite;schema=trilium'
&& super.isEnabled();
}
doRender() {
this.$widget = $(TPL);
this.overflowing();
this.$sqlConsoleResultContainer = this.$widget.find('.sql-console-result-container');
}
async sqlQueryResultsEvent({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);
}
}
}
}

View File

@ -0,0 +1,83 @@
import TabAwareWidget from "./tab_aware_widget.js";
import treeService from "../services/tree.js";
import linkService from "../services/link.js";
import hoistedNoteService from "../services/hoisted_note.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
const TPL = `
<div class="sql-table-schemas-widget">
<style>
.sql-table-schemas button {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
border-radius: 0.2rem;
}
.sql-console-result-container {
width: 100%;
font-size: smaller;
margin-top: 10px;
flex-grow: 1;
overflow: auto;
min-height: 0;
}
.table-schema td {
padding: 5px;
}
</style>
Tables:
<span class="sql-table-schemas"></span>
</div>`;
export default class SqlTableSchemasWidget extends TabAwareWidget {
isEnabled() {
return this.note
&& this.note.mime === 'text/x-sqlite;schema=trilium'
&& super.isEnabled();
}
doRender() {
this.$widget = $(TPL);
this.overflowing();
this.$sqlConsoleTableSchemas = this.$widget.find('.sql-table-schemas');
}
async refreshWithNote(note) {
if (this.tableSchemasShown) {
return;
}
this.tableSchemasShown = true;
const tableSchema = await server.get('sql/schema');
for (const table of tableSchema) {
const $tableLink = $('<button class="btn">').text(table.name);
const $table = $('<table class="table-schema">');
for (const column of table.columns) {
$table.append(
$("<tr>")
.append($("<td>").text(column.name))
.append($("<td>").text(column.type))
);
}
this.$sqlConsoleTableSchemas.append($tableLink).append(" ");
$tableLink.tooltip({
html: true,
placement: 'bottom',
boundary: 'window',
title: $table[0].outerHTML,
sanitize: false
});
}
}
}

View File

@ -9,65 +9,24 @@ const TPL = `
<div class="note-detail-code note-detail-printable">
<style>
.note-detail-code {
overflow: auto;
height: 100%;
display: flex;
flex-direction: column;
}
.note-detail-code-editor {
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;
}
.table-schema td {
padding: 5px;
min-height: 300px;
}
</style>
<div class="sql-console-area">
Tables:
<span class="sql-console-table-schemas"></span>
</div>
<button data-trigger-command="runActiveNote"
class="no-print execute-button btn btn-sm"
style="position: absolute; top: 0px; right: 10px; z-index: 1000;">
Execute
</button>
<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"; }
@ -75,17 +34,10 @@ export default class EditableCodeTypeWidget extends TypeWidget {
this.$widget = $(TPL);
this.contentSized();
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');
this.$executeButton = this.$widget.find('.execute-button');
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();
}
@ -122,6 +74,11 @@ export default class EditableCodeTypeWidget extends TypeWidget {
}
async doRefresh(note) {
this.$executeButton.toggle(
note.mime.startsWith('application/javascript')
|| note.mime === 'text/x-sqlite;schema=trilium'
);
const noteComplement = await this.tabContext.getNoteComplement();
await this.spacedUpdate.allowUpdateWithoutChange(() => {
@ -138,104 +95,9 @@ 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 $table = $('<table class="table-schema">');
for (const column of table.columns) {
$table.append(
$("<tr>")
.append($("<td>").text(column.name))
.append($("<td>").text(column.type))
);
}
this.$sqlConsoleTableSchemas.append($tableLink).append(" ");
$tableLink
.tooltip({
html: true,
placement: 'bottom',
boundary: 'window',
title: $table[0].outerHTML,
sanitize: false
})
.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

@ -1,6 +1,7 @@
"use strict";
const sql = require('../../services/sql');
const repository = require('../../services/repository');
function getSchema() {
const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
@ -17,7 +18,13 @@ function getSchema() {
}
function execute(req) {
const queries = req.body.query.split("\n---");
const note = repository.getNote(req.params.noteId);
if (!note) {
return [404, `Note ${req.params.noteId} was not found.`];
}
const queries = note.getContent().split("\n---");
try {
const results = [];

View File

@ -232,7 +232,7 @@ function register(app) {
route(POST, '/api/setup/sync-seed', [auth.checkAppNotInitialized], setupApiRoute.saveSyncSeed, apiResultHandler, false);
apiRoute(GET, '/api/sql/schema', sqlRoute.getSchema);
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
apiRoute(POST, '/api/sql/execute/:noteId', sqlRoute.execute);
route(POST, '/api/database/anonymize', [auth.checkApiAuthOrElectron, csrfMiddleware], databaseRoute.anonymize, apiResultHandler, false);
// backup requires execution outside of transaction