mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Added support for enex import
This commit is contained in:
parent
6b3bda2941
commit
e649670bfe
@ -24,13 +24,20 @@ const appDefaultState = Object.assign({}, defaultState, {
|
||||
route: {
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Main',
|
||||
params: {},
|
||||
props: {},
|
||||
},
|
||||
navHistory: [],
|
||||
fileToImport: null,
|
||||
windowCommand: null,
|
||||
});
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.lastMenuScreen_ = null;
|
||||
}
|
||||
|
||||
hasGui() {
|
||||
return true;
|
||||
}
|
||||
@ -76,6 +83,12 @@ class Application extends BaseApplication {
|
||||
newState.windowContentSize = action.size;
|
||||
break;
|
||||
|
||||
case 'WINDOW_COMMAND':
|
||||
|
||||
newState = Object.assign({}, state);
|
||||
newState.windowCommand = { name: action.name };
|
||||
break;
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);
|
||||
@ -90,16 +103,75 @@ class Application extends BaseApplication {
|
||||
if (!await reg.syncStarted()) reg.scheduleSync();
|
||||
}
|
||||
|
||||
return super.generalMiddleware(store, next, action);
|
||||
const result = await super.generalMiddleware(store, next, action);
|
||||
const newState = store.getState();
|
||||
|
||||
if (action.type === 'NAV_GO' || action.type === 'NAV_BACK') {
|
||||
app().updateMenu(newState.route.routeName);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
setupMenu() {
|
||||
updateMenu(screen) {
|
||||
if (this.lastMenuScreen_ === screen) return;
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [{
|
||||
label: _('New note'),
|
||||
accelerator: 'CommandOrControl+N',
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'newNote',
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: _('New to-do'),
|
||||
accelerator: 'CommandOrControl+T',
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'newTodo',
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: _('New notebook'),
|
||||
screens: ['Main'],
|
||||
click: () => {
|
||||
this.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'newNotebook',
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _('Import Evernote notes'),
|
||||
click () { }
|
||||
click: () => {
|
||||
const filePaths = bridge().showOpenDialog({
|
||||
properties: ['openFile', 'createDirectory'],
|
||||
filters: [
|
||||
{ name: _('Evernote Export Files'), extensions: ['enex'] },
|
||||
]
|
||||
});
|
||||
if (!filePaths || !filePaths.length) return;
|
||||
|
||||
this.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Import',
|
||||
props: {
|
||||
filePath: filePaths[0],
|
||||
},
|
||||
});
|
||||
}
|
||||
}, {
|
||||
type: 'separator',
|
||||
}, {
|
||||
label: _('Quit'),
|
||||
accelerator: 'CommandOrControl+Q',
|
||||
@ -113,19 +185,34 @@ class Application extends BaseApplication {
|
||||
click () { bridge().openExternal('http://joplin.cozic.net') }
|
||||
}, {
|
||||
label: _('About Joplin'),
|
||||
click () { }
|
||||
click () { }
|
||||
}]
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
function removeUnwantedItems(template, screen) {
|
||||
let output = [];
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
const t = Object.assign({}, template[i]);
|
||||
if (t.screens && t.screens.indexOf(screen) < 0) continue;
|
||||
if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen);
|
||||
output.push(t);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
let screenTemplate = removeUnwantedItems(template, screen);
|
||||
|
||||
const menu = Menu.buildFromTemplate(screenTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
|
||||
this.lastMenuScreen_ = screen;
|
||||
}
|
||||
|
||||
async start(argv) {
|
||||
argv = await super.start(argv);
|
||||
|
||||
this.setupMenu();
|
||||
this.updateMenu('Main');
|
||||
|
||||
this.initRedux();
|
||||
|
||||
|
@ -35,9 +35,6 @@ class HeaderComponent extends React.Component {
|
||||
style.boxSizing = 'border-box';
|
||||
|
||||
const buttons = [];
|
||||
if (showBackButton) {
|
||||
buttons.push(this.makeButton('back', {}, { title: _('Back'), onClick: () => this.back_click() }));
|
||||
}
|
||||
|
||||
const buttonStyle = {
|
||||
height: theme.headerHeight,
|
||||
@ -53,6 +50,10 @@ class HeaderComponent extends React.Component {
|
||||
cursor: 'default',
|
||||
};
|
||||
|
||||
if (showBackButton) {
|
||||
buttons.push(this.makeButton('back', buttonStyle, { title: _('Back'), onClick: () => this.back_click(), iconName: 'fa-chevron-left ' }));
|
||||
}
|
||||
|
||||
if (this.props.buttons) {
|
||||
for (let i = 0; i < this.props.buttons.length; i++) {
|
||||
const o = this.props.buttons[i];
|
||||
|
136
ElectronClient/app/gui/ImportScreen.jsx
Normal file
136
ElectronClient/app/gui/ImportScreen.jsx
Normal file
@ -0,0 +1,136 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { Folder } = require('lib/models/folder.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { filename, basename } = require('lib/path-utils.js');
|
||||
const { importEnex } = require('lib/import-enex');
|
||||
|
||||
class ImportScreenComponent extends React.Component {
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
doImport: true,
|
||||
filePath: this.props.filePath,
|
||||
messages: [],
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.filePath) {
|
||||
this.setState({
|
||||
doImport: true,
|
||||
filePath: newProps.filePath,
|
||||
messages: [],
|
||||
});
|
||||
|
||||
this.doImport();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.filePath && this.state.doImport) {
|
||||
this.doImport();
|
||||
}
|
||||
}
|
||||
|
||||
addMessage(key, text) {
|
||||
const messages = this.state.messages.slice();
|
||||
let found = false;
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
if (messages[i].key === key) {
|
||||
messages[i].text = text;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) messages.push({ key: key, text: text });
|
||||
|
||||
this.setState({ messages: messages });
|
||||
}
|
||||
|
||||
async doImport() {
|
||||
const filePath = this.props.filePath;
|
||||
const folderTitle = await Folder.findUniqueFolderTitle(filename(filePath));
|
||||
const messages = this.state.messages.slice();
|
||||
|
||||
this.addMessage('start', _('New notebook "%s" will be created and file "%s" will be imported into it', folderTitle, basename(filePath)));
|
||||
|
||||
let lastProgress = '';
|
||||
let progressCount = 0;
|
||||
|
||||
const options = {
|
||||
onProgress: (progressState) => {
|
||||
let line = [];
|
||||
line.push(_('Found: %d.', progressState.loaded));
|
||||
line.push(_('Created: %d.', progressState.created));
|
||||
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
||||
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
|
||||
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
||||
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
|
||||
lastProgress = line.join(' ');
|
||||
this.addMessage('progress', lastProgress);
|
||||
},
|
||||
onError: (error) => {
|
||||
const messages = this.state.messages.slice();
|
||||
let s = error.trace ? error.trace : error.toString();
|
||||
messages.push({ key: 'error_' + (progressCount++), text: s });
|
||||
this.addMessage('error_' + (progressCount++), lastProgress);
|
||||
},
|
||||
}
|
||||
|
||||
// const folder = await Folder.save({ title: folderTitle });
|
||||
|
||||
// await importEnex(folder.id, filePath, options);
|
||||
|
||||
this.addMessage('done', _('The notes have been imported: %s', lastProgress));
|
||||
this.setState({ doImport: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
const messages = this.state.messages;
|
||||
|
||||
const messagesStyle = {
|
||||
padding: 10,
|
||||
fontSize: theme.fontSize,
|
||||
fontFamily: theme.fontFamily,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
};
|
||||
|
||||
const headerStyle = {
|
||||
width: style.width,
|
||||
};
|
||||
|
||||
const messageComps = [];
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
messageComps.push(<div key={messages[i].key}>{messages[i].text}</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{}}>
|
||||
<Header style={headerStyle} />
|
||||
<div style={messagesStyle}>
|
||||
{messageComps}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const ImportScreen = connect(mapStateToProps)(ImportScreenComponent);
|
||||
|
||||
module.exports = { ImportScreen };
|
@ -24,6 +24,12 @@ class MainScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.windowCommand) {
|
||||
this.doCommand(newProps.windowCommand);
|
||||
}
|
||||
}
|
||||
|
||||
toggleVisiblePanes() {
|
||||
let panes = this.state.noteVisiblePanes.slice();
|
||||
if (panes.length === 2) {
|
||||
@ -37,6 +43,84 @@ class MainScreenComponent extends React.Component {
|
||||
this.setState({ noteVisiblePanes: panes });
|
||||
}
|
||||
|
||||
doCommand(command) {
|
||||
if (!command) return;
|
||||
|
||||
const createNewNote = async (title, isTodo) => {
|
||||
const folderId = Setting.value('activeFolderId');
|
||||
if (!folderId) return;
|
||||
|
||||
const note = await Note.save({
|
||||
title: title,
|
||||
parent_id: folderId,
|
||||
is_todo: isTodo ? 1 : 0,
|
||||
});
|
||||
Note.updateGeolocation(note.id);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NOTE_SELECT',
|
||||
id: note.id,
|
||||
});
|
||||
}
|
||||
|
||||
let commandProcessed = true;
|
||||
|
||||
if (command.name === 'newNote') {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
message: _('Note title:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer) await createNewNote(answer, false);
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'newTodo') {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
message: _('To-do title:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer) await createNewNote(answer, true);
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'newNotebook') {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
message: _('Notebook title:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer) {
|
||||
let folder = null;
|
||||
try {
|
||||
folder = await Folder.save({ title: answer }, { userSideValidation: true });
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: folder.id,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
commandProcessed = false;
|
||||
}
|
||||
|
||||
if (commandProcessed) {
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
@ -74,85 +158,24 @@ class MainScreenComponent extends React.Component {
|
||||
height: style.height,
|
||||
};
|
||||
|
||||
const createNewNote = async (title, isTodo) => {
|
||||
const folderId = Setting.value('activeFolderId');
|
||||
if (!folderId) return;
|
||||
|
||||
const note = await Note.save({
|
||||
title: title,
|
||||
parent_id: folderId,
|
||||
is_todo: isTodo ? 1 : 0,
|
||||
});
|
||||
Note.updateGeolocation(note.id);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NOTE_SELECT',
|
||||
id: note.id,
|
||||
});
|
||||
}
|
||||
|
||||
const headerButtons = [];
|
||||
|
||||
headerButtons.push({
|
||||
title: _('New note'),
|
||||
iconName: 'fa-file-o',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
message: _('Note title:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer) await createNewNote(answer, false);
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
onClick: () => { this.doCommand({ name: 'newNote' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
title: _('New to-do'),
|
||||
iconName: 'fa-check-square-o',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
message: _('Note title:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer) await createNewNote(answer, true);
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
onClick: () => { this.doCommand({ name: 'newTodo' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
title: _('New notebook'),
|
||||
iconName: 'fa-folder-o',
|
||||
onClick: () => {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
message: _('Notebook title:'),
|
||||
onClose: async (answer) => {
|
||||
if (answer) {
|
||||
let folder = null;
|
||||
try {
|
||||
folder = await Folder.save({ title: answer }, { userSideValidation: true });
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: folder.id,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
onClick: () => { this.doCommand({ name: 'newNotebook' }) },
|
||||
});
|
||||
|
||||
headerButtons.push({
|
||||
@ -179,6 +202,7 @@ class MainScreenComponent extends React.Component {
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
windowCommand: state.windowCommand,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
const React = require('react'); const Component = React.Component;
|
||||
const { connect } = require('react-redux');
|
||||
const { app } = require('../app.js');
|
||||
|
||||
class NavigatorComponent extends Component {
|
||||
|
||||
@ -7,6 +8,7 @@ class NavigatorComponent extends Component {
|
||||
if (!this.props.route) throw new Error('Route must not be null');
|
||||
|
||||
const route = this.props.route;
|
||||
const screenProps = route.props ? route.props : {};
|
||||
const Screen = this.props.screens[route.routeName].screen;
|
||||
|
||||
const screenStyle = {
|
||||
@ -16,7 +18,7 @@ class NavigatorComponent extends Component {
|
||||
|
||||
return (
|
||||
<div style={this.props.style}>
|
||||
<Screen style={screenStyle}/>
|
||||
<Screen style={screenStyle} {...screenProps}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ const { connect, Provider } = require('react-redux');
|
||||
|
||||
const { MainScreen } = require('./MainScreen.min.js');
|
||||
const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
const { Navigator } = require('./Navigator.min.js');
|
||||
|
||||
const { app } = require('../app');
|
||||
@ -52,6 +53,7 @@ class RootComponent extends React.Component {
|
||||
const screens = {
|
||||
Main: { screen: MainScreen },
|
||||
OneDriveLogin: { screen: OneDriveLoginScreen },
|
||||
Import: { screen: ImportScreen },
|
||||
};
|
||||
|
||||
return (
|
||||
|
41
ElectronClient/app/package-lock.json
generated
41
ElectronClient/app/package-lock.json
generated
@ -1114,8 +1114,7 @@
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"create-error-class": {
|
||||
"version": "3.0.2",
|
||||
@ -2261,8 +2260,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.4",
|
||||
@ -2490,8 +2488,7 @@
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isbinaryfile": {
|
||||
"version": "3.0.2",
|
||||
@ -2611,6 +2608,11 @@
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"jssha": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-2.3.1.tgz",
|
||||
"integrity": "sha1-FHshJTaQNcpLL30hDcU58Amz3po="
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
@ -2653,6 +2655,11 @@
|
||||
"invert-kv": "1.0.0"
|
||||
}
|
||||
},
|
||||
"levenshtein": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/levenshtein/-/levenshtein-1.0.5.tgz",
|
||||
"integrity": "sha1-ORFzepy1baNF0Aj1V4LG8TiXm6M="
|
||||
},
|
||||
"linkify-it": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz",
|
||||
@ -3486,8 +3493,7 @@
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
||||
"dev": true
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
},
|
||||
"progress-stream": {
|
||||
"version": "1.2.0",
|
||||
@ -3726,7 +3732,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
@ -4899,6 +4904,20 @@
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
|
||||
},
|
||||
"string-padding": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-padding/-/string-padding-1.0.2.tgz",
|
||||
"integrity": "sha1-OqrYVbPpc1xeQS3+chmMz5nH9I4="
|
||||
},
|
||||
"string-to-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-rPLJ6tHEGOFIUJoS0su0afMzohg=",
|
||||
"requires": {
|
||||
"inherits": "2.0.3",
|
||||
"readable-stream": "2.3.3"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
@ -4914,7 +4933,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
@ -5270,8 +5288,7 @@
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.1.0",
|
||||
|
@ -31,6 +31,8 @@
|
||||
"fs-extra": "^4.0.2",
|
||||
"highlight.js": "^9.12.0",
|
||||
"html-entities": "^1.2.1",
|
||||
"jssha": "^2.3.1",
|
||||
"levenshtein": "^1.0.5",
|
||||
"lodash": "^4.17.4",
|
||||
"markdown-it": "^8.4.0",
|
||||
"marked": "^0.3.6",
|
||||
@ -48,6 +50,8 @@
|
||||
"sharp": "^0.18.4",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"sqlite3": "^3.1.13",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"tcp-port-used": "^0.1.2"
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ const { time } = require('lib/time-utils.js');
|
||||
const Levenshtein = require('levenshtein');
|
||||
const jsSHA = require("jssha");
|
||||
|
||||
const Promise = require('promise');
|
||||
//const Promise = require('promise');
|
||||
const fs = require('fs-extra');
|
||||
const stringToStream = require('string-to-stream')
|
||||
|
@ -34,6 +34,19 @@ class Folder extends BaseItem {
|
||||
}
|
||||
}
|
||||
|
||||
static async findUniqueFolderTitle(title) {
|
||||
let counter = 1;
|
||||
let titleToTry = title;
|
||||
while (true) {
|
||||
const folder = await this.loadByField('title', titleToTry);
|
||||
if (!folder) return titleToTry;
|
||||
titleToTry = title + ' (' + counter + ')';
|
||||
counter++;
|
||||
if (counter >= 100) titleToTry = title + ' (' + ((new Date()).getTime()) + ')';
|
||||
if (counter >= 1000) throw new Error('Cannot find unique title');
|
||||
}
|
||||
}
|
||||
|
||||
static noteIds(parentId) {
|
||||
return this.db().selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]).then((rows) => {
|
||||
let output = [];
|
||||
|
Loading…
Reference in New Issue
Block a user