Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[iOS] Mentions API & enable display in example app (#600)
Browse files Browse the repository at this point in the history
* [iOS] Mentions API & enable display in example app

* Document public API and rename `text` to `name`

* Allow creating mentions with no suggestions and expand test suite

* Rename `PermalinkDetector` as `PermalinkReplacer` and document it

* Only show commands if pattern is at the start of the document

* Document and extract function for replacements offset computation

* Merge both variants of mention creation API

* Replace `self.suggestionPattern` usage

* Set `WysiwygPillsFlusher` as an internal non static object and attach it to `WysiwygTextView`

* Move filtering non-leading slash pattern to the Rust side

* Document `replaceLinks` post-parsing function
aringenbach authored Mar 6, 2023
1 parent f958067 commit 55faf8b
Showing 31 changed files with 1,376 additions and 13 deletions.
14 changes: 10 additions & 4 deletions crates/wysiwyg/src/composer_model/menu_action.rs
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ where
return MenuAction::None;
}
let (raw_text, start, end) = self.extended_text(range);
if let Some((key, text)) = Self::pattern_for_text(raw_text) {
if let Some((key, text)) = Self::pattern_for_text(raw_text, start) {
MenuAction::Suggestion(SuggestionPattern {
key,
text,
@@ -71,16 +71,22 @@ where

/// Compute at/hash/slash pattern for a given text.
/// Return pattern key and associated text, if it exists.
fn pattern_for_text(mut text: S) -> Option<(PatternKey, String)> {
fn pattern_for_text(
mut text: S,
start_location: usize,
) -> Option<(PatternKey, String)> {
let Some(first_char) = text.pop_first() else {
return None;
};
let Some(key) = PatternKey::from_char(first_char) else {
return None;
};

// Exclude if there is inner whitespaces.
if text.chars().any(|c| c.is_whitespace()) {
// Exclude slash patterns that are not at the beginning of the document
// and any selection that contains inner whitespaces.
if (key == PatternKey::Slash && start_location > 0)
|| text.chars().any(|c| c.is_whitespace())
{
None
} else {
Some((key, text.to_string()))
6 changes: 6 additions & 0 deletions crates/wysiwyg/src/tests/test_menu_action.rs
Original file line number Diff line number Diff line change
@@ -115,6 +115,12 @@ fn slash_pattern_is_detected() {
assert_eq!(model.compute_menu_action(), sp(Slash, "invi", 0, 5));
}

#[test]
fn slash_pattern_is_not_detected_if_not_at_the_beginning_of_dom() {
let model = cm("abc /invi|");
assert_eq!(model.compute_menu_action(), MenuAction::None);
}

// MenuAction update tests.
#[test]
fn at_pattern_is_updated_on_character_input() {
13 changes: 13 additions & 0 deletions platforms/ios/example/Shared/View+Accessibility.swift
Original file line number Diff line number Diff line change
@@ -46,6 +46,19 @@ public enum WysiwygSharedAccessibilityIdentifier: String {
case showTreeButton = "WysiwygShowTreeButton"
case treeText = "WysiwygTreeText"
case forceCrashButton = "WysiwygForceCrashButton"
case setHtmlButton = "WysiwygSetHtmlButton"
case setHtmlField = "WysiwygSetHtmlField"

// Mock buttons for menu
case aliceButton = "WysiwygMenuAliceButton"
case bobButton = "WysiwygMenuBobButton"
case charlieButton = "WysiwygMenuCharlieButton"
case room1Button = "WysiwygMenuRoom1Button"
case room2Button = "WysiwygMenuRoom2Button"
case room3Button = "WysiwygMenuRoom3Button"
case joinCommandButton = "WysiwygMenuJoinButton"
case inviteCommandButton = "WysiwygMenuInviteButton"
case meCommandButton = "WysiwygMenuMeButton"
}

public extension View {
68 changes: 68 additions & 0 deletions platforms/ios/example/Wysiwyg.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
A64AB144296C747C00F08494 /* WysiwygUITests+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64AB143296C747C00F08494 /* WysiwygUITests+Format.swift */; };
A64AB146296C759A00F08494 /* WysiwygUITests+Quotes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */; };
A64AB148296C769000F08494 /* WysiwygUITests+CodeBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = A64AB147296C769000F08494 /* WysiwygUITests+CodeBlocks.swift */; };
A661FDA729B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A661FDA629B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift */; };
A6852F032981643900632252 /* WysiwygUITests+Lists.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6852F022981643900632252 /* WysiwygUITests+Lists.swift */; };
A6852F052981661000632252 /* WysiwygUITests+Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6852F042981661000632252 /* WysiwygUITests+Indent.swift */; };
A68E713C291D34A50023CC04 /* View+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C2157428C0E95C00C8E727 /* View+Accessibility.swift */; };
@@ -27,10 +28,21 @@
A6C2157528C0E95C00C8E727 /* View+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C2157428C0E95C00C8E727 /* View+Accessibility.swift */; };
A6C2157928C0F62000C8E727 /* WysiwygActionToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C2157828C0F62000C8E727 /* WysiwygActionToolbar.swift */; };
A6C2157C28C0FAAD00C8E727 /* WysiwygAction+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C2157B28C0FAAD00C8E727 /* WysiwygAction+Utils.swift */; };
A6E13E4829A7AB4E00A85A55 /* WysiwygPermalinkReplacer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E4729A7AB4E00A85A55 /* WysiwygPermalinkReplacer.swift */; };
A6E13E4B29A8EE6D00A85A55 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E4A29A8EE6D00A85A55 /* AppDelegate.swift */; };
A6E13E4D29A8EF3500A85A55 /* WysiwygAttachmentViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E4C29A8EF3500A85A55 /* WysiwygAttachmentViewProvider.swift */; };
A6E13E4F29A8F00200A85A55 /* WysiwygTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E4E29A8F00200A85A55 /* WysiwygTextAttachment.swift */; };
A6E13E5129A8F06E00A85A55 /* SerializationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E5029A8F06E00A85A55 /* SerializationService.swift */; };
A6E13E5329A8F0DD00A85A55 /* WysiwygTextAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E5229A8F0DD00A85A55 /* WysiwygTextAttachmentData.swift */; };
A6E13E5529A8F1C400A85A55 /* WysiwygAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E13E5429A8F1C400A85A55 /* WysiwygAttachmentView.swift */; };
A6E6B26F2886D9AA009596F2 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = A6E6B26E2886D9AA009596F2 /* WysiwygComposer */; };
A6F3FC0128D4658000C170E8 /* AlertHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F3FC0028D4658000C170E8 /* AlertHelper.swift */; };
A6F3FC0428D465AF00C170E8 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F3FC0328D465AF00C170E8 /* View.swift */; };
A6F3FC0628DA123900C170E8 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F3FC0528DA123900C170E8 /* UIAlertController.swift */; };
A6F4D0CC29AE082900087A3E /* WysiwygSuggestionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0CB29AE082900087A3E /* WysiwygSuggestionList.swift */; };
A6F4D0CF29AE0C1500087A3E /* Users.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0CE29AE0C1500087A3E /* Users.swift */; };
A6F4D0D129AE0C3200087A3E /* Rooms.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0D029AE0C3200087A3E /* Rooms.swift */; };
A6F4D0D329AE0C5100087A3E /* Commands.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F4D0D229AE0C5100087A3E /* Commands.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
@@ -58,16 +70,29 @@
A64AB143296C747C00F08494 /* WysiwygUITests+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Format.swift"; sourceTree = "<group>"; };
A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Quotes.swift"; sourceTree = "<group>"; };
A64AB147296C769000F08494 /* WysiwygUITests+CodeBlocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+CodeBlocks.swift"; sourceTree = "<group>"; };
A661FDA629B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Suggestions.swift"; sourceTree = "<group>"; };
A6852F022981643900632252 /* WysiwygUITests+Lists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Lists.swift"; sourceTree = "<group>"; };
A6852F042981661000632252 /* WysiwygUITests+Indent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygUITests+Indent.swift"; sourceTree = "<group>"; };
A68E713F291D40710023CC04 /* WysiwygSharedConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygSharedConstants.swift; sourceTree = "<group>"; };
A6C2157428C0E95C00C8E727 /* View+Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Accessibility.swift"; sourceTree = "<group>"; };
A6C2157828C0F62000C8E727 /* WysiwygActionToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygActionToolbar.swift; sourceTree = "<group>"; };
A6C2157B28C0FAAD00C8E727 /* WysiwygAction+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WysiwygAction+Utils.swift"; sourceTree = "<group>"; };
A6E13E4729A7AB4E00A85A55 /* WysiwygPermalinkReplacer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygPermalinkReplacer.swift; sourceTree = "<group>"; };
A6E13E4929A8EC0200A85A55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
A6E13E4A29A8EE6D00A85A55 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
A6E13E4C29A8EF3500A85A55 /* WysiwygAttachmentViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygAttachmentViewProvider.swift; sourceTree = "<group>"; };
A6E13E4E29A8F00200A85A55 /* WysiwygTextAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygTextAttachment.swift; sourceTree = "<group>"; };
A6E13E5029A8F06E00A85A55 /* SerializationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationService.swift; sourceTree = "<group>"; };
A6E13E5229A8F0DD00A85A55 /* WysiwygTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygTextAttachmentData.swift; sourceTree = "<group>"; };
A6E13E5429A8F1C400A85A55 /* WysiwygAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygAttachmentView.swift; sourceTree = "<group>"; };
A6E6B2712886DA6E009596F2 /* WysiwygComposer */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = WysiwygComposer; path = ../lib/WysiwygComposer; sourceTree = "<group>"; };
A6F3FC0028D4658000C170E8 /* AlertHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertHelper.swift; sourceTree = "<group>"; };
A6F3FC0328D465AF00C170E8 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
A6F3FC0528DA123900C170E8 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
A6F4D0CB29AE082900087A3E /* WysiwygSuggestionList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WysiwygSuggestionList.swift; sourceTree = "<group>"; };
A6F4D0CE29AE0C1500087A3E /* Users.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Users.swift; sourceTree = "<group>"; };
A6F4D0D029AE0C3200087A3E /* Rooms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rooms.swift; sourceTree = "<group>"; };
A6F4D0D229AE0C5100087A3E /* Commands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Commands.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
@@ -113,10 +138,14 @@
A6472CA92886CF830021A0E8 /* Wysiwyg */ = {
isa = PBXGroup;
children = (
A6E13E4929A8EC0200A85A55 /* Info.plist */,
A6E13E4A29A8EE6D00A85A55 /* AppDelegate.swift */,
A6472CAA2886CF830021A0E8 /* WysiwygApp.swift */,
A6472CAE2886CF840021A0E8 /* Assets.xcassets */,
A6F3FC0228D4659E00C170E8 /* Extensions */,
A6F4D0CD29AE0BE100087A3E /* Pills */,
A6472CB02886CF840021A0E8 /* Preview Content */,
A6E13E4629A7AB3600A85A55 /* Mocks */,
A6C2157A28C0F63400C8E727 /* Views */,
);
path = Wysiwyg;
@@ -141,6 +170,7 @@
A64AB13F296C73CE00F08494 /* WysiwygUITests+Links.swift */,
A6852F022981643900632252 /* WysiwygUITests+Lists.swift */,
A64AB145296C759A00F08494 /* WysiwygUITests+Quotes.swift */,
A661FDA629B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift */,
A64AB13D296C732500F08494 /* WysiwygUITests+Typing.swift */,
);
path = WysiwygUITests;
@@ -162,10 +192,21 @@
A6472CAC2886CF830021A0E8 /* ContentView.swift */,
66F8D0C828E34D4E00CFA145 /* Composer.swift */,
A6C2157828C0F62000C8E727 /* WysiwygActionToolbar.swift */,
A6F4D0CB29AE082900087A3E /* WysiwygSuggestionList.swift */,
);
path = Views;
sourceTree = "<group>";
};
A6E13E4629A7AB3600A85A55 /* Mocks */ = {
isa = PBXGroup;
children = (
A6F4D0D229AE0C5100087A3E /* Commands.swift */,
A6F4D0D029AE0C3200087A3E /* Rooms.swift */,
A6F4D0CE29AE0C1500087A3E /* Users.swift */,
);
path = Mocks;
sourceTree = "<group>";
};
A6E6B26D2886D9AA009596F2 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -191,6 +232,19 @@
path = Extensions;
sourceTree = "<group>";
};
A6F4D0CD29AE0BE100087A3E /* Pills */ = {
isa = PBXGroup;
children = (
A6E13E5029A8F06E00A85A55 /* SerializationService.swift */,
A6E13E5429A8F1C400A85A55 /* WysiwygAttachmentView.swift */,
A6E13E4C29A8EF3500A85A55 /* WysiwygAttachmentViewProvider.swift */,
A6E13E4729A7AB4E00A85A55 /* WysiwygPermalinkReplacer.swift */,
A6E13E4E29A8F00200A85A55 /* WysiwygTextAttachment.swift */,
A6E13E5229A8F0DD00A85A55 /* WysiwygTextAttachmentData.swift */,
);
path = Pills;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
@@ -339,16 +393,27 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A6E13E4B29A8EE6D00A85A55 /* AppDelegate.swift in Sources */,
A6E13E4F29A8F00200A85A55 /* WysiwygTextAttachment.swift in Sources */,
A6E13E4D29A8EF3500A85A55 /* WysiwygAttachmentViewProvider.swift in Sources */,
A6C2157528C0E95C00C8E727 /* View+Accessibility.swift in Sources */,
A6472CAD2886CF830021A0E8 /* ContentView.swift in Sources */,
A6E13E5329A8F0DD00A85A55 /* WysiwygTextAttachmentData.swift in Sources */,
A6E13E4829A7AB4E00A85A55 /* WysiwygPermalinkReplacer.swift in Sources */,
A6F4D0CF29AE0C1500087A3E /* Users.swift in Sources */,
A6472CAB2886CF830021A0E8 /* WysiwygApp.swift in Sources */,
A6C2157928C0F62000C8E727 /* WysiwygActionToolbar.swift in Sources */,
A6E13E5529A8F1C400A85A55 /* WysiwygAttachmentView.swift in Sources */,
A68E7140291D40710023CC04 /* WysiwygSharedConstants.swift in Sources */,
A6F4D0D129AE0C3200087A3E /* Rooms.swift in Sources */,
A6F4D0D329AE0C5100087A3E /* Commands.swift in Sources */,
A6F4D0CC29AE082900087A3E /* WysiwygSuggestionList.swift in Sources */,
A6F3FC0628DA123900C170E8 /* UIAlertController.swift in Sources */,
A6F3FC0128D4658000C170E8 /* AlertHelper.swift in Sources */,
A6F3FC0428D465AF00C170E8 /* View.swift in Sources */,
A6C2157C28C0FAAD00C8E727 /* WysiwygAction+Utils.swift in Sources */,
66F8D0C928E34D4E00CFA145 /* Composer.swift in Sources */,
A6E13E5129A8F06E00A85A55 /* SerializationService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -360,6 +425,7 @@
A64AB13E296C732500F08494 /* WysiwygUITests+Typing.swift in Sources */,
A6472CC62886CF840021A0E8 /* WysiwygUITests.swift in Sources */,
A64AB140296C73CE00F08494 /* WysiwygUITests+Links.swift in Sources */,
A661FDA729B0ACB400E799A6 /* WysiwygUITests+Suggestions.swift in Sources */,
A68E7141291D40710023CC04 /* WysiwygSharedConstants.swift in Sources */,
A64AB144296C747C00F08494 /* WysiwygUITests+Format.swift in Sources */,
A6852F032981643900632252 /* WysiwygUITests+Lists.swift in Sources */,
@@ -506,6 +572,7 @@
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Wysiwyg/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -535,6 +602,7 @@
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Wysiwyg/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
30 changes: 30 additions & 0 deletions platforms/ios/example/Wysiwyg/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit

final class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
if #available(iOS 15.0, *) {
NSTextAttachment.registerViewProviderClass(WysiwygAttachmentViewProvider.self,
forFileType: WysiwygAttachmentViewProvider.pillUTType)
} else {
// Fallback on earlier versions
}
return true
}
}
36 changes: 36 additions & 0 deletions platforms/ios/example/Wysiwyg/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Mention Pills</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>org.matrix.rte.pills</string>
</array>
</dict>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.text</string>
</array>
<key>UTTypeDescription</key>
<string>Mention Pills</string>
<key>UTTypeIdentifier</key>
<string>org.matrix.rte.pills</string>
</dict>
</array>
</dict>
</plist>
Loading

0 comments on commit 55faf8b

Please # to comment.