Skip to content

Commit

Permalink
Make option parsing type safe (#4052)
Browse files Browse the repository at this point in the history
  • Loading branch information
luin authored Mar 14, 2024
1 parent 71fada2 commit ae9f835
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 83 deletions.
166 changes: 83 additions & 83 deletions packages/quill/src/core/quill.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cloneDeep, merge } from 'lodash-es';
import { merge } from 'lodash-es';
import * as Parchment from 'parchment';
import type { Op } from 'quill-delta';
import Delta from 'quill-delta';
Expand Down Expand Up @@ -34,7 +34,6 @@ interface Options {
debug?: DebugLevel | boolean;
registry?: Parchment.Registry;
readOnly?: boolean;
container?: HTMLElement | string;
placeholder?: string;
bounds?: HTMLElement | string | null;
modules?: Record<string, unknown>;
Expand All @@ -46,17 +45,23 @@ interface ExpandedOptions extends Omit<Options, 'theme'> {
container: HTMLElement;
modules: Record<string, unknown>;
bounds?: HTMLElement | null;
readOnly: boolean;
}

class Quill {
static DEFAULTS: Partial<Options> = {
static DEFAULTS = {
bounds: null,
modules: {},
modules: {
clipboard: true,
keyboard: true,
history: true,
uploader: true,
},
placeholder: '',
readOnly: false,
registry: globalRegistry,
theme: 'default',
};
} as const satisfies Partial<Options>;
static events = Emitter.events;
static sources = Emitter.sources;
static version = typeof QUILL_VERSION === 'undefined' ? 'dev' : QUILL_VERSION;
Expand Down Expand Up @@ -727,94 +732,89 @@ class Quill {
}
}

function resolveSelector(selector: string | HTMLElement | null | undefined) {
return typeof selector === 'string'
? document.querySelector<HTMLElement>(selector)
: selector;
}

function expandModuleConfig(config: Record<string, unknown> | undefined) {
return Object.entries(config ?? {}).reduce(
(expanded, [key, value]) => ({
...expanded,
[key]: value === true ? {} : value,
}),
{},
);
}

function expandConfig(
container: HTMLElement | string,
userConfig: Options,
containerOrSelector: HTMLElement | string,
options: Options,
): ExpandedOptions {
// @ts-expect-error -- TODO fix this later
let expandedConfig: ExpandedOptions = merge(
{
container,
modules: {
clipboard: true,
keyboard: true,
history: true,
uploader: true,
},
},
userConfig,
);
const container = resolveSelector(containerOrSelector);
if (!container) {
throw new Error('Invalid Quill container');
}

// @ts-expect-error -- TODO fix this later
if (!expandedConfig.theme || expandedConfig.theme === Quill.DEFAULTS.theme) {
expandedConfig.theme = Theme;
} else {
expandedConfig.theme = Quill.import(`themes/${expandedConfig.theme}`);
if (expandedConfig.theme == null) {
throw new Error(
`Invalid theme ${expandedConfig.theme}. Did you register it?`,
);
}
const shouldUseDefaultTheme =
!options.theme || options.theme === Quill.DEFAULTS.theme;
const theme = shouldUseDefaultTheme
? Theme
: Quill.import(`themes/${options.theme}`);
if (!theme) {
throw new Error(`Invalid theme ${options.theme}. Did you register it?`);
}
// @ts-expect-error -- TODO fix this later
const themeConfig = cloneDeep(expandedConfig.theme.DEFAULTS);
[themeConfig, expandedConfig].forEach((config) => {
config.modules = config.modules || {};
Object.keys(config.modules).forEach((module) => {
if (config.modules[module] === true) {
config.modules[module] = {};
}
});
});
const moduleNames = Object.keys(themeConfig.modules).concat(
Object.keys(expandedConfig.modules),

const { modules: quillModuleDefaults, ...quillDefaults } = Quill.DEFAULTS;
const { modules: themeModuleDefaults, ...themeDefaults } = theme.DEFAULTS;

const modules: ExpandedOptions['modules'] = merge(
{},
expandModuleConfig(quillModuleDefaults),
expandModuleConfig(themeModuleDefaults),
expandModuleConfig(options.modules),
);
const moduleConfig = moduleNames.reduce((config, name) => {
const moduleClass = Quill.import(`modules/${name}`);
if (moduleClass == null) {
debug.error(
`Cannot load ${name} module. Are you sure you registered it?`,
);
} else {
// @ts-expect-error
config[name] = moduleClass.DEFAULTS || {};
}
return config;
}, {});
// Special case toolbar shorthand
if (
expandedConfig.modules != null &&
expandedConfig.modules.toolbar &&
expandedConfig.modules.toolbar.constructor !== Object
modules != null &&
modules.toolbar &&
modules.toolbar.constructor !== Object
) {
expandedConfig.modules.toolbar = {
container: expandedConfig.modules.toolbar,
modules.toolbar = {
container: modules.toolbar,
};
}
expandedConfig = merge(
{},
Quill.DEFAULTS,
{ modules: moduleConfig },
themeConfig,
expandedConfig,
);
(['bounds', 'container'] as const).forEach((key) => {
const selector = expandedConfig[key];
if (typeof selector === 'string') {
// @ts-expect-error Handle null case
expandedConfig[key] = document.querySelector(selector) as HTMLElement;
}
});
expandedConfig.modules = Object.keys(expandedConfig.modules).reduce(
(config: Record<string, unknown>, name) => {
if (expandedConfig.modules[name]) {
config[name] = expandedConfig.modules[name];
}
return config;
},
{},
);
return expandedConfig;

const config = { ...quillDefaults, ...themeDefaults, ...options };

return {
container,
theme,
modules: Object.entries(modules).reduce(
(modulesWithDefaults, [name, value]) => {
if (!value) return modulesWithDefaults;

const moduleClass = Quill.import(`modules/${name}`);
if (moduleClass == null) {
debug.error(
`Cannot load ${name} module. Are you sure you registered it?`,
);
return modulesWithDefaults;
}
return {
...modulesWithDefaults,
// @ts-expect-error
[name]: merge({}, moduleClass.DEFAULTS || {}, value),
};
},
{},
),
bounds: resolveSelector(config.bounds),
registry: config.registry,
placeholder: config.placeholder,
readOnly: config.readOnly,
};
}

// Handle selection preservation and TEXT_CHANGE emission
Expand Down
1 change: 1 addition & 0 deletions packages/quill/src/core/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Theme {

export interface ThemeConstructor {
new (quill: Quill, options: unknown): Theme;
DEFAULTS: ThemeOptions;
}

export default Theme;

0 comments on commit ae9f835

Please sign in to comment.