mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Desktop, Cli: Resolves #4095: Allow exporting conflict notes
This commit is contained in:
parent
7be93b1fbb
commit
eb8284ecdb
@ -335,7 +335,6 @@
|
||||
"web/env.php": true,
|
||||
"**/*.js": {"when": "$(basename).ts"},
|
||||
"**/*?.js": { "when": "$(basename).tsx"},
|
||||
},
|
||||
"cSpell.enabled": true,
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,12 @@ function exportDir() {
|
||||
return `${__dirname}/export`;
|
||||
}
|
||||
|
||||
async function recreateExportDir() {
|
||||
const dir = exportDir();
|
||||
await fs.remove(dir);
|
||||
await fs.mkdirp(dir);
|
||||
}
|
||||
|
||||
function fieldsEqual(model1: any, model2: any, fieldNames: string[]) {
|
||||
for (let i = 0; i < fieldNames.length; i++) {
|
||||
const f = fieldNames[i];
|
||||
@ -31,10 +37,7 @@ describe('services_InteropService', function() {
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
|
||||
const dir = exportDir();
|
||||
await fs.remove(dir);
|
||||
await fs.mkdirp(dir);
|
||||
await recreateExportDir();
|
||||
done();
|
||||
});
|
||||
|
||||
@ -416,6 +419,36 @@ describe('services_InteropService', function() {
|
||||
expect(await shim.fsDriver().exists(`${exportDir()}/testexportfolder/textexportnote2.md`)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should export conflict notes', asyncTest(async () => {
|
||||
const folder1 = await Folder.save({ title: 'testexportfolder' });
|
||||
await Note.save({ title: 'textexportnote1', parent_id: folder1.id, is_conflict: 1 });
|
||||
await Note.save({ title: 'textexportnote2', parent_id: folder1.id });
|
||||
|
||||
const service = InteropService.instance();
|
||||
|
||||
await service.export({
|
||||
path: exportDir(),
|
||||
format: 'md',
|
||||
sourceFolderIds: [folder1.id],
|
||||
includeConflicts: false,
|
||||
});
|
||||
|
||||
expect(await shim.fsDriver().exists(`${exportDir()}/testexportfolder/textexportnote1.md`)).toBe(false);
|
||||
expect(await shim.fsDriver().exists(`${exportDir()}/testexportfolder/textexportnote2.md`)).toBe(true);
|
||||
|
||||
await recreateExportDir();
|
||||
|
||||
await service.export({
|
||||
path: exportDir(),
|
||||
format: 'md',
|
||||
sourceFolderIds: [folder1.id],
|
||||
includeConflicts: true,
|
||||
});
|
||||
|
||||
expect(await shim.fsDriver().exists(`${exportDir()}/testexportfolder/textexportnote1.md`)).toBe(true);
|
||||
expect(await shim.fsDriver().exists(`${exportDir()}/testexportfolder/textexportnote2.md`)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not try to export folders with a non-existing parent', asyncTest(async () => {
|
||||
// Handles and edge case where user has a folder but this folder with a parent
|
||||
// that doesn't exist. Can happen for example in this case:
|
||||
|
@ -19,6 +19,7 @@ interface ExportNoteOptions {
|
||||
printBackground?: boolean;
|
||||
pageSize?: string;
|
||||
landscape?: boolean;
|
||||
includeConflicts?: boolean;
|
||||
}
|
||||
|
||||
export default class InteropServiceHelper {
|
||||
@ -162,6 +163,7 @@ export default class InteropServiceHelper {
|
||||
exportOptions.format = module.format;
|
||||
exportOptions.modulePath = module.path;
|
||||
exportOptions.target = module.target;
|
||||
exportOptions.includeConflicts = !!options.includeConflicts;
|
||||
if (options.sourceFolderIds) exportOptions.sourceFolderIds = options.sourceFolderIds;
|
||||
if (options.sourceNoteIds) exportOptions.sourceNoteIds = options.sourceNoteIds;
|
||||
|
||||
|
@ -12,6 +12,7 @@ interface MultiNoteActionsProps {
|
||||
dispatch: Function;
|
||||
watchedNoteFiles: string[];
|
||||
plugins: PluginStates;
|
||||
inConflictFolder: boolean;
|
||||
}
|
||||
|
||||
function styles_(props: MultiNoteActionsProps) {
|
||||
@ -51,6 +52,7 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) {
|
||||
dispatch: props.dispatch,
|
||||
watchedNoteFiles: props.watchedNoteFiles,
|
||||
plugins: props.plugins,
|
||||
inConflictFolder: props.inConflictFolder,
|
||||
});
|
||||
|
||||
const itemComps = [];
|
||||
|
@ -36,6 +36,7 @@ const { substrWithEllipsis } = require('@joplin/lib/string-utils');
|
||||
const NoteSearchBar = require('../NoteSearchBar.min.js');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
const Note = require('@joplin/lib/models/Note.js');
|
||||
const Folder = require('@joplin/lib/models/Folder.js');
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const NoteRevisionViewer = require('../NoteRevisionViewer.min');
|
||||
|
||||
@ -449,6 +450,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
dispatch={props.dispatch}
|
||||
watchedNoteFiles={props.watchedNoteFiles}
|
||||
plugins={props.plugins}
|
||||
inConflictFolder={props.selectedFolderId === Folder.conflictFolderId()}
|
||||
/>;
|
||||
}
|
||||
|
||||
@ -560,6 +562,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
notes: state.notes,
|
||||
folders: state.folders,
|
||||
selectedNoteIds: state.selectedNoteIds,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
isProvisional: state.provisionalNoteIds.includes(noteId),
|
||||
editorNoteStatuses: state.editorNoteStatuses,
|
||||
syncStarted: state.syncStarted,
|
||||
|
@ -13,6 +13,7 @@ export interface NoteEditorProps {
|
||||
themeId: number;
|
||||
dispatch: Function;
|
||||
selectedNoteIds: string[];
|
||||
selectedFolderId: string;
|
||||
notes: any[];
|
||||
watchedNoteFiles: string[];
|
||||
isProvisional: boolean;
|
||||
|
@ -2,19 +2,21 @@ import { AppState } from '../../app';
|
||||
import eventManager from '@joplin/lib/eventManager';
|
||||
import NoteListUtils from '../utils/NoteListUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
const { ItemList } = require('../ItemList.min.js');
|
||||
import time from '@joplin/lib/time';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import bridge from '../../services/bridge';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import NoteListItem from '../NoteListItem';
|
||||
import CommandService from '@joplin/lib/services/CommandService.js';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import styled from 'styled-components';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
const React = require('react');
|
||||
|
||||
const { ItemList } = require('../ItemList.min.js');
|
||||
const { connect } = require('react-redux');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const bridge = require('electron').remote.require('./bridge').default;
|
||||
const Note = require('@joplin/lib/models/Note');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const NoteListItem = require('../NoteListItem').default;
|
||||
const CommandService = require('@joplin/lib/services/CommandService.js').default;
|
||||
const styled = require('styled-components').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const Folder = require('@joplin/lib/models/Folder');
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusElementNoteList'),
|
||||
@ -122,6 +124,7 @@ class NoteListComponent extends React.Component {
|
||||
dispatch: this.props.dispatch,
|
||||
watchedNoteFiles: this.props.watchedNoteFiles,
|
||||
plugins: this.props.plugins,
|
||||
inConflictFolder: this.props.selectedFolderId === Folder.conflictFolderId(),
|
||||
});
|
||||
|
||||
menu.popup(bridge().window());
|
||||
|
@ -19,6 +19,7 @@ interface ContextMenuProps {
|
||||
dispatch: Function;
|
||||
watchedNoteFiles: string[];
|
||||
plugins: PluginStates;
|
||||
inConflictFolder: boolean;
|
||||
}
|
||||
|
||||
export default class NoteListUtils {
|
||||
@ -149,7 +150,10 @@ export default class NoteListUtils {
|
||||
new MenuItem({
|
||||
label: module.fullLabel(),
|
||||
click: async () => {
|
||||
await InteropServiceHelper.export(props.dispatch.bind(this), module, { sourceNoteIds: noteIds });
|
||||
await InteropServiceHelper.export(props.dispatch.bind(this), module, {
|
||||
sourceNoteIds: noteIds,
|
||||
includeConflicts: props.inConflictFolder,
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -31,9 +31,18 @@ class Folder extends BaseItem {
|
||||
return field in fieldsToLabels ? fieldsToLabels[field] : field;
|
||||
}
|
||||
|
||||
static noteIds(parentId) {
|
||||
static noteIds(parentId, options = null) {
|
||||
options = Object.assign({}, {
|
||||
includeConflicts: false,
|
||||
}, options);
|
||||
|
||||
const where = ['parent_id = ?'];
|
||||
if (!options.includeConflicts) {
|
||||
where.push('is_conflict = 0');
|
||||
}
|
||||
|
||||
return this.db()
|
||||
.selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId])
|
||||
.selectAll(`SELECT id FROM notes WHERE ${where.join(' AND ')}`, [parentId])
|
||||
.then(rows => {
|
||||
const output = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
|
@ -321,6 +321,8 @@ export default class InteropService {
|
||||
// Recursively get all the folders that have valid parents
|
||||
const folderIds = await Folder.childrenIds('', true);
|
||||
|
||||
if (options.includeConflicts) folderIds.push(Folder.conflictFolderId());
|
||||
|
||||
let fullSourceFolderIds = sourceFolderIds.slice();
|
||||
for (let i = 0; i < sourceFolderIds.length; i++) {
|
||||
const id = sourceFolderIds[i];
|
||||
@ -335,7 +337,7 @@ export default class InteropService {
|
||||
|
||||
if (!sourceNoteIds.length) await queueExportItem(BaseModel.TYPE_FOLDER, folderId);
|
||||
|
||||
const noteIds = await Folder.noteIds(folderId);
|
||||
const noteIds = await Folder.noteIds(folderId, { includeConflicts: !!options.includeConflicts });
|
||||
|
||||
for (let noteIndex = 0; noteIndex < noteIds.length; noteIndex++) {
|
||||
const noteId = noteIds[noteIndex];
|
||||
|
@ -93,6 +93,7 @@ export interface ExportOptions {
|
||||
sourceNoteIds?: string[];
|
||||
modulePath?: string;
|
||||
target?: FileSystemItem;
|
||||
includeConflicts?: boolean;
|
||||
}
|
||||
|
||||
export interface ImportExportResult {
|
||||
|
Loading…
Reference in New Issue
Block a user