mirror of
				https://github.com/vector-im/element-web.git
				synced 2025-11-04 02:02:14 +01:00 
			
		
		
		
	do parts creation only in PartCreator to not scatter dependencies
This commit is contained in:
		
							parent
							
								
									4ce72fdffa
								
							
						
					
					
						commit
						eb4ff50c3c
					
				@ -233,7 +233,7 @@ export default class MessageEditor extends React.Component {
 | 
			
		||||
            parts = editState.getSerializedParts().map(p => partCreator.deserializePart(p));
 | 
			
		||||
        } else {
 | 
			
		||||
            // otherwise, parse the body of the event
 | 
			
		||||
            parts = parseEvent(editState.getEvent(), room, this.context.matrixClient);
 | 
			
		||||
            parts = parseEvent(editState.getEvent(), partCreator);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new EditorModel(
 | 
			
		||||
 | 
			
		||||
@ -15,22 +15,19 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import {UserPillPart, RoomPillPart, PlainPart} from "./parts";
 | 
			
		||||
 | 
			
		||||
export default class AutocompleteWrapperModel {
 | 
			
		||||
    constructor(updateCallback, getAutocompleterComponent, updateQuery, room, client) {
 | 
			
		||||
    constructor(updateCallback, getAutocompleterComponent, updateQuery, partCreator) {
 | 
			
		||||
        this._updateCallback = updateCallback;
 | 
			
		||||
        this._getAutocompleterComponent = getAutocompleterComponent;
 | 
			
		||||
        this._updateQuery = updateQuery;
 | 
			
		||||
        this._partCreator = partCreator;
 | 
			
		||||
        this._query = null;
 | 
			
		||||
        this._room = room;
 | 
			
		||||
        this._client = client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onEscape(e) {
 | 
			
		||||
        this._getAutocompleterComponent().onEscape(e);
 | 
			
		||||
        this._updateCallback({
 | 
			
		||||
            replacePart: new PlainPart(this._queryPart.text),
 | 
			
		||||
            replacePart: this._partCreator.plain(this._queryPart.text),
 | 
			
		||||
            caretOffset: this._queryOffset,
 | 
			
		||||
            close: true,
 | 
			
		||||
        });
 | 
			
		||||
@ -93,21 +90,18 @@ export default class AutocompleteWrapperModel {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _partForCompletion(completion) {
 | 
			
		||||
        const firstChr = completion.completionId && completion.completionId[0];
 | 
			
		||||
        const {completionId} = completion;
 | 
			
		||||
        const text = completion.completion;
 | 
			
		||||
        const firstChr = completionId && completionId[0];
 | 
			
		||||
        switch (firstChr) {
 | 
			
		||||
            case "@": {
 | 
			
		||||
                const displayName = completion.completion;
 | 
			
		||||
                const userId = completion.completionId;
 | 
			
		||||
                const member = this._room.getMember(userId);
 | 
			
		||||
                return new UserPillPart(userId, displayName, member);
 | 
			
		||||
            }
 | 
			
		||||
            case "#": {
 | 
			
		||||
                const displayAlias = completion.completionId;
 | 
			
		||||
                return new RoomPillPart(displayAlias, this._client);
 | 
			
		||||
                return this._partCreator.userPill(text, completionId);
 | 
			
		||||
            }
 | 
			
		||||
            case "#":
 | 
			
		||||
                return this._partCreator.roomPill(completionId);
 | 
			
		||||
            // also used for emoji completion
 | 
			
		||||
            default:
 | 
			
		||||
                return new PlainPart(completion.completion);
 | 
			
		||||
                return this._partCreator.plain(text);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,73 +16,68 @@ limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { MATRIXTO_URL_PATTERN } from '../linkify-matrix';
 | 
			
		||||
import { PlainPart, UserPillPart, RoomPillPart, NewlinePart } from "./parts";
 | 
			
		||||
import { walkDOMDepthFirst } from "./dom";
 | 
			
		||||
 | 
			
		||||
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
 | 
			
		||||
 | 
			
		||||
function parseLink(a, room, client) {
 | 
			
		||||
function parseLink(a, partCreator) {
 | 
			
		||||
    const {href} = a;
 | 
			
		||||
    const pillMatch = REGEX_MATRIXTO.exec(href) || [];
 | 
			
		||||
    const resourceId = pillMatch[1]; // The room/user ID
 | 
			
		||||
    const prefix = pillMatch[2]; // The first character of prefix
 | 
			
		||||
    switch (prefix) {
 | 
			
		||||
        case "@":
 | 
			
		||||
            return new UserPillPart(
 | 
			
		||||
                resourceId,
 | 
			
		||||
                a.textContent,
 | 
			
		||||
                room.getMember(resourceId),
 | 
			
		||||
            );
 | 
			
		||||
            return partCreator.userPill(a.textContent, resourceId);
 | 
			
		||||
        case "#":
 | 
			
		||||
            return new RoomPillPart(resourceId, client);
 | 
			
		||||
            return partCreator.roomPill(resourceId);
 | 
			
		||||
        default: {
 | 
			
		||||
            if (href === a.textContent) {
 | 
			
		||||
                return new PlainPart(a.textContent);
 | 
			
		||||
                return partCreator.plain(a.textContent);
 | 
			
		||||
            } else {
 | 
			
		||||
                return new PlainPart(`[${a.textContent}](${href})`);
 | 
			
		||||
                return partCreator.plain(`[${a.textContent}](${href})`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseCodeBlock(n) {
 | 
			
		||||
function parseCodeBlock(n, partCreator) {
 | 
			
		||||
    const parts = [];
 | 
			
		||||
    const preLines = ("```\n" + n.textContent + "```").split("\n");
 | 
			
		||||
    preLines.forEach((l, i) => {
 | 
			
		||||
        parts.push(new PlainPart(l));
 | 
			
		||||
        parts.push(partCreator.plain(l));
 | 
			
		||||
        if (i < preLines.length - 1) {
 | 
			
		||||
            parts.push(new NewlinePart("\n"));
 | 
			
		||||
            parts.push(partCreator.newline());
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return parts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseElement(n, room, client) {
 | 
			
		||||
function parseElement(n, partCreator) {
 | 
			
		||||
    switch (n.nodeName) {
 | 
			
		||||
        case "A":
 | 
			
		||||
            return parseLink(n, room, client);
 | 
			
		||||
            return parseLink(n, partCreator);
 | 
			
		||||
        case "BR":
 | 
			
		||||
            return new NewlinePart("\n");
 | 
			
		||||
            return partCreator.newline();
 | 
			
		||||
        case "EM":
 | 
			
		||||
            return new PlainPart(`*${n.textContent}*`);
 | 
			
		||||
            return partCreator.plain(`*${n.textContent}*`);
 | 
			
		||||
        case "STRONG":
 | 
			
		||||
            return new PlainPart(`**${n.textContent}**`);
 | 
			
		||||
            return partCreator.plain(`**${n.textContent}**`);
 | 
			
		||||
        case "PRE":
 | 
			
		||||
            return parseCodeBlock(n);
 | 
			
		||||
            return parseCodeBlock(n, partCreator);
 | 
			
		||||
        case "CODE":
 | 
			
		||||
            return new PlainPart(`\`${n.textContent}\``);
 | 
			
		||||
            return partCreator.plain(`\`${n.textContent}\``);
 | 
			
		||||
        case "DEL":
 | 
			
		||||
            return new PlainPart(`<del>${n.textContent}</del>`);
 | 
			
		||||
            return partCreator.plain(`<del>${n.textContent}</del>`);
 | 
			
		||||
        case "LI":
 | 
			
		||||
            if (n.parentElement.nodeName === "OL") {
 | 
			
		||||
                return new PlainPart(` 1. `);
 | 
			
		||||
                return partCreator.plain(` 1. `);
 | 
			
		||||
            } else {
 | 
			
		||||
                return new PlainPart(` - `);
 | 
			
		||||
                return partCreator.plain(` - `);
 | 
			
		||||
            }
 | 
			
		||||
        default:
 | 
			
		||||
            // don't textify block nodes we'll decend into
 | 
			
		||||
            if (!checkDecendInto(n)) {
 | 
			
		||||
                return new PlainPart(n.textContent);
 | 
			
		||||
                return partCreator.plain(n.textContent);
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -125,22 +120,22 @@ function checkIgnored(n) {
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function prefixQuoteLines(isFirstNode, parts) {
 | 
			
		||||
function prefixQuoteLines(isFirstNode, parts, partCreator) {
 | 
			
		||||
    const PREFIX = "> ";
 | 
			
		||||
    // a newline (to append a > to) wouldn't be added to parts for the first line
 | 
			
		||||
    // if there was no content before the BLOCKQUOTE, so handle that
 | 
			
		||||
    if (isFirstNode) {
 | 
			
		||||
        parts.splice(0, 0, new PlainPart(PREFIX));
 | 
			
		||||
        parts.splice(0, 0, partCreator.plain(PREFIX));
 | 
			
		||||
    }
 | 
			
		||||
    for (let i = 0; i < parts.length; i += 1) {
 | 
			
		||||
        if (parts[i].type === "newline") {
 | 
			
		||||
            parts.splice(i + 1, 0, new PlainPart(PREFIX));
 | 
			
		||||
            parts.splice(i + 1, 0, partCreator.plain(PREFIX));
 | 
			
		||||
            i += 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseHtmlMessage(html, room, client) {
 | 
			
		||||
function parseHtmlMessage(html, partCreator) {
 | 
			
		||||
    // no nodes from parsing here should be inserted in the document,
 | 
			
		||||
    // as scripts in event handlers, etc would be executed then.
 | 
			
		||||
    // we're only taking text, so that is fine
 | 
			
		||||
@ -159,13 +154,13 @@ function parseHtmlMessage(html, room, client) {
 | 
			
		||||
 | 
			
		||||
        const newParts = [];
 | 
			
		||||
        if (lastNode && (checkBlockNode(lastNode) || checkBlockNode(n))) {
 | 
			
		||||
            newParts.push(new NewlinePart("\n"));
 | 
			
		||||
            newParts.push(partCreator.newline());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (n.nodeType === Node.TEXT_NODE) {
 | 
			
		||||
            newParts.push(new PlainPart(n.nodeValue));
 | 
			
		||||
            newParts.push(partCreator.plain(n.nodeValue));
 | 
			
		||||
        } else if (n.nodeType === Node.ELEMENT_NODE) {
 | 
			
		||||
            const parseResult = parseElement(n, room, client);
 | 
			
		||||
            const parseResult = parseElement(n, partCreator);
 | 
			
		||||
            if (parseResult) {
 | 
			
		||||
                if (Array.isArray(parseResult)) {
 | 
			
		||||
                    newParts.push(...parseResult);
 | 
			
		||||
@ -177,14 +172,14 @@ function parseHtmlMessage(html, room, client) {
 | 
			
		||||
 | 
			
		||||
        if (newParts.length && inQuote) {
 | 
			
		||||
            const isFirstPart = parts.length === 0;
 | 
			
		||||
            prefixQuoteLines(isFirstPart, newParts);
 | 
			
		||||
            prefixQuoteLines(isFirstPart, newParts, partCreator);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        parts.push(...newParts);
 | 
			
		||||
 | 
			
		||||
        // extra newline after quote, only if there something behind it...
 | 
			
		||||
        if (lastNode && lastNode.nodeName === "BLOCKQUOTE") {
 | 
			
		||||
            parts.push(new NewlinePart("\n"));
 | 
			
		||||
            parts.push(partCreator.newline());
 | 
			
		||||
        }
 | 
			
		||||
        lastNode = null;
 | 
			
		||||
        return checkDecendInto(n);
 | 
			
		||||
@ -205,18 +200,18 @@ function parseHtmlMessage(html, room, client) {
 | 
			
		||||
    return parts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function parseEvent(event, room, client) {
 | 
			
		||||
export function parseEvent(event, partCreator) {
 | 
			
		||||
    const content = event.getContent();
 | 
			
		||||
    let parts;
 | 
			
		||||
    if (content.format === "org.matrix.custom.html") {
 | 
			
		||||
        parts = parseHtmlMessage(content.formatted_body || "", room, client);
 | 
			
		||||
        parts = parseHtmlMessage(content.formatted_body || "", partCreator);
 | 
			
		||||
    } else {
 | 
			
		||||
        const body = content.body || "";
 | 
			
		||||
        const lines = body.split("\n");
 | 
			
		||||
        parts = lines.reduce((parts, line, i) => {
 | 
			
		||||
            const isLast = i === lines.length - 1;
 | 
			
		||||
            const text = new PlainPart(line);
 | 
			
		||||
            const newLine = !isLast && new NewlinePart("\n");
 | 
			
		||||
            const text = partCreator.plain(line);
 | 
			
		||||
            const newLine = !isLast && partCreator.newline();
 | 
			
		||||
            if (newLine) {
 | 
			
		||||
                return parts.concat(text, newLine);
 | 
			
		||||
            } else {
 | 
			
		||||
@ -225,7 +220,7 @@ export function parseEvent(event, room, client) {
 | 
			
		||||
        }, []);
 | 
			
		||||
    }
 | 
			
		||||
    if (content.msgtype === "m.emote") {
 | 
			
		||||
        parts.unshift(new PlainPart("/me "));
 | 
			
		||||
        parts.unshift(partCreator.plain("/me "));
 | 
			
		||||
    }
 | 
			
		||||
    return parts;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -107,7 +107,7 @@ class BasePart {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class PlainPart extends BasePart {
 | 
			
		||||
class PlainPart extends BasePart {
 | 
			
		||||
    acceptsInsertion(chr) {
 | 
			
		||||
        return chr !== "@" && chr !== "#" && chr !== ":" && chr !== "\n";
 | 
			
		||||
    }
 | 
			
		||||
@ -199,7 +199,7 @@ class PillPart extends BasePart {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class NewlinePart extends BasePart {
 | 
			
		||||
class NewlinePart extends BasePart {
 | 
			
		||||
    acceptsInsertion(chr, i) {
 | 
			
		||||
        return (this.text.length + i) === 0 && chr === "\n";
 | 
			
		||||
    }
 | 
			
		||||
@ -235,20 +235,10 @@ export class NewlinePart extends BasePart {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class RoomPillPart extends PillPart {
 | 
			
		||||
    constructor(displayAlias, client) {
 | 
			
		||||
class RoomPillPart extends PillPart {
 | 
			
		||||
    constructor(displayAlias, room) {
 | 
			
		||||
        super(displayAlias, displayAlias);
 | 
			
		||||
        this._room = this._findRoomByAlias(displayAlias, client);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _findRoomByAlias(alias, client) {
 | 
			
		||||
        if (alias[0] === '#') {
 | 
			
		||||
            return client.getRooms().find((r) => {
 | 
			
		||||
                return r.getAliases().includes(alias);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            return client.getRoom(alias);
 | 
			
		||||
        }
 | 
			
		||||
        this._room = room;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setAvatar(node) {
 | 
			
		||||
@ -270,7 +260,7 @@ export class RoomPillPart extends PillPart {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UserPillPart extends PillPart {
 | 
			
		||||
class UserPillPart extends PillPart {
 | 
			
		||||
    constructor(userId, displayName, member) {
 | 
			
		||||
        super(userId, displayName);
 | 
			
		||||
        this._member = member;
 | 
			
		||||
@ -311,7 +301,7 @@ export class UserPillPart extends PillPart {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class PillCandidatePart extends PlainPart {
 | 
			
		||||
class PillCandidatePart extends PlainPart {
 | 
			
		||||
    constructor(text, autoCompleteCreator) {
 | 
			
		||||
        super(text);
 | 
			
		||||
        this._autoCompleteCreator = autoCompleteCreator;
 | 
			
		||||
@ -351,8 +341,7 @@ export class PartCreator {
 | 
			
		||||
                updateCallback,
 | 
			
		||||
                getAutocompleterComponent,
 | 
			
		||||
                updateQuery,
 | 
			
		||||
                room,
 | 
			
		||||
                client,
 | 
			
		||||
                this,
 | 
			
		||||
            );
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
@ -362,7 +351,7 @@ export class PartCreator {
 | 
			
		||||
            case "#":
 | 
			
		||||
            case "@":
 | 
			
		||||
            case ":":
 | 
			
		||||
                return new PillCandidatePart("", this._autoCompleteCreator);
 | 
			
		||||
                return this.pillCandidate("");
 | 
			
		||||
            case "\n":
 | 
			
		||||
                return new NewlinePart();
 | 
			
		||||
            default:
 | 
			
		||||
@ -371,24 +360,51 @@ export class PartCreator {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createDefaultPart(text) {
 | 
			
		||||
        return new PlainPart(text);
 | 
			
		||||
        return this.plain(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    deserializePart(part) {
 | 
			
		||||
        switch (part.type) {
 | 
			
		||||
            case "plain":
 | 
			
		||||
                return new PlainPart(part.text);
 | 
			
		||||
                return this.plain(part.text);
 | 
			
		||||
            case "newline":
 | 
			
		||||
                return new NewlinePart(part.text);
 | 
			
		||||
                return this.newline();
 | 
			
		||||
            case "pill-candidate":
 | 
			
		||||
                return new PillCandidatePart(part.text, this._autoCompleteCreator);
 | 
			
		||||
                return this.pillCandidate(part.text);
 | 
			
		||||
            case "room-pill":
 | 
			
		||||
                return new RoomPillPart(part.text, this._client);
 | 
			
		||||
            case "user-pill": {
 | 
			
		||||
                const member = this._room.getMember(part.userId);
 | 
			
		||||
                return new UserPillPart(part.userId, part.text, member);
 | 
			
		||||
            }
 | 
			
		||||
                return this.roomPill(part.text);
 | 
			
		||||
            case "user-pill":
 | 
			
		||||
                return this.userPill(part.text, part.userId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    plain(text) {
 | 
			
		||||
        return new PlainPart(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    newline() {
 | 
			
		||||
        return new NewlinePart("\n");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pillCandidate(text) {
 | 
			
		||||
        return new PillCandidatePart(text, this._autoCompleteCreator);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    roomPill(alias) {
 | 
			
		||||
        let room;
 | 
			
		||||
        if (alias[0] === '#') {
 | 
			
		||||
            room = this._client.getRooms().find((r) => {
 | 
			
		||||
                return r.getAliases().includes(alias);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            room = this._client.getRoom(alias);
 | 
			
		||||
        }
 | 
			
		||||
        return new RoomPillPart(alias, room);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    userPill(displayName, userId) {
 | 
			
		||||
        const member = this._room.getMember(userId);
 | 
			
		||||
        return new UserPillPart(userId, displayName, member);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user