from wagtail import hooks class FeatureRegistry: """ A central store of information about optional features that can be enabled in rich text editors by passing a ``features`` list to the RichTextField, such as how to whitelist / convert HTML tags, and how to enable the feature on various editors. This information may come from diverse sources - for example, wagtailimages might define an 'images' feature and a Draftail plugin for it, while a third-party module might define a TinyMCE plugin for the same feature. The information is therefore collected into this registry via the 'register_rich_text_features' hook. """ def __init__(self): # Has the register_rich_text_features hook been run for this registry? self.has_scanned_for_features = False # a dict of dicts, one for each editor (draftail.js, TinyMCE etc); each dict is a mapping # of feature names to 'plugin' objects that define how to implement that feature # (e.g. paths to JS files to import). The API of that plugin object is not defined # here, and is specific to each editor. self.plugins_by_editor = {} # a list of feature names that will be applied on rich text areas that do not specify # an explicit `feature` list. self.default_features = [] # a mapping of linktype names to rewriter functions for converting database representations # of links (e.g. ) into front-end HTML. Each rewriter function # takes a dict of attributes, and returns the rewritten opening tag as a string self.link_types = {} # a mapping of embedtype names to rewriter functions for converting database representations # of embedded content (e.g. ) # into front-end HTML. Each rewriter function takes a dict of attributes, and returns an # HTML fragment to replace it with self.embed_types = {} # a dict of dicts, one for each converter backend (editorhtml, contentstate etc); # each dict is a mapping of feature names to 'rule' objects that define how to convert # that feature's elements between editor representation and database representation # (e.g. elements to whitelist, functions for transferring attributes). # The API of that rule object is not defined here, and is specific to each converter backend. self.converter_rules_by_converter = {} def get_default_features(self): if not self.has_scanned_for_features: self._scan_for_features() return self.default_features def _scan_for_features(self): for fn in hooks.get_hooks("register_rich_text_features"): fn(self) self.has_scanned_for_features = True def register_editor_plugin(self, editor_name, feature_name, plugin): plugins = self.plugins_by_editor.setdefault(editor_name, {}) plugins[feature_name] = plugin def get_editor_plugin(self, editor_name, feature_name): if not self.has_scanned_for_features: self._scan_for_features() try: return self.plugins_by_editor[editor_name][feature_name] except KeyError: return None def register_link_type(self, handler): self.link_types[handler.identifier] = handler def get_link_types(self): if not self.has_scanned_for_features: self._scan_for_features() return self.link_types def register_embed_type(self, handler): self.embed_types[handler.identifier] = handler def get_embed_types(self): if not self.has_scanned_for_features: self._scan_for_features() return self.embed_types def register_converter_rule(self, converter_name, feature_name, rule): rules = self.converter_rules_by_converter.setdefault(converter_name, {}) rules[feature_name] = rule def get_converter_rule(self, converter_name, feature_name): if not self.has_scanned_for_features: self._scan_for_features() try: return self.converter_rules_by_converter[converter_name][feature_name] except KeyError: return None @staticmethod def function_as_entity_handler(identifier, fn): """Supports legacy registering of entity handlers as functions.""" return type( "EntityHandlerRegisteredAsFunction", (object,), { "identifier": identifier, "expand_db_attributes": staticmethod(fn), }, )