diff --git a/devblog/2021-03-01-RichTextDataProcessor-Configuration-API.md b/devblog/2021-03-01-RichTextDataProcessor-Configuration-API.md deleted file mode 100644 index 6815de98d5..0000000000 --- a/devblog/2021-03-01-RichTextDataProcessor-Configuration-API.md +++ /dev/null @@ -1,245 +0,0 @@ -RichText Data Processor: Configuration API -================================================================================ - -We are just at designing a central aspect for enabling CKEditor 5 for CoreMedia -Studio: Adding a plugin for data processing CoreMedia RichText 1.0 from view -to data and vice versa. - -The first sketch exists for mapping elements from view to data with a similar -design as it was for CKEditor 4. As for example CoreMedia RichText 1.0 does -not support elements `

` to `

`, we need to transform it to a paragraph -`

` with a well-known class: - -```javascript -{ - h1: (element) => { - element.name = "p"; - element.attributes["class"] = "p--heading-1"; - } -} -``` - -CKEditor 4: Misbehavior of CoreMedia Data Processing --------------------------------------------------------------------------------- - -In CKEditor 4 we ignored modeling the reverse-mapping. Thus, even within -CKEditor HTML a previously entered `

` was restored from server as -`

`. - -This caused no obvious problem at first glance, as actions to insert headings -were custom actions always adding `

` when a heading -has been selected during formatting. - -The problem only became obvious on paste from external sources: CKEditor HTML -contained `

` but toolbar buttons for headings did not correctly get -enabled or disabled. To make them update according to current cursor position -you had to reload the data from server, so that CKEditor HTML contains the -class-annotated `

` element. - -CKEditor 5: Sketching Requirements --------------------------------------------------------------------------------- - -### Req. 1: Use CKEditor Toolbars and Actions - -The central requirement is, that CKEditor toolbars and actions, which come by -default, are supported. Thus, if the editor has an action to set the current -line to heading level 1, no customization should be required to the action: It -should just simply add `

`, not more. - -### Req. 2: Provide Bijective Mapping - -To be able to fulfill Req. 1, we need to ensure that any mapping from -`

` to `

` comes with a reverse mapping, so that -`

` becomes `

` when rendered in CKEditor. - -### Req. 3: Configuration Option - -Each new feature added to CKEditor (like for example choosing font-colors) -requires thinking about mapping from HTML to CoreMedia RichText 1.0. - -Sticking to the example of font-colors we will most likely have a `style` -attribute added, which is not valid in CoreMedia RichText 1.0. If we choose -mapping it to some class attribute like for example -`` we should be able specifying this in -the configuration section of a CKEditor instance. - -### Req. 4: Extension Point - -We not only need to be able to configure behaviors, we must be able extending -them. Think of an attribute added to `

` by a new CKEditor plugin. We now -must be able to extend the default `

` mapping from `

` to -`

`. - -### Req. 5: Configurable Extension Point - -This may be an optional requirement, at least not priority one: It may be nice, -if it would be possible to access the configuration as well from mapping rules. -This way, we can access for example the strictness setting. - -### Req. 6: Easy Bijective Mapping Overview - -In traditional CKEditor 4 implementation, we would have two mappings kept -separate. One for the `toData` direction (HTML to RichText) and one for the -`toView` direction (RichText to HTML). - -This is easily error-prone, as when mapping one way you should not forget -about mentioning the reverse mapping as well. - -The idea is to have both mappings close together, so that you can easily spot -_a missing branch_. - -### Req. 7: One Way Mapping - -It should be possible proving one way mapping. This most likely is especially -about adjusting elements, which cause invalid CoreMedia RichText 1.0. - -### Req. 8: Similarity to CKEditor 4 Data Processing - -At the time of writing, this is already fulfilled, but it may break, if we -decide to refactor the approach. To be able to see, if the mapping in CKEditor 5 -is similar to CKEditor 4 mapping, the mapping configuration should be similar. - -### Req. 9: Fast - -Any solution we find needs to be fast on repeating calls. It is expected, that -for example every 5 seconds the data need to be stored back to the server, and -thus transformed. An editor must not experience noticeable difference while -typing in a 10-character text from a 20,000-character text (roughly guessed -number). - -Configuration Sketch --------------------------------------------------------------------------------- - -### Default Mapping - -By default, the mapping may look as follows: - -```javascript -{ - elements: { - h1: { - toData: (element) => { - element.name = "p"; - element.attributes["class"] = "p--heading-1"; - }, - toView: { - p: (element) => { - if (element.attributes["class"] === "p--heading-1") { - element.name = "h1"; - delete element.attributes["class"]; - } - } - } - }, - // Similar mapping as above. Challenge: Two rules to handle p elements. - h2: { - toData: (element) => { - element.name = "p"; - element.attributes["class"] = "p--heading-2"; - }, - toView: { - p: (element) => { - if (element.attributes["class"] === "p--heading-2") { - element.name = "h2"; - delete element.attributes["class"]; - } - }, - }, - }, - // The One-Way Direction (Here just removing an invalid element we want to ignore). - "invalid:element": () => false, - // Especially for configuration access, the editor instance is handed over - // as optional argument: - "$$": (element, editor) => { - // now do processing according to given strictness for example. - }, - } -} -``` - -This is still similar to the CKEditor 4 configuration. It also has the toView -mapping close to the toData mapping. The idea is, that on initialization phase -we dynamically split the toData and toView mapping into two, to have a fast -lookup table during processing. In the end, we will have something like this: - -toData: - -```javascript -{ - elements: { - h1: (element) => { - element.name = "p"; - element.attributes["class"] = "p--heading-1"; - }, - h2: (element) => { - element.name = "p"; - element.attributes["class"] = "p--heading-2"; - }, - "invalid:element": () => false, - "$$": (element, editor) => { - // ... - }, - } -} -``` - -toView: - -```javascript -{ - elements: { - p: [ - (element) => { - if (element.attributes["class"] === "p--heading-1") { - element.name = "h1"; - delete element.attributes["class"]; - } - }, - (element) => { - if (element.attributes["class"] === "p--heading-2") { - element.name = "h2"; - delete element.attributes["class"]; - } - }, - ] - } -} -``` - -Now, we can just check for each element name in the map. For the toView part -we need an array, as both rules may apply to `

`. - -### Configuration and Extension - -Here is an example, how we may add the font-color mapping to a span. It takes -into account, that we may already have a mapping for spans in the default -configuration. If we don't, we will override the behavior, which may be -another desired use-case. - -```javascript -ClassicEditor.create(document.querySelector( '.editor' ), { - plugins: [ - // ... - CoreMediaRichText - ], - "coremedia:richtext": { - strictness: Strictness.STRICT, - rules: { - // editor: Just showing, how to pass the editor if we require for example - // configuration access. - span: (element, super, editor) => { - // Unsure, if super is a reserved word. If yes, just choose another name. - super.apply(this, arguments); - // Very simplistic approach. Just for demonstration purpose. - if (element.attributes["style"]?.contains("color:white")) { - element.attributes["class"]=`${element.attributes["class"]}`; - } - }; - }, - }, -}); -``` - -Having this, we may also change our mind on how to handle the parsed `toView` -mapping. Instead, upon parsing, we just may override the existing mapping -with a new one, taking the old mapping into account. diff --git a/devblog/2021-03-02-Extensible-Configuration-API.md b/devblog/2021-03-02-Extensible-Configuration-API.md deleted file mode 100644 index 885498361d..0000000000 --- a/devblog/2021-03-02-Extensible-Configuration-API.md +++ /dev/null @@ -1,143 +0,0 @@ -Extensible Configuration API -================================================================================ - -In the [previous post](2021-03-01-RichTextDataProcessor-Configuration-API.md) -we sketched a possible scenario how to provide a configurable filtering API. -One challenge was, that we need to be able to customize the filtering within -the CKEditor configuration section. Customization may include replacing, -extending or even (for whatever reason) disabling an existing filter. - -Yet, we want especially at CKEditor configuration level have an easy to write -configuration section. One of the challenges are the parameters to pass. When -we are _between_ the configuration hierarchy, we don't want to have to write -bloated parameter lists, where 80% are just forwarded without ever using them. -We need some _pick, what you want_ approach. - -The idea is, to pass an object to the filter functions. Which seems to be -heavy-weight at first glance, provides much flexibility … and can easily be -extended for future scenarios (like adding additional parameters). - -Example Hierarchy Processing --------------------------------------------------------------------------------- - -Here is a rough example, which you can try at -[TS Playground][example-1]: - -```typescript -type EditorConfig = { [key:string]: string }; -// Used for named parameters. -interface MapFnParameters { - el: HTMLElement, - parent?: MapFn, - cfg?: EditorConfig, -} -type MapFn = (args: MapFnParameters) => void; -type Config = { [key:string]: MapFn }; - -const element: HTMLElement = document.createElement("h1"); -element.classList.add("initial"); - -// Some additional configuration options in CKEditor Configuration. -const editorConfig: EditorConfig = { - "class": "config", -}; - -const defaultConfig: Config = { - h1: (args: MapFnParameters) => { - args.el.classList.add("default"); - args.cfg && args.cfg["class"] && args.el.classList.add(args.cfg["class"]); - }, -}; - -const customConfig: Config = { - h1: (args: MapFnParameters) => { - args.parent && args.parent(args); - args.el.classList.add("custom"); - }, -}; - -const mapFn: MapFn | undefined = customConfig[element.localName]; - -mapFn && mapFn({ - el: element, - parent: defaultConfig[element.localName], - cfg: editorConfig -}); - -console.log({className: element.className}); -``` - -Example List Processing --------------------------------------------------------------------------------- - -An additional requirement from -[previous post](2021-03-01-RichTextDataProcessor-Configuration-API.md) is the -ability to merge lists of processing instructions. This is especially relevant -for the `toView` section, where we may have a bunch of rules for processing -a paragraph, which represents a heading at certain levels. - -Extending the example above, now having two custom configs to be merged, it may -look like this [see TS PlayGround][example-2]: - -```typescript -function mergeConfigs(...config: Config[]): Config { - const result: Config = {}; - config.forEach((c) => { - Object.keys(c).forEach((k) => { - if (result.hasOwnProperty(k)) { - const previousFn: MapFn = result[k]; - const thisFn: MapFn = c[k]; - result[k] = (args: MapFnParameters) => { - previousFn(args); - thisFn(args); - }; - } else { - result[k] = c[k]; - } - }); - }); - return result; -} - -const customConfig1: Config = { - h1: (args: MapFnParameters) => { - args.parent && args.parent(args); - args.el.classList.add("custom1"); - }, -}; -const customConfig2: Config = { - h1: (args: MapFnParameters) => { - args.parent && args.parent(args); - args.el.classList.add("custom2"); - }, -}; -const customConfig: Config = mergeConfigs(customConfig1, customConfig2); -``` - -Where is the Difference? --------------------------------------------------------------------------------- - -The two approaches (hierarchy and list based) are similar. This raises the -question, if there is a common base, or if one could be used to represent the -other. - -The main difference is, that for the hierarchical approach we want to be able -to completely override a default behavior. This may be a bugfix, or just -(for the headings example) another class we want to assign instead of -`p--heading-1`. - -To use the list approach also for hierarchical approach, we would require some -explicit order to define, a priority. If a priority is higher, this processing -will come last, and thus, enable overriding the original behavior. - -But, this comes at cost: - -* You need to carefully maintain and document the order attributes, so that - everyone knows what a higher or lower priority is. - -* The continuous processing costs may increase without need, as the super-call - would always be executed, even if a later instructions completely overrides - the behavior. - -[example-1]: -[example-2]: diff --git a/devblog/2021-09-28-Localization.md b/devblog/2021-09-28-Localization.md deleted file mode 100644 index 8164226fdd..0000000000 --- a/devblog/2021-09-28-Localization.md +++ /dev/null @@ -1,37 +0,0 @@ -# About providing localized texts - -Even if CKEditor 5 supports `po`-files we have to use `*.ts` files to define the dictionaries. -`po`-files are preprocessed and variables are not supported. This means something like `t(config.mytitle)` is not resolved to a localized string. -The dictionaries defined in `*.ts` files are loaded dynamically and enables us to localize variables. - -An example `*.ts` file with localizations looks like this - -```typescript -import { add } from "@ckeditor/ckeditor5-utils/src/translation-service"; - -add("de", { - Link: "Verknüpfung", - "Enter url or drag and drop content onto this area.": "URL angeben oder Inhalt hierher ziehen", -}); -``` - -# Providing localized texts — Decisions - -*Decision*: Localization in plugins must be provided as `*.ts` files. - -*Decision*: Localization `*.ts`-files are stored in the folder `src/lang` of each plugin package. - -*Decision*: Each feature has its own localization file (e.g. `contentlinks.ts` and `linktarget.ts`). - -*Decision*: As recommended in CKEditor 5 documentation we use english text as key for localization. - -# About passing localized texts to components - -As documented in CKEditor 5 documentation we simply use the `t()`-function of CKEditor. - -Example: -```typescript - contentLinkView.set({ - label: t("Link") - }); -``` diff --git a/devblog/README.md b/devblog/README.md deleted file mode 100644 index 67af249bd4..0000000000 --- a/devblog/README.md +++ /dev/null @@ -1,5 +0,0 @@ -Developer Blog -================================================================================ - -This folder is meant to hold notes for development. It may include design -sketches as thoughts, and may help to understand design decisions later on.