1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Desktop, Mobile: Resolves #8154: Translate Welcome notes

This commit is contained in:
Laurent Cozic 2023-05-10 12:18:59 +01:00
parent 052a829167
commit 8cedf27fea
17 changed files with 524 additions and 210 deletions

View File

@ -483,6 +483,7 @@ packages/lib/SyncTargetOneDrive.js
packages/lib/SyncTargetRegistry.js packages/lib/SyncTargetRegistry.js
packages/lib/Synchronizer.js packages/lib/Synchronizer.js
packages/lib/TaskQueue.js packages/lib/TaskQueue.js
packages/lib/WelcomeUtils.js
packages/lib/array.js packages/lib/array.js
packages/lib/callbackUrlUtils.js packages/lib/callbackUrlUtils.js
packages/lib/callbackUrlUtils.test.js packages/lib/callbackUrlUtils.test.js

1
.gitignore vendored
View File

@ -469,6 +469,7 @@ packages/lib/SyncTargetOneDrive.js
packages/lib/SyncTargetRegistry.js packages/lib/SyncTargetRegistry.js
packages/lib/Synchronizer.js packages/lib/Synchronizer.js
packages/lib/TaskQueue.js packages/lib/TaskQueue.js
packages/lib/WelcomeUtils.js
packages/lib/array.js packages/lib/array.js
packages/lib/callbackUrlUtils.js packages/lib/callbackUrlUtils.js
packages/lib/callbackUrlUtils.test.js packages/lib/callbackUrlUtils.test.js

View File

@ -27,7 +27,7 @@ import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
import ImportScreen from './ImportScreen'; import ImportScreen from './ImportScreen';
const { ResourceScreen } = require('./ResourceScreen.js'); const { ResourceScreen } = require('./ResourceScreen.js');
import Navigator from './Navigator'; import Navigator from './Navigator';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils'); import WelcomeUtils from '@joplin/lib/WelcomeUtils';
const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components'); const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components');
const bridge = require('@electron/remote').require('./bridge').default; const bridge = require('@electron/remote').require('./bridge').default;
@ -141,7 +141,7 @@ class RootComponent extends React.Component<Props, any> {
}); });
} }
await WelcomeUtils.install(this.props.dispatch); await WelcomeUtils.install(Setting.value('locale'), this.props.dispatch);
} }
private renderModalMessage(props: ModalDialogProps) { private renderModalMessage(props: ModalDialogProps) {

View File

@ -12,7 +12,7 @@
"electronRebuild": "gulp electronRebuild", "electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json", "tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json", "watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools", "start": "gulp build && electron . --env dev --log-level debug --open-dev-tools",
"test": "jest", "test": "jest",
"test-ci": "yarn test" "test-ci": "yarn test"
}, },

View File

@ -5,7 +5,7 @@
"version": "2.11.0", "version": "2.11.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "react-native start --reset-cache", "start": "BROWSERSLIST_IGNORE_OLD_DATA=true react-native start --reset-cache",
"android": "react-native run-android", "android": "react-native run-android",
"build": "gulp build", "build": "gulp build",
"tsc": "tsc --project tsconfig.json", "tsc": "tsc --project tsconfig.json",

View File

@ -76,7 +76,7 @@ const { defaultState } = require('@joplin/lib/reducer');
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local'); const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local');
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine'; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils'); import WelcomeUtils from '@joplin/lib/WelcomeUtils';
const { themeStyle } = require('./components/global-style.js'); const { themeStyle } = require('./components/global-style.js');
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js'); const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
@ -667,7 +667,7 @@ async function initialize(dispatch: Function) {
void DecryptionWorker.instance().scheduleStart(); void DecryptionWorker.instance().scheduleStart();
}); });
await WelcomeUtils.install(dispatch); await WelcomeUtils.install(Setting.value('locale'), dispatch);
// Collect revisions more frequently on mobile because it doesn't auto-save // Collect revisions more frequently on mobile because it doesn't auto-save
// and it cannot collect anything when the app is not active. // and it cannot collect anything when the app is not active.

View File

@ -1,79 +0,0 @@
const welcomeAssets = require('./welcomeAssets');
const Note = require('./models/Note').default;
const Setting = require('./models/Setting').default;
const Folder = require('./models/Folder').default;
const shim = require('./shim').default;
const uuid = require('./uuid').default;
const { fileExtension, basename } = require('./path-utils');
const { pregQuote } = require('./string-utils');
class WelcomeUtils {
static async createWelcomeItems() {
const output = {
defaultFolderId: null,
};
const folderAssets = welcomeAssets.folders;
const tempDir = Setting.value('resourceDir');
for (let i = 0; i < folderAssets.length; i++) {
const folderAsset = folderAssets[i];
const folder = await Folder.save({ title: `${folderAsset.title} (${Setting.appTypeToLabel(Setting.value('appType'))})` });
if (!output.defaultFolderId) output.defaultFolderId = folder.id;
}
const noteAssets = welcomeAssets.notes;
for (let i = noteAssets.length - 1; i >= 0; i--) {
const noteAsset = noteAssets[i];
let noteBody = noteAsset.body;
for (const resourceUrl in noteAsset.resources) {
if (!noteAsset.resources.hasOwnProperty(resourceUrl)) continue;
const resourceAsset = noteAsset.resources[resourceUrl];
const ext = fileExtension(resourceUrl);
const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`;
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
const resource = await shim.createResourceFromPath(tempFilePath, {
title: basename(resourceUrl),
});
await shim.fsDriver().remove(tempFilePath);
const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g');
noteBody = noteBody.replace(regex, `(:/${resource.id})`);
}
await Note.save({
parent_id: output.defaultFolderId,
title: noteAsset.title,
body: noteBody,
});
// if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags);
}
return output;
}
static async install(dispatch) {
if (!Setting.value('welcome.enabled')) {
Setting.setValue('welcome.wasBuilt', true);
return;
}
if (!Setting.value('welcome.wasBuilt')) {
const result = await WelcomeUtils.createWelcomeItems();
Setting.setValue('welcome.wasBuilt', true);
dispatch({
type: 'FOLDER_SELECT',
id: result.defaultFolderId,
});
Setting.setValue('activeFolderId', result.defaultFolderId);
}
}
}
module.exports = WelcomeUtils;

View File

@ -0,0 +1,122 @@
const welcomeAssetsAny = require('./welcomeAssets');
import Note from './models/Note';
import Setting from './models/Setting';
import Folder from './models/Folder';
import shim from './shim';
import uuid from './uuid';
import { fileExtension, basename } from './path-utils';
import { _ } from './locale';
const { pregQuote } = require('./string-utils');
export interface ItemMetadatum {
id: string;
}
export type ItemMetadata = Record<string, ItemMetadatum>;
export interface CreateWelcomeItemsResult {
defaultFolderId: string;
}
export interface WelcomeAssetResource {
id: string;
body: string;
}
export interface WelcomeAssetNote {
id: string;
parent_id: string;
title: string;
body: string;
resources: Record<string, WelcomeAssetResource>;
}
export interface WelcomeAssetFolder {
id: string;
title: string;
}
export interface AssetContent {
notes: WelcomeAssetNote[];
folders: WelcomeAssetFolder[];
timestamp: number;
}
export type WelcomeAssets = Record<string, AssetContent>;
class WelcomeUtils {
public static async createWelcomeItems(locale: string): Promise<CreateWelcomeItemsResult> {
const output: CreateWelcomeItemsResult = {
defaultFolderId: null,
};
const allWelcomeAssets = welcomeAssetsAny as WelcomeAssets;
const welcomeAssets = locale in allWelcomeAssets ? allWelcomeAssets[locale] : allWelcomeAssets['en_GB'];
const enGbWelcomeAssets = allWelcomeAssets['en_GB'];
const folderAssets = welcomeAssets.folders;
const tempDir = Setting.value('resourceDir');
// Actually we don't really support mutiple folders at this point, because not needed
for (let i = 0; i < folderAssets.length; i++) {
const folder = await Folder.save({ title: _('Welcome!') });
if (!output.defaultFolderId) output.defaultFolderId = folder.id;
}
const noteAssets = welcomeAssets.notes;
for (let i = noteAssets.length - 1; i >= 0; i--) {
const noteAsset = noteAssets[i];
const enGbNoteAsset = enGbWelcomeAssets.notes[i];
let noteBody = noteAsset.body;
for (const resourceUrl in enGbNoteAsset.resources) {
if (!enGbNoteAsset.resources.hasOwnProperty(resourceUrl)) continue;
const resourceAsset = enGbNoteAsset.resources[resourceUrl];
const ext = fileExtension(resourceUrl);
const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`;
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
const resource = await shim.createResourceFromPath(tempFilePath, {
title: basename(resourceUrl),
});
await shim.fsDriver().remove(tempFilePath);
const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g');
noteBody = noteBody.replace(regex, `(:/${resource.id})`);
}
await Note.save({
parent_id: output.defaultFolderId,
title: noteAsset.title,
body: noteBody,
});
// if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags);
}
return output;
}
public static async install(locale: string, dispatch: Function) {
if (!Setting.value('welcome.enabled')) {
Setting.setValue('welcome.wasBuilt', true);
return;
}
if (!Setting.value('welcome.wasBuilt')) {
const result = await WelcomeUtils.createWelcomeItems(locale);
Setting.setValue('welcome.wasBuilt', true);
dispatch({
type: 'FOLDER_SELECT',
id: result.defaultFolderId,
});
Setting.setValue('activeFolderId', result.defaultFolderId);
}
}
}
export default WelcomeUtils;

View File

@ -584,14 +584,7 @@ function localesFromLanguageCode(languageCode: string, locales: string[]): strin
} }
function _(s: string, ...args: any[]): string { function _(s: string, ...args: any[]): string {
const strings = localeStrings(currentLocale_); return stringByLocale(currentLocale_, s, ...args);
let result = strings[s];
if (result === '' || result === undefined) result = s;
try {
return sprintf(result, ...args);
} catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
}
} }
function _n(singular: string, plural: string, n: number, ...args: any[]) { function _n(singular: string, plural: string, n: number, ...args: any[]) {
@ -599,4 +592,15 @@ function _n(singular: string, plural: string, n: number, ...args: any[]) {
return _(singular, ...args); return _(singular, ...args);
} }
const stringByLocale = (locale: string, s: string, ...args: any[]): string => {
const strings = localeStrings(locale);
let result = strings[s];
if (result === '' || result === undefined) result = s;
try {
return sprintf(result, ...args);
} catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
}
};
export { _, _n, supportedLocales, currentLocale, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly }; export { _, _n, supportedLocales, currentLocale, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@ -4,7 +4,7 @@ import { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSyn
import Folder from '../../models/Folder'; import Folder from '../../models/Folder';
import Note from '../../models/Note'; import Note from '../../models/Note';
import BaseItem from '../../models/BaseItem'; import BaseItem from '../../models/BaseItem';
const WelcomeUtils = require('../../WelcomeUtils'); import WelcomeUtils from '../../WelcomeUtils';
describe('Synchronizer.basics', () => { describe('Synchronizer.basics', () => {
@ -332,12 +332,12 @@ describe('Synchronizer.basics', () => {
it('should create a new Welcome notebook on each client', (async () => { it('should create a new Welcome notebook on each client', (async () => {
// Create the Welcome items on two separate clients // Create the Welcome items on two separate clients
await WelcomeUtils.createWelcomeItems(); await WelcomeUtils.createWelcomeItems('en_GB');
await synchronizerStart(); await synchronizerStart();
await switchClient(2); await switchClient(2);
await WelcomeUtils.createWelcomeItems(); await WelcomeUtils.createWelcomeItems('en_GB');
const beforeFolderCount = (await Folder.all()).length; const beforeFolderCount = (await Folder.all()).length;
const beforeNoteCount = (await Note.all()).length; const beforeNoteCount = (await Note.all()).length;
expect(beforeFolderCount === 1).toBe(true); expect(beforeFolderCount === 1).toBe(true);

File diff suppressed because one or more lines are too long

View File

@ -2,17 +2,14 @@ import { readFileSync, readdirSync, writeFileSync } from 'fs-extra';
import { dirname } from 'path'; import { dirname } from 'path';
import { fileExtension, basename } from '@joplin/lib/path-utils'; import { fileExtension, basename } from '@joplin/lib/path-utils';
import markdownUtils from '@joplin/lib/markdownUtils'; import markdownUtils from '@joplin/lib/markdownUtils';
import { AssetContent, ItemMetadata, WelcomeAssetNote, WelcomeAssetResource, WelcomeAssets } from '@joplin/lib/WelcomeUtils';
const rootDir = dirname(dirname(__dirname)); const rootDir = dirname(dirname(__dirname));
const welcomeDir = `${rootDir}/readme/welcome`; const enWelcomeDir = `${rootDir}/readme/welcome`;
const createdDate = new Date('2018-06-22T12:00:00Z'); const createdDate = new Date('2018-06-22T12:00:00Z');
interface ItemMetadatum { const itemMetadata_: ItemMetadata = {
id: string;
}
const itemMetadata_: Record<string, ItemMetadatum> = {
'1_welcome_to_joplin.md': { '1_welcome_to_joplin.md': {
id: '8a1556e382704160808e9a7bef7135d3', id: '8a1556e382704160808e9a7bef7135d3',
}, },
@ -42,15 +39,44 @@ const itemMetadata_: Record<string, ItemMetadatum> = {
'search': { id: '83eae47427df4805905103d4a91727b7' }, 'search': { id: '83eae47427df4805905103d4a91727b7' },
}; };
function itemMetadata(path: string) { const allMetadata_: Record<string, ItemMetadata> = {};
allMetadata_['en_GB'] = itemMetadata_;
allMetadata_['fr_FR'] = {
...itemMetadata_,
'1_welcome_to_joplin.md': {
id: '223a99e0dad4c8882988f446815ea28c',
},
'2_importing_and_exporting_notes.md': {
id: '21648b1b1b541e7bb87cff262bcc6b54',
},
'3_synchronising_your_notes.md': {
id: '3adfa574c0264f68f4c33c4133e734fb',
},
'4_tips.md': {
id: '4d0ffc5beb024e6c498129ad814d156e',
},
'5_privacy.md': {
id: '69f9b160ddb50a954157716e3d916c68',
},
'folder_Welcome': { id: '5494e8c3dcfc84c1549ed22fb3a89265' },
};
const getWelcomeDir = (locale: string) => {
if (locale === 'en_GB') return enWelcomeDir;
return `${rootDir}/readme/_i18n/${locale}/welcome`;
};
function itemMetadata(metadata: ItemMetadata, path: string) {
const f = basename(path); const f = basename(path);
const md = itemMetadata_[f]; const md = metadata[f];
if (!md) throw new Error(`No metadata for: ${path}`); if (!md) throw new Error(`No metadata for: ${path}`);
return md; return md;
} }
function itemIdFromPath(path: string) { function itemIdFromPath(metadata: ItemMetadata, path: string) {
const md = itemMetadata(path); const md = itemMetadata(metadata, path);
if (!md.id) throw new Error(`No ID for ${path}`); if (!md.id) throw new Error(`No ID for ${path}`);
return md.id; return md.id;
} }
@ -60,41 +86,30 @@ function fileToBase64(filePath: string) {
return Buffer.from(content).toString('base64'); return Buffer.from(content).toString('base64');
} }
interface Resource { function parseNoteFile(metadata: ItemMetadata, locale: string, filePath: string): WelcomeAssetNote {
id: string;
body: string;
}
interface Note {
id: string;
parent_id: string;
title: string;
body: string;
resources: Record<string, Resource>;
}
function parseNoteFile(filePath: string): Note {
const n = basename(filePath); const n = basename(filePath);
const number = n.split('_')[0]; const number = n.split('_')[0];
const body = readFileSync(filePath, 'utf8'); const body = readFileSync(filePath, 'utf8');
const title = `${number}. ${body.split('\n')[0].substr(2)}`; const title = `${number}. ${body.split('\n')[0].substr(2)}`;
const resources: Record<string, Resource> = {}; const resources: Record<string, WelcomeAssetResource> = {};
const imagePaths = markdownUtils.extractImageUrls(body); if (locale === 'en_GB') {
const imagePaths = markdownUtils.extractImageUrls(body);
for (let i = 0; i < imagePaths.length; i++) { for (let i = 0; i < imagePaths.length; i++) {
const imagePath = imagePaths[i]; const imagePath = imagePaths[i];
const fullImagePath = `${welcomeDir}/${imagePath}`; const fullImagePath = `${enWelcomeDir}/${imagePath}`;
const base64 = fileToBase64(fullImagePath); const base64 = fileToBase64(fullImagePath);
resources[imagePath] = { resources[imagePath] = {
id: itemIdFromPath(fullImagePath), id: itemIdFromPath(metadata, fullImagePath),
body: base64, body: base64,
}; };
}
} }
return { return {
id: itemIdFromPath(filePath), id: itemIdFromPath(metadata, filePath),
title: title, title: title,
body: body, body: body,
resources: resources, resources: resources,
@ -103,30 +118,40 @@ function parseNoteFile(filePath: string): Note {
} }
async function main() { async function main() {
const notes = []; const supportedLocales = ['en_GB', 'fr_FR'];
const filenames = readdirSync(welcomeDir); const allContent: WelcomeAssets = {};
const rootFolder = { for (const locale of supportedLocales) {
id: itemIdFromPath('folder_Welcome'), const metadata = allMetadata_[locale];
title: 'Welcome!', const welcomeDir = getWelcomeDir(locale);
};
for (let i = 0; i < filenames.length; i++) { const notes = [];
const f = filenames[i]; const filenames = readdirSync(welcomeDir);
const ext = fileExtension(f);
if (ext === 'md') { const rootFolder = {
const note = await parseNoteFile(`${welcomeDir}/${f}`); id: itemIdFromPath(metadata, 'folder_Welcome'),
note.parent_id = rootFolder.id; title: '',
notes.push(note); };
for (let i = 0; i < filenames.length; i++) {
const f = filenames[i];
const ext = fileExtension(f);
if (ext === 'md') {
const note = await parseNoteFile(metadata, locale, `${welcomeDir}/${f}`);
note.parent_id = rootFolder.id;
notes.push(note);
}
} }
const folders = [];
folders.push(rootFolder);
const content: AssetContent = { notes: notes, folders: folders, timestamp: createdDate.getTime() };
allContent[locale] = content;
} }
const folders = []; const jsonContent = JSON.stringify(allContent, null, 4);
folders.push(rootFolder);
const content = { notes: notes, folders: folders, timestamp: createdDate.getTime() };
const jsonContent = JSON.stringify(content, null, 4);
const jsContent = `module.exports = ${jsonContent}`; const jsContent = `module.exports = ${jsonContent}`;
writeFileSync(`${rootDir}/packages/lib/welcomeAssets.js`, jsContent, { encoding: 'utf8' }); writeFileSync(`${rootDir}/packages/lib/welcomeAssets.js`, jsContent, { encoding: 'utf8' });
} }

View File

@ -0,0 +1,67 @@
# Bienvenue dans Joplin !
Joplin est une application gratuite et open source de prise de notes, qui vous aide à rédiger et à organiser vos notes, et à les synchroniser entre vos appareils. Les notes sont consultables, peuvent être copiées, étiquetées et modifiées directement depuis l'application ou depuis votre propre éditeur de texte. Les notes sont au [format Markdown](https://joplinapp.org/help/#markdown). Joplin est disponible en tant qu'application de **burean**, **mobile** et **terminal**.
Les notes de ce carnet donnent un aperçu de ce que Joplin peut faire et comment l'utiliser. En général, les trois applications partagent à peu près les mêmes fonctionnalités ; toute différence sera clairement indiquée.
![](./AllClients.png)
## Joplin est divisé en trois parties
Joplin a trois colonnes principales :
- La **barre latérale** contient la liste de vos carnets et étiquettes, ainsi que l'état de la synchronisation.
- La **liste de notes** contient la liste actuelle des notes - soit les notes du bloc-notes actuellement sélectionné, les notes de la balise actuellement sélectionnée ou les résultats de la recherche.
- L'**éditeur de notes** est là où vous écrivez vos notes. Il existe un **éditeur de texte enrichi** et un **éditeur Markdown** - cliquez sur le bouton **Basculer l'éditeur** dans le coin supérieur droit pour basculer entre les deux ! Vous pouvez également utiliser un [éditeur externe](https://joplinapp.org/help/#external-text-editor) pour modifier les notes. Par exemple, vous pouvez utiliser Typora comme éditeur externe et il affichera la note ainsi que toutes les images intégrées.
## Écrire des notes en Markdown
Markdown est un langage de balisage léger avec une syntaxe de formatage de texte brut. Joplin prend en charge une [syntaxe Markdown à saveur Github](https://joplinapp.org/markdown/) avec quelques variantes et ajouts.
En général, bien que Markdown soit un langage de balisage, il est censé être lisible directement. Ceci est un exemple simple (vous pouvez voir à quoi il ressemble dans le panneau de visualisation) :
* * *
# En-tête
## Sous-titre
Les paragraphes sont séparés par une ligne blanche. Les attributs de texte _italic_, **bold** et `monospace` sont pris en charge. Vous pouvez créer des listes à puces :
* pommes
* des oranges
* des poires
Ou des listes numérotées :
1. laver
2. rincer
3. répéter
Ceci est un [lien](https://joplinapp.org) et, enfin, ci-dessous est une règle horizontale :
* * *
Beaucoup plus est possible, y compris l'ajout d'exemples de code informatique, de formules mathématiques ou de listes de cases à cocher - voir la [documentation Markdown](https://joplinapp.org/help/#markdown) pour plus d'informations.
## Organiser vos notes
### Avec des carnets
Les notes de Joplin sont organisées en une arborescence de carnets et de sous-carnets.
- Sur l'appli de **bureau**, vous pouvez créer un carnet en cliquant sur "Nouveau carnet", puis vous pouvez les faire glisser et les déposer dans d'autres carnets pour les organiser comme vous le souhaitez.
- Sur **mobile**, appuyez sur l'icône "+" et sélectionnez "Nouveau carnet".
- Sur le **terminal**, appuyez sur `:mn`
![](./SubNotebooks.png)
### Avec des étiquettes
La deuxième façon d'organiser vos notes consiste à utiliser des étiquettes :
- Sur **bureau**, faites un clic droit sur n'importe quelle note dans la liste des notes et sélectionnez "Modifier les étiquettes". Vous pouvez ensuite ajouter les étiquettes en les séparant par des virgules.
- Sur **mobile**, ouvrez la note et appuyez sur le bouton "⋮" et sélectionnez "étiquettes".
- Sur le **terminal**, tapez `:help tag` pour les commandes disponibles.

View File

@ -0,0 +1,15 @@
# Importer et exporter des notes
## Importation depuis Evernote
Joplin peut importer des carnets complets depuis Evernote, ainsi que des notes, des étiquettes, des images, des fichiers joints et des métadonnées de note (telles que l'auteur, la géolocalisation, etc.) via des fichiers ENEX.
Pour importer des données Evernote, exportez d'abord vos carnets Evernote vers des fichiers ENEX comme décrit [ici](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restaurer-importer-notes-et-carnets). Ensuite, sur **bureau**, procédez comme suit : Ouvrez Fichier > Importer > ENEX et sélectionnez votre fichier. Les notes seront importées dans un nouveau cahier séparé. Si nécessaire, ils peuvent ensuite être déplacés vers un autre carnet, ou le carnet peut être renommé, etc. [En savoir plus sur l'importation Evernote](https://joplinapp.org/help/#importing-from-evernote).
# Importation à partir d'autres applications
Joplin peut également importer des notes depuis [de nombreuses autres applications](https://github.com/laurent22/joplin#importing-from-other-applications) ainsi que [depuis Markdown ou depuis des fichiers texte](https://github.com/laurent22/joplin#importing-from-markdown-files).
# Exporter des notes
Joplin peut exporter au format JEX (fichier d'exportation Joplin), qui est une archive pouvant contenir plusieurs notes, cahiers, etc. Il s'agit d'un format principalement conçu à des fins de sauvegarde. Vous pouvez également exporter vers d'autres formats tels que des fichiers Markdown simples, vers JSON ou vers PDF. Découvrez [l'exportation de notes](https://github.com/laurent22/joplin#exporting) sur le site officiel.

View File

@ -0,0 +1,25 @@
# Synchroniser vos notes
Joplin vous permet de synchroniser vos données à l'aide de divers services d'hébergement de fichiers. Les services cloud pris en charge sont les suivants :
## Configuration de la synchronisation Joplin Cloud
[Joplin Cloud](https://joplinapp.org/plans/) est un service Web spécialement conçu pour Joplin. Outre la synchronisation de vos données, il vous permet également de publier une note sur Internet ou de partager un carnet avec vos amis, votre famille ou vos collègues. Joplin Cloud, par rapport à d'autres services, présente également un certain nombre d'améliorations des performances permettant une synchronisation plus rapide.
Pour l'utiliser, rendez-vous dans l'écran de configuration, puis dans la rubrique Synchronisation. Dans la liste des cibles de synchronisation, sélectionnez "Joplin Cloud". Entrez votre e-mail et votre mot de passe, et vous êtes prêt à utiliser Joplin Cloud.
## Configuration de la synchronisation Dropbox
Sélectionnez "Dropbox" comme cible de synchronisation dans l'écran de configuration. Ensuite, pour lancer le processus de synchronisation, cliquez sur le bouton "Synchroniser" dans la barre latérale et suivez les instructions.
## Configuration de la synchronisation Nextcloud
Nextcloud est une solution de cloud pouvant être auto-hébergée. Pour le configurer, accédez à l'écran de configuration et sélectionnez Nextcloud comme cible de synchronisation. Saisissez ensuite l'URL WebDAV (pour l'obtenir, rendez-vous sur votre page Nextcloud, cliquez sur Paramètres dans le coin inférieur gauche de la page et copiez l'URL). Notez qu'il doit s'agir de l'**URL complète**, donc par exemple si vous voulez que les notes soient sous `/Joplin`, l'URL serait quelque chose comme `https://example.com/remote.php/webdav /Joplin` (notez que la partie "/Joplin"). Et **assurez-vous de créer le répertoire "/Joplin" dans Nextcloud**. Définissez enfin le nom d'utilisateur et le mot de passe. Si cela ne fonctionne pas, veuillez [voir cette explication](https://github.com/laurent22/joplin/issues/61#issuecomment-373282608) pour plus de détails.
## Configuration de la synchronisation OneDrive ou WebDAV
OneDrive et WebDAV sont également pris en charge en tant que services de synchronisation. Veuillez consulter [la documentation de synchronisation](https://github.com/laurent22/joplin#synchronisation) pour plus d'informations.
## Utilisation du chiffrement de bout en bout
Joplin prend en charge le chiffrement de bout en bout (E2EE) sur toutes les applications. E2EE est un système où seul le propriétaire des données peut les lire. Il empêche les espions potentiels, y compris les fournisseurs de télécommunications, les fournisseurs d'accès Internet et même les développeurs de Joplin, d'accéder aux données. Veuillez consulter le [tutoriel sur le chiffrement de bout en bout](https://joplinapp.org/e2ee/) pour plus d'informations sur cette fonctionnalité et comment l'activer.

View File

@ -0,0 +1,70 @@
# Trucs & Astuces
Les premières notes vous ont donné un aperçu des principales fonctionnalités de Joplin, mais il peut faire plus. Voir ci-dessous pour certaines de ces fonctionnalités et comment obtenir plus d'aide en utilisant l'application :
## Web Clipper
![](./WebClipper.png)
Le Web Clipper est une extension de navigateur qui vous permet d'enregistrer des pages Web et des captures d'écran à partir de votre navigateur. Pour commencer à l'utiliser, ouvrez l'application de bureau Joplin, accédez aux options du Web Clipper et suivez les instructions.
Plus d'infos sur le site officiel : https://joplinapp.org/clipper/
## Plugins
Joplin prend en charge de nombreux plugins qui vous permettent d'ajouter de nouvelles fonctionnalités à l'application, telles que des onglets, une table des matières pour vos notes, un moyen de gérer les notes préférées et bien d'autres. Pour ajouter un plugin, rendez-vous dans la section "Plugins" de l'écran de configuration. À partir de là, vous pouvez rechercher et installer des plugins, ainsi que rechercher ou mettre à jour des plugins.
## Pièces jointes
Tout type de fichier peut être joint à une note. Dans Markdown, les liens vers ces fichiers sont représentés par un ID. Dans le visualiseur de notes, ces fichiers, s'il s'agit d'images, seront affichés ou, s'il s'agit d'autres fichiers (PDF, fichiers texte, etc.), ils seront affichés sous forme de liens. Cliquer sur ce lien ouvrira le fichier dans l'application par défaut.
Les images peuvent être jointes soit en cliquant sur "Joindre un fichier", soit en collant (avec `Ctrl+V` ou `Cmd+V`) une image directement dans l'éditeur, soit en glissant-déposant une image.
Plus d'infos sur les pièces jointes : https://joplinapp.org/help/#attachments
## Recherche
Joplin prend en charge les requêtes de recherche avancées, qui sont entièrement documentées sur le site officiel : https://joplinapp.org/help/#searching
## Alarmes
Une alarme peut être associée à n'importe quelle tâche. Elle sera déclenchée à l'heure indiquée par l'affichage d'une notification. Pour utiliser cette fonctionnalité, consultez la documentation : https://joplinapp.org/help/#notifications
## Conseils avancés Markdown
Joplin utilise et rend [Github-flavored Markdown](https://joplinapp.org/markdown/) avec quelques variations et ajouts.
Par exemple, les tableaux sont pris en charge :
| Les tableaux | Sont | Cools |
| ------------- |:-------------:| -----:|
| col 3 est | alignée à droite | $1600 |
| col 2 est | centrée | $12 |
Vous pouvez également créer des listes de cases à cocher. Ces cases peuvent être cochées directement dans le visualiseur, ou en ajoutant un "x" à l'intérieur :
- [ ] Lait
- [ ] Œufs
- [x] Bière
Des expressions mathématiques peuvent être ajoutées à l'aide de la [notation KaTeX](https://khan.github.io/KaTeX/) :
$$
f(x) = \int_{-\infty}^\infty
\hat f(\xi)\,e^{2 \pi i \xi x}
\,d\xi
$$
Diverses autres astuces sont possibles, telles que l'utilisation de HTML ou la personnalisation du CSS. Voir la documentation Markdown pour plus d'informations - https://joplinapp.org/markdown/
## Communauté et aide supplémentaire
- Pour une discussion générale sur Joplin, l'assistance aux utilisateurs, les questions de développement logiciel et pour discuter des nouvelles fonctionnalités, rendez-vous sur le [Forum Joplin](https://discourse.joplinapp.org/). Il est possible de se connecter avec votre compte GitHub.
- Les dernières nouvelles sont publiées [sur la page Patreon](https://www.patreon.com/joplin).
- Pour les rapports de bugs et les demandes de fonctionnalités, accédez au [GitHub Issue Tracker](https://github.com/laurent22/joplin/issues).
## Donations
Les dons à Joplin soutiennent le développement du projet. Développer des applications de qualité prend généralement du temps, mais il y a aussi des dépenses, comme les certificats numériques pour signer les applications, les frais d'app store, l'hébergement, etc. Surtout, votre don permettra de maintenir le standard de développement actuel.
Veuillez consulter la [page de don](https://joplinapp.org/donate/) pour savoir comment soutenir le développement de Joplin.

View File

@ -0,0 +1,21 @@
# Joplin Politique de confidentialité
Les applications Joplin, y compris les applications Android, iOS, Windows, macOS et Linux, n'envoient aucune donnée à aucun service sans votre autorisation. Toutes les données enregistrées par Joplin, telles que les notes ou les images, sont enregistrées sur votre propre appareil et vous êtes libre de supprimer ces données à tout moment.
Si vous choisissez de synchroniser avec un tiers, tel que OneDrive ou Dropbox, les notes seront envoyées à ce compte, auquel cas la politique de confidentialité du tiers s'applique.
Afin de fournir certaines fonctionnalités, Joplin peut avoir besoin de se connecter à des services tiers. Vous pouvez désactiver la plupart de ces fonctionnalités dans les paramètres de l'application :
| Caractéristique | Descriptif | Par défaut | Peut être désactivé |
| -------- | -------------- | -------- | --- |
| Mise à jour automatique | Joplin se connecte périodiquement à GitHub pour vérifier les nouvelles versions. | Activé | Oui |
| Géolocalisation | Joplin enregistre les informations de géolocalisation dans les propriétés de la note lorsque vous créez une note. | Activé | Oui |
| Synchronisation | Joplin prend en charge la synchronisation de vos notes sur plusieurs appareils. Si vous choisissez de synchroniser avec un tiers, tel que OneDrive, les notes seront envoyées à votre compte OneDrive, auquel cas la politique de confidentialité du tiers s'applique. | Désactivé | Oui |
| Vérification de la connexion Wifi | Sur mobile, Joplin vérifie la connectivité Wifi pour donner la possibilité de synchroniser les données uniquement lorsque le Wifi est activé. | Activé | Non <sup>(1)</sup> |
| Dictionnaire correcteur orthographique | Sous Linux et Windows, l'application de bureau télécharge le dictionnaire du correcteur orthographique à partir de `redirector.gvt1.com`. | Activé | Oui <sup>(2)</sup> |
| Référentiel de plugins | L'application de bureau télécharge la liste des plugins disponibles depuis le [référentiel GitHub officiel](https://github.com/joplin/plugins). Si ce référentiel n'est pas accessible (par exemple en Chine), l'application essaiera d'obtenir la liste des plugins à partir de [divers miroirs](https://github.com/laurent22/joplin/blob/8ac6017c02017b6efd59f5fcab7e0b07f8d44164/packages/lib/services/plugins/RepositoryApi.ts#L22), auquel cas l'écran du plugin [fonctionne légèrement différemment](https://github.com/laurent22/joplin/issues/5161#issuecomment-925226975). | Activé | Non
<sup>(1) https://github.com/laurent22/joplin/issues/5705</sup><br/>
<sup>(2) Si le correcteur orthographique est désactivé, [il ne téléchargera pas le dictionnaire](https://discourse.joplinapp.org/t/new-version-of-joplin-contacting-google-servers-on-startup /23000/40?u=laurent).</sup>
Pour toute question concernant la politique de confidentialité de Joplin, merci de laisser un message [sur le forum](https://discourse.joplinapp.org/).