|
|
@@ -3,6 +3,31 @@ import deepmerge from 'ts-deepmerge';
|
|
|
|
|
|
type Attributes = typeof defaultSchema.attributes;
|
|
|
|
|
|
+type ExtractPropertyDefinition<T> = T extends Record<string, (infer U)[]>
|
|
|
+ ? U
|
|
|
+ : never;
|
|
|
+
|
|
|
+type PropertyDefinition = ExtractPropertyDefinition<NonNullable<Attributes>>;
|
|
|
+
|
|
|
+const excludeRestrictedClassAttributes = (propertyDefinitions: PropertyDefinition[]): PropertyDefinition[] => {
|
|
|
+ if (propertyDefinitions == null) {
|
|
|
+ return propertyDefinitions;
|
|
|
+ }
|
|
|
+
|
|
|
+ return propertyDefinitions.filter((propertyDefinition) => {
|
|
|
+ if (!Array.isArray(propertyDefinition)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return propertyDefinition[0] !== 'class' && propertyDefinition[0] !== 'className';
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// generate relaxed schema
|
|
|
+const relaxedSchemaAttributes = structuredClone(defaultSchema.attributes) ?? {};
|
|
|
+relaxedSchemaAttributes.a = excludeRestrictedClassAttributes(relaxedSchemaAttributes.a);
|
|
|
+relaxedSchemaAttributes.ul = excludeRestrictedClassAttributes(relaxedSchemaAttributes.ul);
|
|
|
+relaxedSchemaAttributes.li = excludeRestrictedClassAttributes(relaxedSchemaAttributes.li);
|
|
|
+
|
|
|
/**
|
|
|
* reference: https://meta.stackexchange.com/questions/1777/what-html-tags-are-allowed-on-stack-exchange-sites,
|
|
|
* https://github.com/jch/html-pipeline/blob/70b6903b025c668ff3c02a6fa382031661182147/lib/html/pipeline/sanitization_filter.rb#L41
|
|
|
@@ -20,7 +45,7 @@ export const tagNames: Array<string> = [
|
|
|
];
|
|
|
|
|
|
export const attributes: Attributes = deepmerge(
|
|
|
- defaultSchema.attributes ?? {},
|
|
|
+ relaxedSchemaAttributes,
|
|
|
{
|
|
|
iframe: ['allow', 'referrerpolicy', 'sandbox', 'src', 'srcdoc'],
|
|
|
video: ['controls', 'src', 'muted', 'preload', 'width', 'height', 'autoplay'],
|