1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: Remove template feature (replaced by template plugin)

This commit is contained in:
Laurent Cozic 2021-08-06 10:58:32 +01:00
parent b0609319fd
commit e9d590169b
15 changed files with 52 additions and 222 deletions

View File

@ -267,9 +267,6 @@ packages/app-desktop/gui/MainScreen/commands/renameTag.js.map
packages/app-desktop/gui/MainScreen/commands/search.d.ts
packages/app-desktop/gui/MainScreen/commands/search.js
packages/app-desktop/gui/MainScreen/commands/search.js.map
packages/app-desktop/gui/MainScreen/commands/selectTemplate.d.ts
packages/app-desktop/gui/MainScreen/commands/selectTemplate.js
packages/app-desktop/gui/MainScreen/commands/selectTemplate.js.map
packages/app-desktop/gui/MainScreen/commands/setTags.d.ts
packages/app-desktop/gui/MainScreen/commands/setTags.js
packages/app-desktop/gui/MainScreen/commands/setTags.js.map

3
.gitignore vendored
View File

@ -252,9 +252,6 @@ packages/app-desktop/gui/MainScreen/commands/renameTag.js.map
packages/app-desktop/gui/MainScreen/commands/search.d.ts
packages/app-desktop/gui/MainScreen/commands/search.js
packages/app-desktop/gui/MainScreen/commands/search.js.map
packages/app-desktop/gui/MainScreen/commands/selectTemplate.d.ts
packages/app-desktop/gui/MainScreen/commands/selectTemplate.js
packages/app-desktop/gui/MainScreen/commands/selectTemplate.js.map
packages/app-desktop/gui/MainScreen/commands/setTags.d.ts
packages/app-desktop/gui/MainScreen/commands/setTags.js
packages/app-desktop/gui/MainScreen/commands/setTags.js.map

View File

@ -165,7 +165,6 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
- Supports multiple languages.
- External editor support - open notes in your favorite external editor with one click in Joplin.
- Extensible functionality through plugin and data APIs.
- Template support with data variables for auto creation of time & dates.
- Custom CSS support for customisation of both the rendered markdown and overall user interface.
- Customisable layout allows toggling, movement and sizing of various elements.
- Keyboard shortcuts are editable and allow binding of most Joplin commands with export/import functionality.
@ -376,29 +375,6 @@ The whole UI can be customized by placing a custom editor style file in the prof
Important: userstyle.css and userchrome.css are provided for your convenience, but they are advanced settings, and styles you define may break from one version to the next. If you want to use them, please know that it might require regular development work from you to keep them working. The Joplin team cannot make a commitment to keep the application HTML structure stable.
# Note templates
In the **desktop app**, templates can be used to create new notes or to insert into existing ones by adding a template file to the `templates` directory (File > Templates). For example creating the file `hours.md` in the `templates` directory with the contents:
```markdown
Date: {{date}}
Hours:
Details:
```
Templates can then be inserted from the menu (File->Templates).
The currently supported template variables are:
| Variable | Description | Example |
| --- | --- | --- |
| `{{date}}` | Today's date formatted based on the settings format | 2019-01-01 |
| `{{time}}` | Current time formatted based on the settings format | 13:00 |
| `{{datetime}}` | Current date and time formatted based on the settings format | 01/01/19 1:00 PM |
| `{{#custom_datetime}}` | Current date and/or time formatted based on a supplied string (using [moment.js](https://momentjs.com/) formatting) | `{{#custom_datetime}}M d{{/custom_datetime}}` |
| `{{bowm}}` | Date of the beginning of the week (when week starts on Monday) based on the settings format | |
| `{{bows}}` | Date of the beginning of the week (when week starts on Sunday) based on the settings format | |
# Plugins
The **desktop app** has the ability to extend beyond its standard functionality by the way of plugins. These plugins adhere to the Joplin plugin API and can be installed & configured within the application via the `Plugins` page in the Configuration screen. This menu allows the manual installation of the plugin using the single 'Joplin Plugin Archive' (*.jpl) file. Once the application is reloaded the plugins will appear within the plugins menu where they can be toggled on/off or removed entirely.

View File

@ -41,7 +41,6 @@ const Menu = bridge().Menu;
const PluginManager = require('@joplin/lib/services/PluginManager');
import RevisionService from '@joplin/lib/services/RevisionService';
import MigrationService from '@joplin/lib/services/MigrationService';
const TemplateUtils = require('@joplin/lib/TemplateUtils');
import { loadCustomCss, injectCustomStyles } from '@joplin/lib/CssUtils';
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
@ -62,7 +61,6 @@ const commands = [
require('./gui/MainScreen/commands/renameFolder'),
require('./gui/MainScreen/commands/renameTag'),
require('./gui/MainScreen/commands/search'),
require('./gui/MainScreen/commands/selectTemplate'),
require('./gui/MainScreen/commands/setTags'),
require('./gui/MainScreen/commands/showModalMessage'),
require('./gui/MainScreen/commands/showNoteContentProperties'),
@ -538,6 +536,26 @@ class Application extends BaseApplication {
return cssString;
}
private async checkForLegacyTemplates() {
const templatesDir = `${Setting.value('profileDir')}/templates`;
if (await shim.fsDriver().exists(templatesDir)) {
try {
const files = await shim.fsDriver().readDirStats(templatesDir);
for (const file of files) {
if (file.path.endsWith('.md')) {
// There is atleast one template.
this.store().dispatch({
type: 'CONTAINS_LEGACY_TEMPLATES',
});
break;
}
}
} catch (error) {
reg.logger().error(`Failed to read templates directory: ${error}`);
}
}
}
private async initPluginService() {
const service = PluginService.instance();
@ -617,8 +635,6 @@ class Application extends BaseApplication {
argv = await super.start(argv);
await fs.mkdirp(Setting.value('templateDir'), 0o755);
await this.applySettingsSideEffects();
if (Setting.value('sync.upgradeState') === Setting.SYNC_UPGRADE_STATE_MUST_DO) {
@ -715,18 +731,13 @@ class Application extends BaseApplication {
css: cssString,
});
const templates = await TemplateUtils.loadTemplates(Setting.value('templateDir'));
this.store().dispatch({
type: 'TEMPLATE_UPDATE_ALL',
templates: templates,
});
this.store().dispatch({
type: 'NOTE_DEVTOOLS_SET',
value: Setting.value('flagOpenDevTools'),
});
await this.checkForLegacyTemplates();
// Note: Auto-update currently doesn't work in Linux: it downloads the update
// but then doesn't install it on exit.
if (shim.isWindows() || shim.isMac()) {

View File

@ -14,8 +14,6 @@ const getLabel = (commandName: string): string => {
switch (commandName) {
case 'quit':
return _('Quit');
case 'insertTemplate':
return _('Insert template');
case 'zoomActualSize':
return _('Actual Size');
case 'gotoAnything':

View File

@ -63,6 +63,7 @@ interface Props {
showMissingMasterKeyMessage: boolean;
showNeedUpgradingMasterKeyMessage: boolean;
showShouldReencryptMessage: boolean;
showInstallTemplatesPlugin: boolean;
focusedField: string;
themeId: number;
settingEditorCodeView: boolean;
@ -70,6 +71,7 @@ interface Props {
startupPluginsLoaded: boolean;
shareInvitations: ShareInvitation[];
isSafeMode: boolean;
needApiAuth: boolean;
}
interface ShareFolderDialogOptions {
@ -123,7 +125,6 @@ const commands = [
require('./commands/renameFolder'),
require('./commands/renameTag'),
require('./commands/search'),
require('./commands/selectTemplate'),
require('./commands/setTags'),
require('./commands/showModalMessage'),
require('./commands/showNoteContentProperties'),
@ -551,6 +552,16 @@ class MainScreenComponent extends React.Component<Props, State> {
});
};
const onViewPluginScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
props: {
defaultSection: 'plugins',
},
});
};
const onRestartAndUpgrade = async () => {
Setting.setValue('sync.upgradeState', Setting.SYNC_UPGRADE_STATE_MUST_DO);
await Setting.saveAll();
@ -627,6 +638,12 @@ class MainScreenComponent extends React.Component<Props, State> {
_('Set the password'),
onViewEncryptionConfigScreen
);
} else if (this.props.showInstallTemplatesPlugin) {
msg = this.renderNotificationMessage(
'The template feature has been moved to a plugin called "Templates".',
'Install plugin',
onViewPluginScreen
);
}
return (
@ -638,7 +655,7 @@ class MainScreenComponent extends React.Component<Props, State> {
messageBoxVisible(props: Props = null) {
if (!props) props = this.props;
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props);
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin;
}
registerCommands() {
@ -855,7 +872,6 @@ const mapStateToProps = (state: AppState) => {
selectedNoteId: state.selectedNoteIds.length === 1 ? state.selectedNoteIds[0] : null,
pluginsLegacy: state.pluginsLegacy,
plugins: state.pluginService.plugins,
templates: state.templates,
customCss: state.customCss,
editorNoteStatuses: state.editorNoteStatuses,
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
@ -865,6 +881,8 @@ const mapStateToProps = (state: AppState) => {
startupPluginsLoaded: state.startupPluginsLoaded,
shareInvitations: state.shareService.shareInvitations,
isSafeMode: state.settings.isSafeMode,
needApiAuth: state.needApiAuth,
showInstallTemplatesPlugin: state.hasLegacyTemplates && !state.pluginService.plugins['joplin.plugin.templates'],
};
};

View File

@ -2,7 +2,6 @@ import { utils, CommandRuntime, CommandDeclaration, CommandContext } from '@jopl
import { _ } from '@joplin/lib/locale';
import Setting from '@joplin/lib/models/Setting';
import Note from '@joplin/lib/models/Note';
const TemplateUtils = require('@joplin/lib/TemplateUtils');
export const declaration: CommandDeclaration = {
name: 'newNote',
@ -12,12 +11,10 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, template: string = null, isTodo: boolean = false) => {
execute: async (_context: CommandContext, body: string = '', isTodo: boolean = false) => {
const folderId = Setting.value('activeFolderId');
if (!folderId) return;
const body = template ? TemplateUtils.render(template) : '';
const defaultValues = Note.previewFieldsWithDefaultValues({ includeTimestamps: false });
let newNote = Object.assign({}, defaultValues, {

View File

@ -9,8 +9,8 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, template: string = null) => {
return CommandService.instance().execute('newNote', template, true);
execute: async (_context: CommandContext, body: string = '') => {
return CommandService.instance().execute('newNote', body, true);
},
enabledCondition: 'oneFolderSelected && !inConflictFolder',
};

View File

@ -1,33 +0,0 @@
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
const TemplateUtils = require('@joplin/lib/TemplateUtils');
export const declaration: CommandDeclaration = {
name: 'selectTemplate',
};
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async (_context: CommandContext, noteType: string) => {
comp.setState({
promptOptions: {
label: _('Template file:'),
inputType: 'dropdown',
value: comp.props.templates[0], // Need to start with some value
autocomplete: comp.props.templates,
onClose: async (answer: any) => {
if (answer) {
if (noteType === 'note' || noteType === 'todo') {
void CommandService.instance().execute('newNote', answer.value, noteType === 'todo');
} else {
void CommandService.instance().execute('insertText', TemplateUtils.render(answer.value));
}
}
comp.setState({ promptOptions: null });
},
},
});
},
};
};

View File

@ -25,7 +25,6 @@ const packageInfo = require('../packageInfo.js');
const { clipboard } = require('electron');
const Menu = bridge().Menu;
const PluginManager = require('@joplin/lib/services/PluginManager');
const TemplateUtils = require('@joplin/lib/TemplateUtils');
const menuUtils = new MenuUtils(CommandService.instance());
@ -301,7 +300,6 @@ function useMenu(props: Props) {
const importItems = [];
const exportItems = [];
const templateItems: any[] = [];
const ioService = InteropService.instance();
const ioModules = ioService.modules();
for (let i = 0; i < ioModules.length; i++) {
@ -366,39 +364,6 @@ function useMenu(props: Props) {
const newSubFolderItem = menuItemDic.newSubFolder;
const printItem = menuItemDic.print;
templateItems.push({
label: _('Create note from template'),
click: () => {
void CommandService.instance().execute('selectTemplate', 'note');
},
}, {
label: _('Create to-do from template'),
click: () => {
void CommandService.instance().execute('selectTemplate', 'todo');
},
}, {
label: _('Insert template'),
accelerator: keymapService.getAccelerator('insertTemplate'),
click: () => {
void CommandService.instance().execute('selectTemplate');
},
}, {
label: _('Open template directory'),
click: () => {
void bridge().openItem(Setting.value('templateDir'));
},
}, {
label: _('Refresh templates'),
click: async () => {
const templates = await TemplateUtils.loadTemplates(Setting.value('templateDir'));
props.dispatch({
type: 'TEMPLATE_UPDATE_ALL',
templates: templates,
});
},
});
let toolsItems: any[] = [];
// we need this workaround, because on macOS the menu is different
@ -493,13 +458,6 @@ function useMenu(props: Props) {
{
type: 'separator',
visible: shim.isMac() ? false : true,
}, {
label: _('Templates'),
visible: shim.isMac() ? false : true,
submenu: templateItems,
}, {
type: 'separator',
visible: shim.isMac() ? false : true,
}, {
label: _('Import'),
visible: shim.isMac() ? false : true,
@ -555,11 +513,6 @@ function useMenu(props: Props) {
selector: 'performClose:',
}, {
type: 'separator',
}, {
label: _('Templates'),
submenu: templateItems,
}, {
type: 'separator',
}, {
label: _('Import'),
submenu: importItems,

View File

@ -677,7 +677,6 @@ export default class BaseApplication {
Setting.setConstant('env', initArgs.env);
Setting.setConstant('profileDir', profileDir);
Setting.setConstant('templateDir', `${profileDir}/templates`);
Setting.setConstant('resourceDirName', resourceDirName);
Setting.setConstant('resourceDir', resourceDir);
Setting.setConstant('tempDir', tempDir);

View File

@ -1,79 +0,0 @@
const shim = require('./shim').default;
const time = require('./time').default;
const Mustache = require('mustache');
const TemplateUtils = {};
// Mustache escapes strings (including /) with the html code by default
// This isn't useful for markdown so it's disabled
Mustache.escape = text => {
return text;
};
function beginningOfWeek(index) {
// index: 0 for Sunday, 1 for Monday
const thisDate = new Date();
const day = thisDate.getDay(),
diff = day >= index ? day - index : 6 - day;
return new Date().setDate(thisDate.getDate() - diff);
}
TemplateUtils.render = function(input) {
// new template variables can be added here
// If there are too many, this should be moved to a new file
// view needs to be set in this function so that the formats reflect settings
const view = {
date: time.formatMsToLocal(new Date().getTime(), time.dateFormat()),
time: time.formatMsToLocal(new Date().getTime(), time.timeFormat()),
datetime: time.formatMsToLocal(new Date().getTime()),
custom_datetime: () => {
return (text, render) => {
return render(time.formatMsToLocal(new Date().getTime(), text));
};
},
bowm: time.formatMsToLocal(beginningOfWeek(1), time.dateFormat()),
bows: time.formatMsToLocal(beginningOfWeek(0), time.dateFormat()),
};
return Mustache.render(input, view);
};
TemplateUtils.loadTemplates = async function(filePath) {
const templates = [];
let files = [];
if (await shim.fsDriver().exists(filePath)) {
try {
files = await shim.fsDriver().readDirStats(filePath);
} catch (error) {
let msg = error.message ? error.message : '';
msg = `Could not read template names from ${filePath}\n${msg}`;
error.message = msg;
throw error;
}
// Make sure templates are always in the same order
// sensitivity ensures that the sort will ignore case
files.sort((a, b) => { return a.path.localeCompare(b.path, undefined, { sensitivity: 'accent' }); });
for (const file of files) {
if (file.path.endsWith('.md')) {
try {
const fileString = await shim.fsDriver().readFile(`${filePath}/${file.path}`, 'utf-8');
templates.push({ label: file.path, value: fileString });
} catch (error) {
let msg = error.message ? error.message : '';
msg = `Could not load template ${file.path}\n${msg}`;
error.message = msg;
throw error;
}
}
}
}
return templates;
};
module.exports = TemplateUtils;

View File

@ -108,7 +108,6 @@ export interface Constants {
resourceDirName: string;
resourceDir: string;
profileDir: string;
templateDir: string;
tempDir: string;
pluginDataDir: string;
cacheDir: string;
@ -190,7 +189,6 @@ class Setting extends BaseModel {
resourceDirName: '',
resourceDir: '',
profileDir: '',
templateDir: '',
tempDir: '',
pluginDataDir: '',
cacheDir: '',

View File

@ -78,7 +78,7 @@ export interface State {
hasDisabledSyncItems: boolean;
hasDisabledEncryptionItems: boolean;
customCss: string;
templates: any[];
hasLegacyTemplates: boolean;
collapsedFolderIds: string[];
clipperServer: StateClipperServer;
decryptionWorker: StateDecryptionWorker;
@ -132,7 +132,7 @@ export const defaultState: State = {
hasDisabledSyncItems: false,
hasDisabledEncryptionItems: false,
customCss: '',
templates: [],
hasLegacyTemplates: false,
collapsedFolderIds: [],
clipperServer: {
startState: 'idle',
@ -1016,6 +1016,10 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
}
break;
case 'CONTAINS_LEGACY_TEMPLATES':
draft.hasLegacyTemplates = true;
break;
case 'SYNC_STARTED':
draft.syncStarted = true;
break;
@ -1120,10 +1124,6 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
draft.customCss += action.css;
break;
case 'TEMPLATE_UPDATE_ALL':
draft.templates = action.templates;
break;
case 'SET_NOTE_TAGS':
draft.selectedNoteTags = action.items;
break;

View File

@ -20,7 +20,6 @@ const defaultKeymapItems = {
{ accelerator: 'Cmd+Q', command: 'quit' },
{ accelerator: 'Cmd+,', command: 'config' },
{ accelerator: 'Cmd+W', command: 'closeWindow' },
{ accelerator: 'Option+Cmd+I', command: 'insertTemplate' },
{ accelerator: 'Cmd+C', command: 'textCopy' },
{ accelerator: 'Cmd+X', command: 'textCut' },
{ accelerator: 'Cmd+V', command: 'textPaste' },
@ -61,7 +60,6 @@ const defaultKeymapItems = {
{ accelerator: 'Ctrl+T', command: 'newTodo' },
{ accelerator: 'Ctrl+S', command: 'synchronize' },
{ accelerator: 'Ctrl+Q', command: 'quit' },
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
{ accelerator: 'Ctrl+C', command: 'textCopy' },
{ accelerator: 'Ctrl+X', command: 'textCut' },
{ accelerator: 'Ctrl+V', command: 'textPaste' },