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

Mobile: Allow enabling and disabling feature flags

This commit is contained in:
Laurent Cozic 2021-06-19 17:32:36 +01:00
parent fc132216cb
commit 5b368e39ca
10 changed files with 189 additions and 130 deletions

View File

@ -701,6 +701,9 @@ packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map
packages/app-mobile/components/SelectDateTimeDialog.d.ts
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SelectDateTimeDialog.js.map
packages/app-mobile/components/screens/ConfigScreen.d.ts
packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen.js.map
packages/app-mobile/components/screens/Note.d.ts
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Note.js.map

3
.gitignore vendored
View File

@ -687,6 +687,9 @@ packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map
packages/app-mobile/components/SelectDateTimeDialog.d.ts
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SelectDateTimeDialog.js.map
packages/app-mobile/components/screens/ConfigScreen.d.ts
packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen.js.map
packages/app-mobile/components/screens/Note.d.ts
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Note.js.map

View File

@ -4,7 +4,7 @@ import ButtonBar from './ButtonBar';
import Button, { ButtonLevel } from '../Button/Button';
import { _ } from '@joplin/lib/locale';
import bridge from '../../services/bridge';
import Setting, { SyncStartupOperation } from '@joplin/lib/models/Setting';
import Setting, { AppType, SyncStartupOperation } from '@joplin/lib/models/Setting';
import control_PluginsStates from './controls/plugins/PluginsStates';
const { connect } = require('react-redux');
@ -360,7 +360,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
const md = Setting.settingMetadata(key);
const descriptionText = Setting.keyDescription(key, 'desktop');
const descriptionText = Setting.keyDescription(key, AppType.Desktop);
const descriptionComp = this.renderDescription(this.props.themeId, descriptionText);
if (settingKeyToControl[key]) {

View File

@ -1,5 +1,4 @@
const React = require('react');
const { StyleSheet } = require('react-native');
const { themeStyle } = require('./global-style.js');

View File

@ -1,28 +1,29 @@
import Slider from '@react-native-community/slider';
const React = require('react');
const { Platform, TouchableOpacity, Linking, View, Switch, StyleSheet, Text, Button, ScrollView, TextInput, Alert, PermissionsAndroid } = require('react-native');
const { Platform, Linking, View, Switch, StyleSheet, ScrollView, Text, Button, TouchableOpacity, TextInput, Alert, PermissionsAndroid } = require('react-native');
import Setting, { AppType } from '@joplin/lib/models/Setting';
import NavService from '@joplin/lib/services/NavService';
import ReportService from '@joplin/lib/services/ReportService';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import checkPermissions from '../../utils/checkPermissions';
import time from '@joplin/lib/time';
import shim from '@joplin/lib/shim';
import setIgnoreTlsErrors from '../../utils/TlsUtils';
import { reg } from '@joplin/lib/registry';
import { State } from '@joplin/lib/reducer';
const VersionInfo = require('react-native-version-info');
const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js');
const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js');
const { Dropdown } = require('../Dropdown.js');
const { themeStyle } = require('../global-style.js');
const Setting = require('@joplin/lib/models/Setting').default;
const shared = require('@joplin/lib/components/shared/config-shared.js');
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
const { reg } = require('@joplin/lib/registry.js');
const NavService = require('@joplin/lib/services/NavService').default;
const VersionInfo = require('react-native-version-info').default;
const ReportService = require('@joplin/lib/services/ReportService').default;
const time = require('@joplin/lib/time').default;
const shim = require('@joplin/lib/shim').default;
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine').default;
const RNFS = require('react-native-fs');
const checkPermissions = require('../../utils/checkPermissions.js').default;
import setIgnoreTlsErrors from '../../utils/TlsUtils';
class ConfigScreenComponent extends BaseScreenComponent {
static navigationOptions() {
static navigationOptions(): any {
return { header: null };
}
@ -49,7 +50,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
this.e2eeConfig_ = () => {
NavService.go('EncryptionConfig');
void NavService.go('EncryptionConfig');
};
this.saveButton_press = async () => {
@ -69,7 +70,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
this.syncStatusButtonPress_ = () => {
NavService.go('Status');
void NavService.go('Status');
};
this.exportDebugButtonPress_ = async () => {
@ -120,7 +121,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
throw new Error('Permission denied');
}
const copyFiles = async (source, dest) => {
const copyFiles = async (source: string, dest: string) => {
await shim.fsDriver().mkdir(dest);
const files = await shim.fsDriver().readDirStats(source);
@ -148,7 +149,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
this.logButtonPress_ = () => {
NavService.go('Log');
void NavService.go('Log');
};
}
@ -175,7 +176,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
if (this.styles_[themeId]) return this.styles_[themeId];
this.styles_ = {};
const styles = {
const styles: any = {
body: {
flex: 1,
justifyContent: 'flex-start',
@ -263,7 +264,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
return this.styles_[themeId];
}
renderHeader(key, title) {
renderHeader(key: string, title: string) {
const theme = themeStyle(this.props.themeId);
return (
<View key={key} style={this.styles().headerWrapperStyle}>
@ -272,7 +273,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
renderButton(key, title, clickHandler, options = null) {
renderButton(key: string, title: string, clickHandler: Function, options: any = null) {
if (!options) options = {};
let descriptionComp = null;
@ -297,7 +298,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
sectionToComponent(key, section, settings) {
sectionToComponent(key: string, section: any, settings: any) {
const settingComps = [];
for (let i = 0; i < section.metadatas.length; i++) {
@ -341,12 +342,32 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
settingToComponent(key, value) {
private renderToggle(key: string, label: string, value: any, updateSettingValue: Function, descriptionComp: any = null) {
const theme = themeStyle(this.props.themeId);
return (
<View key={key}>
<View style={this.containerStyle(false)}>
<Text key="label" style={this.styles().switchSettingText}>
{label}
</Text>
<Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={(value: any) => updateSettingValue(key, value)} />
</View>
{descriptionComp}
</View>
);
}
private containerStyle(hasDescription: boolean): any {
return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
}
settingToComponent(key: string, value: any) {
const themeId = this.props.themeId;
const theme = themeStyle(themeId);
const output = null;
const output: any = null;
const updateSettingValue = (key, value) => {
const updateSettingValue = (key: string, value: any) => {
return shared.updateSettingValue(this, key, value);
};
@ -354,7 +375,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
const settingDescription = md.description ? md.description() : '';
const descriptionComp = !settingDescription ? null : <Text style={this.styles().settingDescriptionText}>{settingDescription}</Text>;
const containerStyle = !settingDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
const containerStyle = this.containerStyle(!!settingDescription);
if (md.isEnum) {
value = value.toString();
@ -388,7 +409,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
color: theme.color,
fontSize: theme.fontSize,
}}
onValueChange={(itemValue) => {
onValueChange={(itemValue: any) => {
updateSettingValue(key, itemValue);
}}
/>
@ -397,17 +418,18 @@ class ConfigScreenComponent extends BaseScreenComponent {
</View>
);
} else if (md.type == Setting.TYPE_BOOL) {
return (
<View key={key}>
<View style={containerStyle}>
<Text key="label" style={this.styles().switchSettingText}>
{md.label()}
</Text>
<Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={value => updateSettingValue(key, value)} />
</View>
{descriptionComp}
</View>
);
return this.renderToggle(key, md.label(), value, updateSettingValue, descriptionComp);
// return (
// <View key={key}>
// <View style={containerStyle}>
// <Text key="label" style={this.styles().switchSettingText}>
// {md.label()}
// </Text>
// <Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={(value:any) => updateSettingValue(key, value)} />
// </View>
// {descriptionComp}
// </View>
// );
} else if (md.type == Setting.TYPE_INT) {
const unitLabel = md.unitLabel ? md.unitLabel(value) : value;
// Note: Do NOT add the minimumTrackTintColor and maximumTrackTintColor props
@ -431,7 +453,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
<Text key="label" style={this.styles().settingText}>
{md.label()}
</Text>
<TextInput autoCorrect={false} autoCompleteType="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={value => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
<TextInput autoCorrect={false} autoCompleteType="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={(value: any) => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
</View>
);
} else {
@ -441,6 +463,19 @@ class ConfigScreenComponent extends BaseScreenComponent {
return output;
}
private renderFeatureFlags(settings: any): any[] {
const updateSettingValue = (key: string, value: any) => {
console.info('UPDATE', key, value);
return shared.updateSettingValue(this, key, value);
};
const output: any[] = [];
for (const key of Setting.featureFlagKeys(AppType.Mobile)) {
output.push(this.renderToggle(key, key, settings[key], updateSettingValue));
}
return output;
}
render() {
const settings = this.state.settings;
@ -464,7 +499,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
const profileExportPrompt = (
<View style={this.styles().settingContainer} key="profileExport">
<Text style={this.styles().settingText}>Path:</Text>
<TextInput style={{ ...this.styles().textInput, paddingRight: 20 }} onChange={(event) => this.setState({ profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard" keyboardAppearance={theme.keyboardAppearance}></TextInput>
<TextInput style={{ ...this.styles().textInput, paddingRight: 20 }} onChange={(event: any) => this.setState({ profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard" keyboardAppearance={theme.keyboardAppearance}></TextInput>
<Button title="OK" onPress={this.exportProfileButtonPress2_}></Button>
</View>
);
@ -473,6 +508,9 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
}
settingComps.push(this.renderHeader('featureFlags', _('Feature flags')));
settingComps.push(<View key="featureFlagsContainer">{this.renderFeatureFlags(settings)}</View>);
settingComps.push(this.renderHeader('moreInfo', _('More information')));
if (Platform.OS === 'android' && Platform.Version >= 23) {
@ -568,11 +606,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
}
const ConfigScreen = connect(state => {
const ConfigScreen = connect((state: State) => {
return {
settings: state.settings,
themeId: state.settings.theme,
};
})(ConfigScreenComponent);
module.exports = { ConfigScreen };
export default ConfigScreen;

View File

@ -604,4 +604,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 6f558aa823c0fd07348253cc01996e4ee3de75c4
COCOAPODS: 1.8.4
COCOAPODS: 1.10.1

View File

@ -57,7 +57,7 @@ import JoplinDatabase from '@joplin/lib/JoplinDatabase';
import Database from '@joplin/lib/database';
const { NotesScreen } = require('./components/screens/notes.js');
const { TagsScreen } = require('./components/screens/tags.js');
const { ConfigScreen } = require('./components/screens/config.js');
import ConfigScreen from './components/screens/ConfigScreen';
const { FolderScreen } = require('./components/screens/folder.js');
const { LogScreen } = require('./components/screens/log.js');
const { StatusScreen } = require('./components/screens/status.js');

View File

@ -42,7 +42,7 @@ export interface SettingItem {
label?(): string;
description?: Function;
options?(): any;
appTypes?: string[];
appTypes?: AppType[];
show?(settings: any): boolean;
filter?(value: any): any;
secure?: boolean;
@ -93,12 +93,18 @@ export enum Env {
Prod = 'prod',
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
Cli = 'cli',
}
export interface Constants {
env: Env;
isDemo: boolean;
appName: string;
appId: string;
appType: string;
appType: AppType;
resourceDirName: string;
resourceDir: string;
profileDir: string;
@ -180,7 +186,7 @@ class Setting extends BaseModel {
isDemo: false,
appName: 'joplin',
appId: 'SET_ME', // Each app should set this identifier
appType: 'SET_ME', // 'cli' or 'mobile'
appType: 'SET_ME' as any, // 'cli' or 'mobile'
resourceDirName: '',
resourceDir: '',
profileDir: '',
@ -290,7 +296,7 @@ class Setting extends BaseModel {
value: true,
type: SettingItemType.Bool,
public: false,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
storage: SettingStorage.File,
},
'sync.target': {
@ -300,7 +306,7 @@ class Setting extends BaseModel {
public: true,
section: 'sync',
label: () => _('Synchronisation target'),
description: (appType: string) => {
description: (appType: AppType) => {
return appType !== 'cli' ? null : _('The target to synchronise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).');
},
options: () => {
@ -553,7 +559,7 @@ class Setting extends BaseModel {
public: true,
advanced: true,
isEnum: true,
appTypes: ['mobile', 'desktop'],
appTypes: [AppType.Mobile, AppType.Desktop],
label: () => _('Attachment download behaviour'),
description: () => _('In "Manual" mode, attachments are downloaded only when you click on them. In "Auto", they are downloaded when you open the note. In "Always", all the attachments are downloaded whether you open the note or not.'),
options: () => {
@ -645,7 +651,7 @@ class Setting extends BaseModel {
value: Setting.THEME_LIGHT,
type: SettingItemType.Int,
public: true,
appTypes: ['mobile', 'desktop'],
appTypes: [AppType.Mobile, AppType.Desktop],
show: (settings) => {
return !settings['themeAutoDetect'];
},
@ -660,7 +666,7 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
section: 'appearance',
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
public: true,
label: () => _('Automatically switch theme to match system theme'),
storage: SettingStorage.File,
@ -673,7 +679,7 @@ class Setting extends BaseModel {
show: (settings) => {
return settings['themeAutoDetect'];
},
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
isEnum: true,
label: () => _('Preferred light theme'),
section: 'appearance',
@ -688,7 +694,7 @@ class Setting extends BaseModel {
show: (settings) => {
return settings['themeAutoDetect'];
},
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
isEnum: true,
label: () => _('Preferred dark theme'),
section: 'appearance',
@ -702,13 +708,13 @@ class Setting extends BaseModel {
public: false,
},
showNoteCounts: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, public: false, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') },
showNoteCounts: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, public: false, advanced: true, appTypes: [AppType.Desktop], label: () => _('Show note counts') },
layoutButtonSequence: {
value: Setting.LAYOUT_ALL,
type: SettingItemType.Int,
public: false,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
isEnum: true,
options: () => ({
[Setting.LAYOUT_ALL]: _('%s / %s / %s', _('Editor'), _('Viewer'), _('Split View')),
@ -718,15 +724,15 @@ class Setting extends BaseModel {
}),
storage: SettingStorage.File,
},
uncompletedTodosOnTop: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: ['cli'], label: () => _('Uncompleted to-dos on top') },
showCompletedTodos: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: ['cli'], label: () => _('Show completed to-dos') },
uncompletedTodosOnTop: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: [AppType.Cli], label: () => _('Uncompleted to-dos on top') },
showCompletedTodos: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: [AppType.Cli], label: () => _('Show completed to-dos') },
'notes.sortOrder.field': {
value: 'user_updated_time',
type: SettingItemType.String,
section: 'note',
isEnum: true,
public: true,
appTypes: ['cli'],
appTypes: [AppType.Cli],
label: () => _('Sort notes by'),
options: () => {
const Note = require('./Note').default;
@ -744,17 +750,17 @@ class Setting extends BaseModel {
type: SettingItemType.Bool,
public: true,
section: 'note',
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('Auto-pair braces, parenthesis, quotations, etc.'),
storage: SettingStorage.File,
},
'notes.sortOrder.reverse': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
'notes.sortOrder.reverse': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, label: () => _('Reverse sort order'), appTypes: [AppType.Cli] },
'folders.sortOrder.field': {
value: 'title',
type: SettingItemType.String,
isEnum: true,
public: true,
appTypes: ['cli'],
appTypes: [AppType.Cli],
label: () => _('Sort notebooks by'),
options: () => {
const Folder = require('./Folder').default;
@ -767,7 +773,7 @@ class Setting extends BaseModel {
},
storage: SettingStorage.File,
},
'folders.sortOrder.reverse': { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
'folders.sortOrder.reverse': { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, public: true, label: () => _('Reverse sort order'), appTypes: [AppType.Cli] },
trackLocation: { value: true, type: SettingItemType.Bool, section: 'note', storage: SettingStorage.File, public: true, label: () => _('Save geo-location with notes') },
// 2020-10-29: For now disable the beta editor due to
@ -780,7 +786,7 @@ class Setting extends BaseModel {
type: SettingItemType.Bool,
section: 'note',
public: false, // mobilePlatform === 'ios',
appTypes: ['mobile'],
appTypes: [AppType.Mobile],
label: () => 'Opt-in to the editor beta',
description: () => 'This beta adds list continuation, Markdown preview, and Markdown shortcuts. If you find bugs, please report them in the Discourse forum.',
},
@ -791,7 +797,7 @@ class Setting extends BaseModel {
section: 'note',
isEnum: true,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('When creating a new to-do:'),
options: () => {
return {
@ -807,7 +813,7 @@ class Setting extends BaseModel {
section: 'note',
isEnum: true,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('When creating a new note:'),
options: () => {
return {
@ -823,7 +829,7 @@ class Setting extends BaseModel {
type: SettingItemType.Object,
section: 'plugins',
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
needRestart: true,
autoSave: true,
},
@ -834,38 +840,38 @@ class Setting extends BaseModel {
section: 'plugins',
public: true,
advanced: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => 'Development plugins',
description: () => 'You may add multiple plugin paths, each separated by a comma. You will need to restart the application for the changes to take effect.',
storage: SettingStorage.File,
},
// Deprecated - use markdown.plugin.*
'markdown.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: [AppType.Mobile, AppType.Desktop] },
'markdown.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: [AppType.Mobile, AppType.Desktop] },
// Deprecated
'markdown.plugin.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.linkify': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Linkify')}${wysiwygYes}` },
'markdown.plugin.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.linkify': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable Linkify')}${wysiwygYes}` },
'markdown.plugin.katex': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
'markdown.plugin.mermaid': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Mermaid diagrams support')}${wysiwygYes}` },
'markdown.plugin.katex': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
'markdown.plugin.mermaid': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable Mermaid diagrams support')}${wysiwygYes}` },
'markdown.plugin.audioPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable audio player')}${wysiwygNo}` },
'markdown.plugin.videoPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable video player')}${wysiwygNo}` },
'markdown.plugin.pdfViewer': { storage: SettingStorage.File, value: !mobilePlatform, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['desktop'], label: () => `${_('Enable PDF viewer')}${wysiwygNo}` },
'markdown.plugin.mark': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ==mark== syntax')}${wysiwygYes}` },
'markdown.plugin.footnote': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable footnotes')}${wysiwygNo}` },
'markdown.plugin.toc': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable table of contents extension')}${wysiwygNo}` },
'markdown.plugin.sub': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ~sub~ syntax')}${wysiwygYes}` },
'markdown.plugin.sup': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ^sup^ syntax')}${wysiwygYes}` },
'markdown.plugin.deflist': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable deflist syntax')}${wysiwygNo}` },
'markdown.plugin.abbr': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable abbreviation syntax')}${wysiwygNo}` },
'markdown.plugin.emoji': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable markdown emoji')}${wysiwygNo}` },
'markdown.plugin.insert': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ++insert++ syntax')}${wysiwygYes}` },
'markdown.plugin.multitable': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable multimarkdown table extension')}${wysiwygNo}` },
'markdown.plugin.audioPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable audio player')}${wysiwygNo}` },
'markdown.plugin.videoPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable video player')}${wysiwygNo}` },
'markdown.plugin.pdfViewer': { storage: SettingStorage.File, value: !mobilePlatform, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Desktop], label: () => `${_('Enable PDF viewer')}${wysiwygNo}` },
'markdown.plugin.mark': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ==mark== syntax')}${wysiwygYes}` },
'markdown.plugin.footnote': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable footnotes')}${wysiwygNo}` },
'markdown.plugin.toc': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable table of contents extension')}${wysiwygNo}` },
'markdown.plugin.sub': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ~sub~ syntax')}${wysiwygYes}` },
'markdown.plugin.sup': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ^sup^ syntax')}${wysiwygYes}` },
'markdown.plugin.deflist': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable deflist syntax')}${wysiwygNo}` },
'markdown.plugin.abbr': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable abbreviation syntax')}${wysiwygNo}` },
'markdown.plugin.emoji': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable markdown emoji')}${wysiwygNo}` },
'markdown.plugin.insert': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ++insert++ syntax')}${wysiwygYes}` },
'markdown.plugin.multitable': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable multimarkdown table extension')}${wysiwygNo}` },
// Tray icon (called AppIndicator) doesn't work in Ubuntu
// http://www.webupd8.org/2017/04/fix-appindicator-not-working-for.html
@ -876,7 +882,7 @@ class Setting extends BaseModel {
type: SettingItemType.Bool,
section: 'application',
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('Show tray icon'),
description: () => {
return platform === 'linux' ? _('Note: Does not work in all desktop environments.') : _('This will allow Joplin to run in the background. It is recommended to enable this setting so that your notes are constantly being synchronised, thus reducing the number of conflicts.');
@ -884,7 +890,7 @@ class Setting extends BaseModel {
storage: SettingStorage.File,
},
startMinimized: { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Start application minimised in the tray icon') },
startMinimized: { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: true, appTypes: [AppType.Desktop], label: () => _('Start application minimised in the tray icon') },
collapsedFolderIds: { value: [], type: SettingItemType.Array, public: false },
@ -907,9 +913,9 @@ class Setting extends BaseModel {
},
// Deprecated in favour of windowContentZoomFactor
'style.zoom': { value: 100, type: SettingItemType.Int, public: false, storage: SettingStorage.File, appTypes: ['desktop'], section: 'appearance', label: () => '', minimum: 50, maximum: 500, step: 10 },
'style.zoom': { value: 100, type: SettingItemType.Int, public: false, storage: SettingStorage.File, appTypes: [AppType.Desktop], section: 'appearance', label: () => '', minimum: 50, maximum: 500, step: 10 },
'style.editor.fontSize': { value: 13, type: SettingItemType.Int, public: true, storage: SettingStorage.File, appTypes: ['desktop'], section: 'appearance', label: () => _('Editor font size'), minimum: 4, maximum: 50, step: 1 },
'style.editor.fontSize': { value: 13, type: SettingItemType.Int, public: true, storage: SettingStorage.File, appTypes: [AppType.Desktop], section: 'appearance', label: () => _('Editor font size'), minimum: 4, maximum: 50, step: 1 },
'style.editor.fontFamily':
(mobilePlatform) ?
({
@ -918,7 +924,7 @@ class Setting extends BaseModel {
isEnum: true,
public: true,
label: () => _('Editor font'),
appTypes: ['mobile'],
appTypes: [AppType.Mobile],
section: 'appearance',
options: () => {
// IMPORTANT: The font mapping must match the one in global-styles.js::editorFont()
@ -940,7 +946,7 @@ class Setting extends BaseModel {
value: '',
type: SettingItemType.String,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
section: 'appearance',
label: () => _('Editor font family'),
description: () =>
@ -951,7 +957,7 @@ class Setting extends BaseModel {
value: '',
type: SettingItemType.String,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
section: 'appearance',
label: () => _('Editor monospace font family'),
description: () =>
@ -959,7 +965,7 @@ class Setting extends BaseModel {
storage: SettingStorage.File,
},
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, public: false, appTypes: ['desktop'] },
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, public: false, appTypes: [AppType.Desktop] },
// TODO: Is there a better way to do this? The goal here is to simply have
// a way to display a link to the customizable stylesheets, not for it to
@ -978,7 +984,7 @@ class Setting extends BaseModel {
},
type: SettingItemType.Button,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('Custom stylesheet for rendered Markdown'),
section: 'appearance',
advanced: true,
@ -995,7 +1001,7 @@ class Setting extends BaseModel {
},
type: SettingItemType.Button,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('Custom stylesheet for Joplin-wide app styles'),
section: 'appearance',
advanced: true,
@ -1006,7 +1012,7 @@ class Setting extends BaseModel {
value: null,
type: SettingItemType.Button,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('Re-upload local data to sync target'),
section: 'sync',
advanced: true,
@ -1017,7 +1023,7 @@ class Setting extends BaseModel {
value: null,
type: SettingItemType.Button,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => _('Delete local data and re-download from sync target'),
section: 'sync',
advanced: true,
@ -1025,8 +1031,8 @@ class Setting extends BaseModel {
},
autoUpdateEnabled: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: platform !== 'linux', appTypes: ['desktop'], label: () => _('Automatically update the application') },
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', storage: SettingStorage.File, public: true, appTypes: ['desktop'], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
autoUpdateEnabled: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: platform !== 'linux', appTypes: [AppType.Desktop], label: () => _('Automatically update the application') },
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', storage: SettingStorage.File, public: true, appTypes: [AppType.Desktop], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
'clipperServer.autoStart': { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, public: false },
'sync.interval': {
value: 300,
@ -1055,13 +1061,13 @@ class Setting extends BaseModel {
public: true,
label: () => _('Synchronise only over WiFi connection'),
storage: SettingStorage.File,
appTypes: ['mobile'],
appTypes: [AppType.Mobile],
},
noteVisiblePanes: { value: ['editor', 'viewer'], type: SettingItemType.Array, storage: SettingStorage.File, public: false, appTypes: ['desktop'] },
tagHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: ['desktop'] },
folderHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: ['desktop'] },
editor: { value: '', type: SettingItemType.String, subType: 'file_path_and_args', storage: SettingStorage.File, public: true, appTypes: ['cli', 'desktop'], label: () => _('Text editor command'), description: () => _('The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.') },
'export.pdfPageSize': { value: 'A4', type: SettingItemType.String, advanced: true, storage: SettingStorage.File, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('Page size for PDF export'), options: () => {
noteVisiblePanes: { value: ['editor', 'viewer'], type: SettingItemType.Array, storage: SettingStorage.File, public: false, appTypes: [AppType.Desktop] },
tagHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: [AppType.Desktop] },
folderHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: [AppType.Desktop] },
editor: { value: '', type: SettingItemType.String, subType: 'file_path_and_args', storage: SettingStorage.File, public: true, appTypes: [AppType.Cli, AppType.Desktop], label: () => _('Text editor command'), description: () => _('The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.') },
'export.pdfPageSize': { value: 'A4', type: SettingItemType.String, advanced: true, storage: SettingStorage.File, isEnum: true, public: true, appTypes: [AppType.Desktop], label: () => _('Page size for PDF export'), options: () => {
return {
'A4': _('A4'),
'Letter': _('Letter'),
@ -1071,7 +1077,7 @@ class Setting extends BaseModel {
'Legal': _('Legal'),
};
} },
'export.pdfPageOrientation': { value: 'portrait', type: SettingItemType.String, storage: SettingStorage.File, advanced: true, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('Page orientation for PDF export'), options: () => {
'export.pdfPageOrientation': { value: 'portrait', type: SettingItemType.String, storage: SettingStorage.File, advanced: true, isEnum: true, public: true, appTypes: [AppType.Desktop], label: () => _('Page orientation for PDF export'), options: () => {
return {
'portrait': _('Portrait'),
'landscape': _('Landscape'),
@ -1082,7 +1088,7 @@ class Setting extends BaseModel {
value: '',
type: SettingItemType.String,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
isEnum: true,
advanced: true,
label: () => _('Keyboard Mode'),
@ -1100,7 +1106,7 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
public: true,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
label: () => 'Enable spell checking in Markdown editor? (WARNING BETA feature)',
description: () => 'Spell checker in the Markdown editor was previously unstable (cursor location was not stable, sometimes edits would not be saved or reflected in the viewer, etc.) however it appears to be more reliable now. If you notice any issue, please report it on GitHub or the Joplin Forum (Help -> Joplin Forum)',
},
@ -1114,7 +1120,7 @@ class Setting extends BaseModel {
return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav'), SyncTargetRegistry.nameToId('joplinServer')].indexOf(settings['sync.target']) >= 0;
},
public: true,
appTypes: ['desktop', 'cli'],
appTypes: [AppType.Desktop, AppType.Cli],
label: () => _('Custom TLS certificates'),
description: () => _('Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on "Check synchronisation configuration".'),
storage: SettingStorage.File,
@ -1129,7 +1135,6 @@ class Setting extends BaseModel {
[SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav'), SyncTargetRegistry.nameToId('joplinServer')].indexOf(settings['sync.target']) >= 0;
},
public: true,
appTypes: ['desktop', 'cli', 'mobile'],
label: () => _('Ignore TLS certificate errors'),
storage: SettingStorage.File,
},
@ -1146,7 +1151,7 @@ class Setting extends BaseModel {
},
'api.token': { value: null, type: SettingItemType.String, public: false, storage: SettingStorage.File },
'api.port': { value: null, type: SettingItemType.Int, storage: SettingStorage.File, public: true, appTypes: ['cli'], description: () => _('Specify the port that should be used by the API server. If not set, a default will be used.') },
'api.port': { value: null, type: SettingItemType.Int, storage: SettingStorage.File, public: true, appTypes: [AppType.Cli], description: () => _('Specify the port that should be used by the API server. If not set, a default will be used.') },
'resourceService.lastProcessedChangeId': { value: 0, type: SettingItemType.Int, public: false },
'searchEngine.lastProcessedChangeId': { value: 0, type: SettingItemType.Int, public: false },
@ -1175,8 +1180,8 @@ class Setting extends BaseModel {
'welcome.wasBuilt': { value: false, type: SettingItemType.Bool, public: false },
'welcome.enabled': { value: true, type: SettingItemType.Bool, public: false },
'camera.type': { value: 0, type: SettingItemType.Int, public: false, appTypes: ['mobile'] },
'camera.ratio': { value: '4:3', type: SettingItemType.String, public: false, appTypes: ['mobile'] },
'camera.type': { value: 0, type: SettingItemType.Int, public: false, appTypes: [AppType.Mobile] },
'camera.ratio': { value: '4:3', type: SettingItemType.String, public: false, appTypes: [AppType.Mobile] },
'spellChecker.enabled': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, public: false },
'spellChecker.language': { value: '', type: SettingItemType.String, storage: SettingStorage.File, public: false },
@ -1185,7 +1190,7 @@ class Setting extends BaseModel {
value: 100,
type: SettingItemType.Int,
public: false,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
minimum: 30,
maximum: 300,
step: 10,
@ -1197,7 +1202,7 @@ class Setting extends BaseModel {
type: SettingItemType.Int,
section: 'appearance',
public: true,
appTypes: ['cli'],
appTypes: [AppType.Cli],
label: () => _('Notebook list growth factor'),
description: () =>
_('The factor property sets how the item will grow or shrink ' +
@ -1211,7 +1216,7 @@ class Setting extends BaseModel {
type: SettingItemType.Int,
section: 'appearance',
public: true,
appTypes: ['cli'],
appTypes: [AppType.Cli],
label: () => _('Note list growth factor'),
description: () =>
_('The factor property sets how the item will grow or shrink ' +
@ -1225,7 +1230,7 @@ class Setting extends BaseModel {
type: SettingItemType.Int,
section: 'appearance',
public: true,
appTypes: ['cli'],
appTypes: [AppType.Cli],
label: () => _('Note area growth factor'),
description: () =>
_('The factor property sets how the item will grow or shrink ' +
@ -1239,7 +1244,7 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
public: false,
appTypes: ['desktop'],
appTypes: [AppType.Desktop],
storage: SettingStorage.Database,
},
@ -1264,6 +1269,11 @@ class Setting extends BaseModel {
return this.metadata_;
}
public static featureFlagKeys(appType: AppType): string[] {
const keys = this.keys(false, appType);
return keys.filter(k => k.indexOf('featureFlag.') === 0);
}
private static validateKey(key: string) {
if (!key) throw new Error('Cannot register empty key');
if (key.length > 128) throw new Error(`Key length cannot be longer than 128 characters: ${key}`);
@ -1319,7 +1329,7 @@ class Setting extends BaseModel {
return key in this.metadata();
}
static keyDescription(key: string, appType: string = null) {
static keyDescription(key: string, appType: AppType = null) {
const md = this.settingMetadata(key);
if (!md.description) return null;
return md.description(appType);
@ -1329,7 +1339,7 @@ class Setting extends BaseModel {
return this.metadata()[key] && this.metadata()[key].secure === true;
}
static keys(publicOnly: boolean = false, appType: string = null, options: KeysOptions = null) {
static keys(publicOnly: boolean = false, appType: AppType = null, options: KeysOptions = null) {
options = Object.assign({}, {
secureOnly: false,
}, options);
@ -1827,7 +1837,7 @@ class Setting extends BaseModel {
this.saveTimeoutId_ = null;
}
static publicSettings(appType: string) {
static publicSettings(appType: AppType) {
if (!appType) throw new Error('appType is required');
const metadata = this.metadata();

View File

@ -3,7 +3,7 @@ export default class NavService {
public static dispatch: Function = () => {};
private static handlers_: Function[] = [];
static async go(routeName: string) {
public static async go(routeName: string) {
if (this.handlers_.length) {
const r = await this.handlers_[this.handlers_.length - 1]();
if (r) return r;
@ -15,7 +15,7 @@ export default class NavService {
});
}
static addHandler(handler: Function) {
public static addHandler(handler: Function) {
for (let i = this.handlers_.length - 1; i >= 0; i--) {
const h = this.handlers_[i];
if (h === handler) return;
@ -24,7 +24,7 @@ export default class NavService {
this.handlers_.push(handler);
}
static removeHandler(hanlder: Function) {
public static removeHandler(hanlder: Function) {
for (let i = this.handlers_.length - 1; i >= 0; i--) {
const h = this.handlers_[i];
if (h === hanlder) this.handlers_.splice(i, 1);

View File

@ -334,6 +334,12 @@ export enum SettingItemType {
Button = 6,
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
Cli = 'cli',
}
export enum SettingStorage {
Database = 1,
File = 2,
@ -377,7 +383,7 @@ export interface SettingItem {
/**
* Reserved property. Not used at the moment.
*/
appTypes?: string[];
appTypes?: AppType[];
/**
* Set this to `true` to store secure data, such as passwords. Any such