diff --git a/packages/app-cli/tests/models_Setting.ts b/packages/app-cli/tests/models_Setting.ts index 2dc073076..0aba31597 100644 --- a/packages/app-cli/tests/models_Setting.ts +++ b/packages/app-cli/tests/models_Setting.ts @@ -1,4 +1,4 @@ -import Setting from '@joplin/lib/models/Setting'; +import Setting, { SettingSectionSource } from '@joplin/lib/models/Setting'; const { setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow } = require('./test-utils.js'); @@ -121,7 +121,7 @@ describe('models_Setting', function() { })); it('should register new sections', (async () => { - await Setting.registerSection('mySection', { + await Setting.registerSection('mySection', SettingSectionSource.Default, { label: 'My section', }); diff --git a/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx b/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx index fbf3af4a1..b07f4661e 100644 --- a/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx +++ b/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx @@ -1,6 +1,9 @@ +import { SettingSectionSource } from '@joplin/lib/models/Setting'; import * as React from 'react'; +import { useMemo } from 'react'; +import Setting from '@joplin/lib/models/Setting'; +import { _ } from '@joplin/lib/locale'; const styled = require('styled-components').default; -const Setting = require('@joplin/lib/models/Setting').default; interface Props { selection: string; @@ -30,6 +33,21 @@ export const StyledListItem = styled.a` } `; +export const StyledDivider = styled.div` + box-sizing: border-box; + display: flex; + flex-direction: row; + color: ${(props: any) => props.theme.color2}; + padding: ${(props: any) => props.theme.mainPadding}px; + padding-top: ${(props: any) => props.theme.mainPadding * .8}px; + padding-bottom: ${(props: any) => props.theme.mainPadding * .8}px; + border-top: 1px solid ${(props: any) => props.theme.dividerColor}; + border-bottom: 1px solid ${(props: any) => props.theme.dividerColor}; + background-color: ${(props: any) => props.theme.selectedColor2}; + font-size: ${(props: any) => Math.round(props.theme.fontSize)}px; + opacity: 0.5; +`; + export const StyledListItemLabel = styled.span` font-size: ${(props: any) => Math.round(props.theme.fontSize * 1.2)}px; font-weight: 500; @@ -50,6 +68,20 @@ export const StyledListItemIcon = styled.i` export default function Sidebar(props: Props) { const buttons: any[] = []; + const sortedSections = useMemo(() => { + const output = props.sections.slice(); + output.sort((a: any, b: any) => { + const s1 = a.source || SettingSectionSource.Default; + const s2 = b.source || SettingSectionSource.Default; + if (s1 === SettingSectionSource.Default && s2 === SettingSectionSource.Default) return props.sections.indexOf(s1) - props.sections.indexOf(s2); + if (s1 === SettingSectionSource.Default && s2 === SettingSectionSource.Plugin) return -1; + if (s1 === SettingSectionSource.Plugin && s2 === SettingSectionSource.Default) return +1; + return 0; + }); + console.info('SORTED', output); + return output; + }, [props.sections]); + function renderButton(section: any) { const selected = props.selection === section.name; return ( @@ -62,7 +94,22 @@ export default function Sidebar(props: Props) { ); } - for (const section of props.sections) { + function renderDivider(key: string) { + return ( + + {_('Plugins')} + + ); + } + + let pluginDividerAdded = false; + + for (const section of sortedSections) { + if (section.source === SettingSectionSource.Plugin && !pluginDividerAdded) { + buttons.push(renderDivider('divider-plugins')); + pluginDividerAdded = true; + } + buttons.push(renderButton(section)); } diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 2227a4e4a..a89fa8516 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -62,11 +62,17 @@ interface CacheItem { value: any; } +export enum SettingSectionSource { + Default = 1, + Plugin = 2, +} + export interface SettingSection { label: string; iconName?: string; description?: string; name?: string; + source?: SettingSectionSource; } interface SettingSections { @@ -1009,8 +1015,8 @@ class Setting extends BaseModel { }); } - static async registerSection(name: string, section: SettingSection) { - this.customSections_[name] = { ...section, name: name }; + static async registerSection(name: string, source: SettingSectionSource, section: SettingSection) { + this.customSections_[name] = { ...section, name: name, source: source }; } static settingMetadata(key: string): SettingItem { @@ -1512,6 +1518,11 @@ class Setting extends BaseModel { throw new Error(`Invalid type ID: ${typeId}`); } + private static sectionSource(sectionName: string): SettingSectionSource { + if (this.customSections_[sectionName]) return this.customSections_[sectionName].source || SettingSectionSource.Default; + return SettingSectionSource.Default; + } + static groupMetadatasBySections(metadatas: SettingItem[]) { const sections = []; const generalSection: any = { name: 'general', metadatas: [] }; @@ -1524,19 +1535,24 @@ class Setting extends BaseModel { generalSection.metadatas.push(md); } else { if (!nameToSections[md.section]) { - nameToSections[md.section] = { name: md.section, metadatas: [] }; + nameToSections[md.section] = { + name: md.section, + metadatas: [], + source: this.sectionSource(md.section), + }; sections.push(nameToSections[md.section]); } nameToSections[md.section].metadatas.push(md); } } - for (const name in this.customSections_) { - nameToSections[name] = { - name: name, - metadatas: [], - }; - } + // for (const name in this.customSections_) { + // nameToSections[name] = { + // name: name, + // source: this.customSections_[name].source, + // metadatas: [], + // }; + // } return sections; } diff --git a/packages/lib/services/plugins/api/JoplinSettings.ts b/packages/lib/services/plugins/api/JoplinSettings.ts index 1e4ed5686..cb81ba68c 100644 --- a/packages/lib/services/plugins/api/JoplinSettings.ts +++ b/packages/lib/services/plugins/api/JoplinSettings.ts @@ -1,5 +1,5 @@ import eventManager from '../../../eventManager'; -import Setting, { SettingItem as InternalSettingItem } from '../../../models/Setting'; +import Setting, { SettingItem as InternalSettingItem, SettingSectionSource } from '../../../models/Setting'; import Plugin from '../Plugin'; import { SettingItem, SettingSection } from './types'; @@ -71,7 +71,7 @@ export default class JoplinSettings { * Registers a new setting section. Like for registerSetting, it is dynamic and needs to be done every time the plugin starts. */ public async registerSection(name: string, section: SettingSection) { - return Setting.registerSection(this.namespacedKey(name), section); + return Setting.registerSection(this.namespacedKey(name), SettingSectionSource.Plugin, section); } /**