You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-11-29 22:48:10 +02:00
Desktop: Added Goto Anything dialog and implemented basic plugin system
This commit is contained in:
@@ -7,7 +7,7 @@ const { Database } = require('lib/database.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const moment = require('moment');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const lodash = require('lodash');
|
||||
const { substrWithEllipsis } = require('lib/string-utils.js');
|
||||
|
||||
class Folder extends BaseItem {
|
||||
|
||||
@@ -217,6 +217,34 @@ class Folder extends BaseItem {
|
||||
return getNestedChildren(all, '');
|
||||
}
|
||||
|
||||
static folderPath(folders, folderId) {
|
||||
const idToFolders = {};
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
idToFolders[folders[i].id] = folders[i];
|
||||
}
|
||||
|
||||
const path = [];
|
||||
while (folderId) {
|
||||
const folder = idToFolders[folderId];
|
||||
if (!folder) break; // Shouldn't happen
|
||||
path.push(folder);
|
||||
folderId = folder.parent_id;
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static folderPathString(folders, folderId) {
|
||||
const path = this.folderPath(folders, folderId);
|
||||
const output = [];
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
output.push(substrWithEllipsis(path[i].title, 0, 16));
|
||||
}
|
||||
return output.join(' / ');
|
||||
}
|
||||
|
||||
static buildTree(folders) {
|
||||
const idToFolders = {};
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
|
||||
@@ -90,9 +90,19 @@ class Tag extends BaseItem {
|
||||
return !!r;
|
||||
}
|
||||
|
||||
static tagsWithNotesSql_() {
|
||||
return 'select distinct tags.id from tags left join note_tags nt on nt.tag_id = tags.id left join notes on notes.id = nt.note_id where notes.id IS NOT NULL';
|
||||
}
|
||||
|
||||
static async allWithNotes() {
|
||||
const tagIdSql = 'select distinct tags.id from tags left join note_tags nt on nt.tag_id = tags.id left join notes on notes.id = nt.note_id where notes.id IS NOT NULL';
|
||||
return await Tag.modelSelectAll('SELECT * FROM tags WHERE id IN (' + tagIdSql + ')');
|
||||
return await Tag.modelSelectAll('SELECT * FROM tags WHERE id IN (' + this.tagsWithNotesSql_() + ')');
|
||||
}
|
||||
|
||||
static async searchAllWithNotes(options) {
|
||||
if (!options) options = {};
|
||||
if (!options.conditions) options.conditions = [];
|
||||
options.conditions.push('id IN (' + this.tagsWithNotesSql_() + ')');
|
||||
return this.search(options);
|
||||
}
|
||||
|
||||
static async tagsByNoteId(noteId) {
|
||||
|
||||
@@ -48,6 +48,7 @@ const defaultState = {
|
||||
toFetchCount: 0,
|
||||
},
|
||||
historyNotes: [],
|
||||
plugins: {},
|
||||
};
|
||||
|
||||
const stateUtils = {};
|
||||
@@ -691,6 +692,17 @@ const reducer = (state = defaultState, action) => {
|
||||
newState.selectedNoteTags = action.items;
|
||||
break;
|
||||
|
||||
case 'PLUGIN_DIALOG_SET':
|
||||
|
||||
if (!action.pluginName) throw new Error('action.pluginName not specified');
|
||||
newState = Object.assign({}, state);
|
||||
const newPlugins = Object.assign({}, newState.plugins);
|
||||
const newPlugin = newState.plugins[action.pluginName] ? Object.assign({}, newState.plugins[action.pluginName]) : {};
|
||||
if ('open' in action) newPlugin.dialogOpen = action.open;
|
||||
newPlugins[action.pluginName] = newPlugin;
|
||||
newState.plugins = newPlugins;
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);
|
||||
|
||||
105
ReactNativeClient/lib/services/PluginManager.js
Normal file
105
ReactNativeClient/lib/services/PluginManager.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const { Logger } = require('lib/logger.js');
|
||||
|
||||
class PluginManager {
|
||||
|
||||
constructor() {
|
||||
this.plugins_ = {};
|
||||
this.logger_ = new Logger();
|
||||
}
|
||||
|
||||
setLogger(l) {
|
||||
this.logger_ = l;
|
||||
}
|
||||
|
||||
logger() {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
static instance() {
|
||||
if (this.instance_) return this.instance_;
|
||||
this.instance_ = new PluginManager();
|
||||
return this.instance_;
|
||||
}
|
||||
|
||||
register(classes) {
|
||||
if (!Array.isArray(classes)) classes = [classes];
|
||||
|
||||
for (let i = 0; i < classes.length; i++) {
|
||||
const PluginClass = classes[i];
|
||||
|
||||
if (this.plugins_[PluginClass.manifest.name]) throw new Error('Already registered: ' + PluginClass.manifest.name);
|
||||
|
||||
this.plugins_[PluginClass.manifest.name] = {
|
||||
Class: PluginClass,
|
||||
instance: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pluginInstance_(name) {
|
||||
const p = this.plugins_[name];
|
||||
if (p.instance) return p.instance;
|
||||
p.instance = new p.Class();
|
||||
p.instance.dispatch = (action) => this.dispatch_(action);
|
||||
return p.instance;
|
||||
}
|
||||
|
||||
pluginClass_(name) {
|
||||
return this.plugins_[name].Class;
|
||||
}
|
||||
|
||||
onPluginMenuItemTrigger_(event) {
|
||||
const p = this.pluginInstance_(event.pluginName);
|
||||
p.onTrigger({
|
||||
itemName: event.itemName,
|
||||
});
|
||||
}
|
||||
|
||||
pluginDialogToShow(pluginStates) {
|
||||
for (const name in pluginStates) {
|
||||
const p = pluginStates[name];
|
||||
if (!p.dialogOpen) continue;
|
||||
|
||||
const Class = this.pluginClass_(name);
|
||||
if (!Class.Dialog) continue;
|
||||
|
||||
return {
|
||||
Dialog: Class.Dialog,
|
||||
props: this.dialogProps_(name),
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
dialogProps_(name) {
|
||||
return {
|
||||
dispatch: (action) => this.dispatch_(action),
|
||||
plugin: this.pluginInstance_(name),
|
||||
};
|
||||
}
|
||||
|
||||
menuItems() {
|
||||
let output = [];
|
||||
for (const name in this.plugins_) {
|
||||
const menuItems = this.plugins_[name].Class.manifest.menuItems;
|
||||
if (!menuItems) continue;
|
||||
|
||||
for (const item of menuItems) {
|
||||
item.click = () => {
|
||||
this.onPluginMenuItemTrigger_({
|
||||
pluginName: name,
|
||||
itemName: item.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
output = output.concat(menuItems);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = PluginManager;
|
||||
@@ -267,7 +267,7 @@ class SearchEngine {
|
||||
|
||||
if (c === ':' && !inQuote) {
|
||||
currentCol = currentTerm;
|
||||
terms[currentCol] = [];
|
||||
if (!terms[currentCol]) terms[currentCol] = [];
|
||||
currentTerm = '';
|
||||
continue;
|
||||
}
|
||||
@@ -368,7 +368,7 @@ class SearchEngine {
|
||||
return this.basicSearch(query);
|
||||
} else {
|
||||
const parsedQuery = this.parseQuery(query);
|
||||
const sql = 'SELECT notes_fts.id, notes_fts.title, offsets(notes_fts) AS offsets, notes.user_updated_time, notes.is_todo, notes.todo_completed FROM notes_fts LEFT JOIN notes ON notes_fts.id = notes.id WHERE notes_fts MATCH ?'
|
||||
const sql = 'SELECT notes_fts.id, notes_fts.title, offsets(notes_fts) AS offsets, notes.user_updated_time, notes.is_todo, notes.todo_completed, notes.parent_id FROM notes_fts LEFT JOIN notes ON notes_fts.id = notes.id WHERE notes_fts MATCH ?'
|
||||
try {
|
||||
const rows = await this.db().selectAll(sql, [query]);
|
||||
this.orderResults_(rows, parsedQuery);
|
||||
|
||||
Reference in New Issue
Block a user