1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00

Merge branch 'welcome'

This commit is contained in:
Laurent Cozic 2019-02-05 21:14:43 +00:00
commit 0e122c9dc5
18 changed files with 435 additions and 37 deletions

View File

@ -15,6 +15,7 @@ const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const WelcomeUtils = require('lib/WelcomeUtils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@ -1099,4 +1100,44 @@ describe('Synchronizer', function() {
expect(note2.title).toBe("un UPDATE");
}));
it("should sync Welcome notebook and not duplicate it", asyncTest(async () => {
// Create the Welcome items on two separate clients and verify
// that they appear only once (due to having hard-coded IDs), that they are not duplicated.
Setting.setConstant('env', 'prod');
await WelcomeUtils.createWelcomeItems();
await synchronizer().start();
await switchClient(2);
await WelcomeUtils.createWelcomeItems();
const beforeFolderCount = (await Folder.all()).length;
const beforeNoteCount = (await Note.all()).length;
expect(beforeFolderCount >= 1).toBe(true);
expect(beforeNoteCount > 1).toBe(true);
await synchronizer().start();
const afterFolderCount = (await Folder.all()).length;
const afterNoteCount = (await Note.all()).length;
expect(afterFolderCount).toBe(beforeFolderCount);
expect(afterNoteCount).toBe(beforeNoteCount);
// Changes to the Welcome items should be synced to all clients
const f1 = (await Folder.all())[0];
await Folder.save({ id: f1.id, title: 'Welcome MOD' });
await synchronizer().start();
await switchClient(1);
await synchronizer().start();
const f1_1 = (await Folder.all())[0];
expect(f1_1.title).toBe('Welcome MOD');
}));
});

View File

@ -201,6 +201,10 @@ Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a
For a more technical description, mostly relevant for development or to review the method being used, please see the [Encryption specification](https://joplin.cozic.net/spec).
# External text editor
Joplin notes can be opened and edited using an external editor of your choice. It can be a simple text editor like Notepad++ or Sublime Text or an actual Markdown editor like Typora. In that case, images will also be displayed within the editor. To open the note in an external editor, click on the icon in the toolbar or press Ctrl+E (or Cmd+E). Your default text editor will be used to open the note. If needed, you can also specify the editor directly in the General Options, under "Text editor command".
# Attachments / Resources
Any kind of file can be attached to a note. In Markdown, links to these files are represented as a simple ID to the resource. In the note viewer, these files, if they are images, will be displayed or, if they are other files (PDF, text files, etc.) they will be displayed as links. Clicking on this link will open the file in the default application.

View File

@ -38,6 +38,7 @@ const SearchEngineUtils = require('lib/services/SearchEngineUtils');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
const SearchEngine = require('lib/services/SearchEngine');
const WelcomeUtils = require('lib/WelcomeUtils');
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetOneDrive);
@ -564,6 +565,8 @@ class BaseApplication {
if (!currentFolder) currentFolder = await Folder.defaultFolder();
Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : '');
await WelcomeUtils.createWelcomeItems();
// await this.testing();process.exit();
return argv;

View File

@ -0,0 +1,100 @@
const welcomeAssets = require('./welcomeAssets');
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting');
const Folder = require('lib/models/Folder');
const Tag = require('lib/models/Tag');
const Resource = require('lib/models/Resource');
const { shim } = require('lib/shim');
const { uuid } = require('lib/uuid');
const { fileExtension, basename} = require('lib/path-utils');
const { pregQuote } = require('lib/string-utils');
class WelcomeUtils {
static async createWelcomeItems() {
const overwriteExisting = Setting.value('env') === 'dev';
const noteAssets = welcomeAssets.notes;
const folderAssets = welcomeAssets.folders;
const tempDir = Setting.value('resourceDir');
// TODO: Update BaseApplication
// TODO: Update mobile root.js
// TODO: Finish Welcome notes
for (let i = 0; i < folderAssets.length; i++) {
const folderAsset = folderAssets[i];
const folderId = folderAsset.id;
let existingFolder = await Folder.load(folderId);
if (existingFolder && overwriteExisting) {
await Folder.delete(existingFolder.id);
existingFolder = null;
}
if (existingFolder) continue;
await Folder.save({
id: folderId,
title: folderAsset.title,
}, { isNew: true });
}
for (let i = noteAssets.length - 1; i >= 0; i--) {
const noteAsset = noteAssets[i];
const noteId = noteAsset.id;
let existingNote = await Note.load(noteId);
if (existingNote && overwriteExisting) {
await Note.delete(existingNote.id);
existingNote = null;
}
if (existingNote) continue;
let noteBody = noteAsset.body;
for (let resourceUrl in noteAsset.resources) {
if (!noteAsset.resources.hasOwnProperty(resourceUrl)) continue;
const resourceAsset = noteAsset.resources[resourceUrl];
const resourceId = resourceAsset.id;
let existingResource = await Resource.load(resourceId);
if (existingResource && overwriteExisting) {
await Resource.delete(resourceId);
existingResource = null;
}
if (!existingResource) {
const ext = fileExtension(resourceUrl);
const tempFilePath = tempDir + '/' + uuid.create() + '.tmp.' + ext;
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
await shim.createResourceFromPath(tempFilePath, {
id: resourceId,
title: basename(resourceUrl),
});
await shim.fsDriver().remove(tempFilePath);
}
const regex = new RegExp(pregQuote('(' + resourceUrl + ')'), 'g');
noteBody = noteBody.replace(regex, '(:/' + resourceId + ')');
}
await Note.save({
id: noteId,
parent_id: noteAsset.parent_id,
title: noteAsset.title,
body: noteBody,
}, { isNew: true });
if (noteAsset.tags) await Tag.setNoteTagsByTitles(noteId, noteAsset.tags);
}
}
}
module.exports = WelcomeUtils;

View File

@ -64,10 +64,10 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
reg.logger().info('Scheduling sync operation...');
// if (Setting.value("env") === "dev" && delay !== 0) {
// reg.logger().info("Schedule sync DISABLED!!!");
// return;
// }
if (Setting.value("env") === "dev" && delay !== 0) {
reg.logger().info("Schedule sync DISABLED!!!");
return;
}
const timeoutCallback = async () => {
reg.scheduleSyncId_ = null;

View File

@ -101,7 +101,7 @@ function shimInit() {
}
}
shim.createResourceFromPath = async function(filePath) {
shim.createResourceFromPath = async function(filePath, defaultProps = null) {
const readChunk = require('read-chunk');
const imageType = require('image-type');
@ -111,8 +111,12 @@ function shimInit() {
if (!(await fs.pathExists(filePath))) throw new Error(_('Cannot access %s', filePath));
defaultProps = defaultProps ? defaultProps : {};
const resourceId = defaultProps.id ? defaultProps.id : uuid.create();
let resource = Resource.new();
resource.id = uuid.create();
resource.id = resourceId;
resource.mime = mime.getType(filePath);
resource.title = basename(filePath);
@ -143,6 +147,10 @@ function shimInit() {
await fs.copy(filePath, targetPath, { overwrite: true });
}
if (defaultProps) {
resource = Object.assign({}, resource, defaultProps);
}
return await Resource.save(resource, { isNew: true });
}

View File

@ -7,6 +7,10 @@ const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN;
const urlValidator = require('valid-url');
const { Buffer } = require('buffer');
const { Linking } = require('react-native');
const mimeUtils = require('lib/mime-utils.js').mime;
const { basename, fileExtension } = require('lib/path-utils.js');
const { uuid } = require('lib/uuid.js');
const Resource = require('lib/models/Resource');
function shimInit() {
shim.Geolocation = GeolocationReact;
@ -129,6 +133,37 @@ function shimInit() {
requestAnimationFrame(function() { resolve(); });
});
}
// NOTE: This is a limited version of createResourceFromPath - unlike the Node version, it
// only really works with images. It does not resize the image either.
shim.createResourceFromPath = async function(filePath, defaultProps = null) {
defaultProps = defaultProps ? defaultProps : {};
const resourceId = defaultProps.id ? defaultProps.id : uuid.create();
const ext = fileExtension(filePath);
let mimeType = mimeUtils.fromFileExtension(ext);
if (!mimeType) mimeType = 'image/jpeg';
let resource = Resource.new();
resource.id = resourceId;
resource.mime = mimeType;
resource.title = basename(filePath);
resource.file_extension = ext;
let targetPath = Resource.fullPath(resource);
await shim.fsDriver().copy(filePath, targetPath);
if (defaultProps) {
resource = Object.assign({}, resource, defaultProps);
}
resource = await Resource.save(resource, { isNew: true });
console.info(resource);
return resource;
}
}
module.exports = { shimInit };

File diff suppressed because one or more lines are too long

View File

@ -53,6 +53,7 @@ const DropdownAlert = require('react-native-dropdownalert').default;
const ShareExtension = require('react-native-share-extension').default;
const ResourceFetcher = require('lib/services/ResourceFetcher');
const SearchEngine = require('lib/services/SearchEngine');
const WelcomeUtils = require('lib/WelcomeUtils');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
@ -516,6 +517,8 @@ async function initialize(dispatch) {
DecryptionWorker.instance().scheduleStart();
});
await WelcomeUtils.createWelcomeItems();
reg.logger().info('Application initialized');
}

118
Tools/build-welcome.js Normal file
View File

@ -0,0 +1,118 @@
require('app-module-path').addPath(__dirname + '/../ReactNativeClient');
const fs = require('fs-extra');
const dirname = require('path').dirname;
const { fileExtension, basename } = require('lib/path-utils.js');
const markdownUtils = require('lib/markdownUtils.js');
const rootDir = dirname(__dirname);
const welcomeDir = rootDir + '/readme/welcome';
const itemMetadata_ = {
'1_welcome_to_joplin.md': {
id: '8a1556e382704160808e9a7bef7135d3',
tags: 'welcome,markdown,organizing',
},
'2_importing_and_exporting_notes.md': {
id: 'b863cbc514cb4cafbae8dd6a4fcad919',
tags: 'welcome,importing,exporting',
},
'3_synchronising_your_notes.md': {
id: '25b656aac0564d1a91ab98295aa3cc58',
tags: 'welcome,synchronizing',
},
'4_tips.md': {
id: '2ee48f80889447429a3cccb04a466072',
tags: 'welcome,attachment,search,alarm',
},
'AllClients.png': { id: '5c05172554194f95b60971f6d577cc1a' },
'SubNotebooks.png': { id: '3a851ab0c0e849b7bc9e8cd5c4feb34a' },
'folder_Welcome': { id: '9bb5d498aba74cc6a047cfdc841e82a1' },
};
function itemMetadata(path) {
const f = basename(path);
const md = itemMetadata_[f];
if (!md) throw new Error('No metadata for: ' + path);
return md;
}
function noteTags(path) {
const md = itemMetadata(path);
if (!md.tags) throw new Error('No tags for: ' + path);
return md.tags.split(',');
}
function itemIdFromPath(path) {
const md = itemMetadata(path);
if (!md.id) throw new Error('No ID for ' + path);
return md.id;
}
function fileToBase64(filePath) {
const content = fs.readFileSync(filePath);
return Buffer.from(content).toString('base64');
}
async function parseNoteFile(filePath) {
const n = basename(filePath);
const number = n.split('_')[0];
const body = fs.readFileSync(filePath, 'utf8');
const title = number + '. ' + body.split('\n')[0].substr(2);
const resources = {};
const imagePaths = markdownUtils.extractImageUrls(body);
for (let i = 0; i < imagePaths.length; i++) {
const imagePath = imagePaths[i];
const fullImagePath = welcomeDir + '/' + imagePath;
const base64 = fileToBase64(fullImagePath);
resources[imagePath] = {
id: itemIdFromPath(fullImagePath),
body: base64,
}
}
return {
id: itemIdFromPath(filePath),
title: title,
body: body,
tags: noteTags(filePath),
resources: resources,
};
}
async function main() {
const notes = [];
const filenames = fs.readdirSync(welcomeDir);
const rootFolder = {
id: itemIdFromPath('folder_Welcome'),
title: 'Welcome',
};
for (let i = 0; i < filenames.length; i++) {
const f = filenames[i];
const ext = fileExtension(f);
if (ext === 'md') {
const note = await parseNoteFile(welcomeDir + '/' + f);
note.parent_id = rootFolder.id;
notes.push(note);
}
}
const folders = [];
folders.push(rootFolder);
const content = { notes: notes, folders: folders }
const jsonContent = JSON.stringify(content, null, 4);
const jsContent = 'module.exports = ' + jsonContent;
fs.writeFileSync(rootDir + '/ReactNativeClient/lib/welcomeAssets.js', jsContent, { encoding: 'utf8' });
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -424,6 +424,8 @@
<h1 id="encryption">Encryption</h1>
<p>Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the notes, notebooks, tags or resources can read them. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the <a href="https://joplin.cozic.net/e2ee">End-To-End Encryption Tutorial</a> for more information about this feature and how to enable it.</p>
<p>For a more technical description, mostly relevant for development or to review the method being used, please see the <a href="https://joplin.cozic.net/spec">Encryption specification</a>.</p>
<h1 id="external-text-editor">External text editor</h1>
<p>Joplin notes can be opened and edited using an external editor of your choice. It can be a simple text editor like Notepad++ or Sublime Text or an actual Markdown editor like Typora. In that case, images will also be displayed within the editor. To open the note in an external editor, click on the icon in the toolbar or press Ctrl+E (or Cmd+E). Your default text editor will be used to open the note. If needed, you can also specify the editor directly in the General Options, under &quot;Text editor command&quot;.</p>
<h1 id="attachments-resources">Attachments / Resources</h1>
<p>Any kind of file can be attached to a note. In Markdown, links to these files are represented as a simple ID to the resource. In the note viewer, these files, if they are images, will be displayed or, if they are other files (PDF, text files, etc.) they will be displayed as links. Clicking on this link will open the file in the default application.</p>
<p>On the <strong>desktop application</strong>, images can be attached either by clicking on &quot;Attach file&quot; or by pasting (with Ctrl+V) an image directly in the editor, or by drag and dropping an image.</p>

View File

@ -26,7 +26,8 @@
"*.min.js",
"*.bundle.js",
"yarn.lock",
"*.icns"
"*.icns",
"*.base64",
],
"folder_exclude_patterns":
[

View File

@ -1,24 +1,24 @@
# Welcome to Joplin
# Welcome to Joplin! 🗒️
Joplin is a free, open source note taking and to-do application, which helps you write and organise your notes, and synchronise them between your devices. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](https://joplin.cozic.net/#markdown).
Joplin is a free, open source note taking and to-do application, which helps you write and organise your notes, and synchronise them between your devices. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](https://joplin.cozic.net/#markdown). Joplin is available as **💻 desktop**, **📱 mobile** and **🔡 terminal** applications.
The notes in this notebook give an overview of what Joplin can do and how to use it. In general the three applications share roughly the same functionalities, and they are not the differences will be clearly indicated.
![](./AllClients.png)
## Joplin is divided into three parts
**TODO: ADD SCREENSHOT**
Joplin has three main columns:
- **Sidebar**: It contains the list of your notebooks and tags, as well as the synchronisation status.
- **Note List**: It contains the current list of notes - either the notes in the currently selected notebook, or the notes in the currently selected tag.
- **Note Editor**: The note editor contains both an editor panel, where your write your note in Markdown, and a viewer panel, which shows the rendered note. To edit notes, you may also use an [external editor](). If you like WYSIWYG editors, you can use something like Typora as an external editor and it will display the note as well as any embedded image.
**TODO: CREATE TEXT EDITOR HELP INFO ON WEBSITE**
- **Note Editor**: The note editor contains of course an actual editor, where your write your note in Markdown, and a viewer, which shows the rendered note. To edit notes, you may also use an [external editor](https://joplin.cozic.net/#external-text-editor). For example, if you like WYSIWYG editors, you can use something like Typora as an external editor and it will display the note as well as any embedded image.
## Writing notes in Markdown
Markdown is a lightweight markup language with plain text formatting syntax. Joplin supports a [Github-flavoured Markdown syntax](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) with a few variations and additions.
In general, while Markdown is a markup language, it is readable as-is, even without being rendered. This is a simple example (you can see how it looks in the viewer panel):
In general, while Markdown is a markup language, it is meant to be human readable, even without being rendered. This is a simple example (you can see how it looks in the viewer panel):
* * *
@ -26,32 +26,44 @@ In general, while Markdown is a markup language, it is readable as-is, even with
## Sub-heading
Paragraphs are separated by a blank line. Text attributes _italic_, **bold** and `monospace` are supported.
You can create bullet list:
Paragraphs are separated by a blank line. Text attributes _italic_, **bold** and `monospace` are supported. You can create bullet lists:
* apples
* oranges
* pears
Or numbered list:
Or numbered lists:
1. wash
2. rinse
3. repeat
And this is a [link](https://joplin.cozic.net).
This is a [link](https://joplin.cozic.net) and, finally, below is an horizontal rule:
* * *
A lot more is possible including adding code samples, math formulas or checkbox lists - see the [Markdown documentation](https://joplin.cozic.net/#markdown) for more information.
A lot more is possible including adding code samples, math formulaes or checkbox lists - see the [Markdown documentation](https://joplin.cozic.net/#markdown) for more information.
## Organising your notes
Joplin notes are organised into a tree of notebooks and sub-notebooks. You can create a notebook by clicking on New Notebook, then you can drag and drop them into other notebooks to organise them as you wish.
### With notebooks 📔
IMAGE
Joplin notes are organised into a tree of notebooks and sub-notebooks.
The second way to organise your notes is using tags. To do so, right-click on any note in the Note List, and select "Edit tags". You can then add the tags, separating each one by a comma.
- On **desktop**, you can create a notebook by clicking on New Notebook, then you can drag and drop them into other notebooks to organise them as you wish.
- On **mobile**, press the "+" icon and select "New notebook".
- On **terminal**, press `:mn`
**TODO: ADD TAGS TO EACH OF THESE NOTES (eg. "welcome", "markdown", "notebook" tag)**
![](./SubNotebooks.png)
### With tags 🏷️
The second way to organise your notes is using tags:
- On **desktop**, right-click on any note in the Note List, and select "Edit tags". You can then add the tags, separating each one by a comma.
- On **mobile**, open the note and press the "⋮" button and select "Tags".
- On **terminal**, type `:help tag` for the available commands.
* * *
**Next:** [How to import notes from other applications](:/b863cbc514cb4cafbae8dd6a4fcad919).

View File

@ -1,19 +1,19 @@
# Importing and exporting notes
# Importing and exporting notes ↔️
## Importing from Evernote
Joplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, resources (attached files) and note metadata (such as author, geo-location, etc.) via ENEX files.
Joplin was designed as a replacement for Evernote and so can import complete Evernote notebooks, as well as notes, tags, images, attached files and note metadata (such as author, geo-location, etc.) via ENEX files.
To import Evernote data, first export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). Then do the following: Open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc.
To import Evernote data, first export your Evernote notebooks to ENEX files as described [here](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restore-import-notes-and-notebooks). Then, on **desktop**, do the following: Open File > Import > ENEX and select your file. The notes will be imported into a new separate notebook. If needed they can then be moved to a different notebook, or the notebook can be renamed, etc. Read [more about Evernote import](https://joplin.cozic.net/#importing-from-evernote).
[More info about Evernote import](https://joplin.cozic.net/#importing-from-evernote)
# How to import other notes
# Importing from other apps
Joplin can also import notes from [many other apps](https://github.com/laurent22/joplin#importing-from-other-applications) as well as [from Markdown or text files](https://github.com/laurent22/joplin#importing-from-markdown-files).
# How to export notes
# Exporting notes
Joplin can export to the JEX format (Joplin Export file), which is an archive that can contain multiple notes, notebooks, etc. This is a format mostly designed for backup purposes. You may also export to other formats such as plain Markdown files, as JSON or as PDF.
Joplin can export to the JEX format (Joplin Export file), which is an archive that can contain multiple notes, notebooks, etc. This is a format mostly designed for backup purposes. You may also export to other formats such as plain Markdown files, to JSON or to PDF. Find out [more about exporting notes](https://github.com/laurent22/joplin#exporting) on the official website.
[More info about exporting notes](https://github.com/laurent22/joplin#exporting)
* * *
**Next:** [How to synchronise your notes](:/25b656aac0564d1a91ab98295aa3cc58)

View File

@ -1,4 +1,4 @@
# Synchronising your notes
# Synchronising your notes 🔄
One of the goals of Joplin was to avoid being tied to any particular company or service, whether it is Evernote, Google or Microsoft. As such the synchronisation is designed without any hard dependency to any particular service. You basically choose the service you prefer among those supported, setup the configuration, and the app will be able to sync between your computers or mobile devices.
@ -20,4 +20,8 @@ OneDrive and WebDAV are also supported as synchronisation services. Please see [
## Using End-To-End Encryption
Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the data can read it. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](https://joplin.cozic.net/e2ee/) for more information about this feature and how to enable it.
Joplin supports end-to-end encryption (E2EE) on all the applications. E2EE is a system where only the owner of the data can read it. It prevents potential eavesdroppers - including telecom providers, internet providers, and even the developers of Joplin from being able to access the data. Please see the [End-To-End Encryption Tutorial](https://joplin.cozic.net/e2ee/) for more information about this feature and how to enable it.
* * *
**Next:** [Some other tips on how to use Joplin](:/2ee48f80889447429a3cccb04a466072)

View File

@ -1,4 +1,4 @@
# Tips
# Tips 💡
The first few notes should have given you an overview of the main functionalities in Joplin, but there's more it can do. See below for some of these features and how to get more help using the app:

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB