Skip to content

Commit ac08261

Browse files
authored
Merge pull request #114 from WoltLab/html-upcast
Html upcast
2 parents 8fb1cd0 + 1fb9906 commit ac08261

7 files changed

Lines changed: 130 additions & 156 deletions

File tree

app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
WoltlabHtmlEmbed,
4141
WoltlabImage,
4242
WoltlabMagicParagraph,
43+
WoltlabMedia,
4344
WoltlabMention,
4445
WoltlabMetacode,
4546
WoltlabSmiley,
@@ -105,6 +106,7 @@ const defaultConfig: Core.EditorConfig = {
105106
WoltlabHtmlEmbed.WoltlabHtmlEmbed,
106107
WoltlabImage.WoltlabImage,
107108
WoltlabMagicParagraph.WoltlabMagicParagraph,
109+
WoltlabMedia.WoltlabMedia,
108110
WoltlabMention.WoltlabMention,
109111
WoltlabMetacode.WoltlabMetacode,
110112
WoltlabSmiley.WoltlabSmiley,

modules.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export * as WoltlabCodeBlock from "./plugins/ckeditor5-woltlab-code-block/src";
4141
export * as WoltlabHtmlEmbed from "./plugins/ckeditor5-woltlab-html-embed/src";
4242
export * as WoltlabImage from "./plugins/ckeditor5-woltlab-image/src";
4343
export * as WoltlabMagicParagraph from "./plugins/ckeditor5-woltlab-magic-paragraph/src";
44+
export * as WoltlabMedia from "./plugins/ckeditor5-woltlab-media/src";
4445
export * as WoltlabMention from "./plugins/ckeditor5-woltlab-mention/src";
4546
export * as WoltlabMetacode from "./plugins/ckeditor5-woltlab-metacode/src";
4647
export * as WoltlabSmiley from "./plugins/ckeditor5-woltlab-smiley/src";

plugins/ckeditor5-woltlab-attachment/src/woltlabattachment.ts

Lines changed: 16 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99

1010
import { Plugin } from "@ckeditor/ckeditor5-core";
1111
import { Image, ImageUtils } from "@ckeditor/ckeditor5-image";
12-
import type {
13-
WoltlabMetacode,
14-
WoltlabMetacodeUpcast,
15-
} from "../../ckeditor5-woltlab-metacode/";
1612

1713
export class WoltlabAttachment extends Plugin {
1814
static get pluginName() {
@@ -34,14 +30,25 @@ export class WoltlabAttachment extends Plugin {
3430
const imageTypes = ["imageBlock", "imageInline"];
3531
imageTypes.forEach((imageType) => {
3632
schema.extend(imageType, {
37-
allowAttributes: ["attachmentId", "data-width"],
33+
allowAttributes: ["attachmentId"],
34+
});
35+
36+
conversion.attributeToAttribute({
37+
model: {
38+
key: "classList",
39+
values: ["woltlabAttachment"],
40+
},
41+
view: {
42+
woltlabAttachment: {
43+
name: imageType === "imageBlock" ? "figure" : "img",
44+
key: "class",
45+
value: "woltlabAttachment",
46+
},
47+
},
3848
});
3949
});
4050

41-
const attributeMapping = new Map([
42-
["attachmentId", "data-attachment-id"],
43-
["data-width", "data-width"],
44-
]);
51+
const attributeMapping = new Map([["attachmentId", "data-attachment-id"]]);
4552
const imageUtils = this.editor.plugins.get("ImageUtils");
4653

4754
Array.from(attributeMapping.entries()).forEach(([model, view]) => {
@@ -87,153 +94,7 @@ export class WoltlabAttachment extends Plugin {
8794
);
8895
});
8996
});
90-
91-
this.#setupAttachUpcast();
92-
}
93-
94-
#setupAttachUpcast(): void {
95-
const options = this.editor.config.get(
96-
"woltlabAttachment",
97-
) as WoltlabAttachmentConfig;
98-
99-
const woltlabMetacode = this.editor.plugins.get(
100-
"WoltlabMetacode",
101-
) as WoltlabMetacode;
102-
woltlabMetacode.on(
103-
"upcast",
104-
(eventInfo, eventData: WoltlabMetacodeUpcast) => {
105-
if (eventData.name !== "attach") {
106-
return;
107-
}
108-
109-
const attachmentId = parseInt(eventData.attributes[0].toString());
110-
if (Number.isNaN(attachmentId)) {
111-
return;
112-
}
113-
114-
if (!options.inlineImageIds.includes(attachmentId)) {
115-
return;
116-
}
117-
118-
let floatBehavior = eventData.attributes[1]
119-
? eventData.attributes[1].toString()
120-
: "none";
121-
if (
122-
floatBehavior !== "left" &&
123-
floatBehavior !== "right" &&
124-
floatBehavior !== "center" &&
125-
floatBehavior !== "none"
126-
) {
127-
floatBehavior = "none";
128-
}
129-
130-
let isThumbnail = eventData.attributes[2] as unknown;
131-
let width = "auto";
132-
if (typeof isThumbnail !== "boolean") {
133-
if (
134-
typeof isThumbnail === "string" ||
135-
typeof isThumbnail === "number"
136-
) {
137-
if (isThumbnail === "true") {
138-
isThumbnail = true;
139-
} else if (isThumbnail === "false") {
140-
isThumbnail = false;
141-
} else {
142-
isThumbnail = parseInt(isThumbnail.toString());
143-
if (Number.isNaN(isThumbnail)) {
144-
isThumbnail = false;
145-
} else {
146-
if (isThumbnail === 0) {
147-
isThumbnail = false;
148-
} else {
149-
width = `${isThumbnail}px`;
150-
isThumbnail = false;
151-
}
152-
}
153-
}
154-
} else {
155-
isThumbnail = false;
156-
}
157-
}
158-
159-
if (
160-
this.#upcastAttachment(
161-
eventData,
162-
options.resolveAttachmentUrl,
163-
attachmentId,
164-
floatBehavior as FloatBehavior,
165-
isThumbnail as boolean,
166-
width,
167-
)
168-
) {
169-
eventInfo.stop();
170-
}
171-
},
172-
);
173-
}
174-
175-
#upcastAttachment(
176-
eventData: WoltlabMetacodeUpcast,
177-
resolveAttachUrl: ResolveAttachmentUrl,
178-
attachmentId: number,
179-
floatBehavior: FloatBehavior,
180-
isThumbnail: boolean,
181-
width: string,
182-
): boolean {
183-
const { conversionApi, data } = eventData;
184-
const { consumable, writer } = conversionApi;
185-
const { viewItem } = data;
186-
187-
let model = "imageInline";
188-
let attributes: Record<string, unknown> = {
189-
attachmentId,
190-
src: resolveAttachUrl(attachmentId, isThumbnail),
191-
"data-width": width,
192-
width,
193-
};
194-
195-
if (floatBehavior === "left") {
196-
model = "imageBlock";
197-
attributes.imageStyle = "sideLeft";
198-
} else if (floatBehavior === "right") {
199-
model = "imageBlock";
200-
attributes.imageStyle = "side";
201-
} else if (floatBehavior === "center") {
202-
model = "imageBlock";
203-
}
204-
205-
const image = writer.createElement(model);
206-
writer.setAttributes(attributes, image);
207-
208-
conversionApi.convertChildren(viewItem, image);
209-
210-
if (!conversionApi.safeInsert(image, data.modelCursor)) {
211-
return false;
212-
}
213-
214-
consumable.consume(viewItem, { name: true });
215-
conversionApi.updateConversionResult(image, data);
216-
217-
return true;
21897
}
21998
}
22099

221100
export default WoltlabAttachment;
222-
223-
type FloatBehavior = "left" | "none" | "right" | "center";
224-
225-
type ResolveAttachmentUrl = (
226-
attachmentId: number,
227-
isThumbnail: boolean,
228-
) => string;
229-
230-
type WoltlabAttachmentConfig = {
231-
inlineImageIds: number[];
232-
resolveAttachmentUrl: ResolveAttachmentUrl;
233-
};
234-
235-
declare module "@ckeditor/ckeditor5-core" {
236-
interface EditorConfig {
237-
woltlabAttachment?: WoltlabAttachmentConfig;
238-
}
239-
}

plugins/ckeditor5-woltlab-image/src/woltlabimage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class WoltlabImage extends Plugin {
3636
const { conversion } = this.editor;
3737

3838
const imageTypes = ["imageBlock", "imageInline"] as const;
39+
const imageUtils = this.editor.plugins.get("ImageUtils");
3940

4041
for (const imageType of imageTypes) {
4142
conversion.for("dataDowncast").attributeToAttribute({
@@ -88,7 +89,9 @@ export class WoltlabImage extends Plugin {
8889
return;
8990
}
9091

91-
const img = domConverter.viewToDom(viewElement);
92+
const img = domConverter.viewToDom(
93+
imageUtils.findViewImgElement(viewElement)!,
94+
);
9295
if (!(img instanceof HTMLImageElement)) {
9396
return;
9497
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"author": "WoltLab GmbH",
3+
"license": "LGPL-2.1-or-later",
4+
"main": "src/index.ts",
5+
"private": true
6+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @author Alexander Ebert
3+
* @copyright 2001-2023 WoltLab GmbH
4+
* @license LGPL-2.1-or-later
5+
* @since 6.0
6+
*/
7+
8+
export { WoltlabMedia } from "./woltlabmedia";
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Uploads files dropped onto the editor to the media system.
3+
*
4+
* @author Alexander Ebert
5+
* @copyright 2001-2023 WoltLab GmbH
6+
* @license LGPL-2.1-or-later
7+
* @since 6.0
8+
*/
9+
10+
import { Plugin } from "@ckeditor/ckeditor5-core";
11+
import { Image, ImageUtils } from "@ckeditor/ckeditor5-image";
12+
import { WoltlabMetacode } from "../../ckeditor5-woltlab-metacode";
13+
14+
export class WoltlabMedia extends Plugin {
15+
static get pluginName() {
16+
return "WoltlabMedia";
17+
}
18+
19+
static get requires() {
20+
return [Image, WoltlabMetacode, ImageUtils] as const;
21+
}
22+
23+
init() {
24+
this.#setupImageElement();
25+
}
26+
27+
#setupImageElement(): void {
28+
const { conversion, model } = this.editor;
29+
30+
// We need to register a custom attribute to keep track of
31+
// images that have been uploaded as media..
32+
const { schema } = model;
33+
const imageTypes = ["imageBlock", "imageInline"];
34+
imageTypes.forEach((imageType) => {
35+
schema.extend(imageType, {
36+
allowAttributes: ["mediaId", "mediaSize"],
37+
});
38+
39+
conversion.attributeToAttribute({
40+
model: {
41+
key: "classList",
42+
values: ["woltlabSuiteMedia"],
43+
},
44+
view: {
45+
woltlabSuiteMedia: {
46+
name: imageType === "imageBlock" ? "figure" : "img",
47+
key: "class",
48+
value: "woltlabSuiteMedia",
49+
},
50+
},
51+
});
52+
});
53+
54+
const attributeMapping = new Map([
55+
["mediaId", "data-media-id"],
56+
["mediaSize", "data-media-size"],
57+
]);
58+
const imageUtils = this.editor.plugins.get("ImageUtils");
59+
60+
Array.from(attributeMapping.entries()).forEach(([model, view]) => {
61+
conversion.for("upcast").attributeToAttribute({
62+
view,
63+
model,
64+
});
65+
66+
conversion.for("downcast").add((dispatcher) => {
67+
imageTypes.forEach((imageType) => {
68+
dispatcher.on(
69+
`attribute:${model}:${imageType}`,
70+
(evt, data, conversionApi) => {
71+
if (!conversionApi.consumable.consume(data.item, evt.name)) {
72+
return;
73+
}
74+
75+
const viewWriter = conversionApi.writer;
76+
const img = imageUtils.findViewImgElement(
77+
conversionApi.mapper.toViewElement(data.item),
78+
);
79+
80+
if (data.attributeNewValue !== null) {
81+
viewWriter.setAttribute(view, data.attributeNewValue, img);
82+
} else {
83+
viewWriter.removeAttribute(view, img);
84+
}
85+
},
86+
);
87+
});
88+
});
89+
});
90+
}
91+
}
92+
93+
export default WoltlabMedia;

0 commit comments

Comments
 (0)